第2课 使用FFmpeg读取rtmp流并用openCV显示视频

news2025/4/22 8:43:25

 本课对应源文件下载链接:

https://download.csdn.net/download/XiBuQiuChong/88680079

这节课我们开始利用ffmpeg和opencv来实现一个rtmp播放器。播放器的最基本功能其实就两个:显示画面和播放声音。在实现这两个功能前,我们需要先用ffmpeg连接到rtmp服务器,当然也可以打开一个文件。

1.压缩备份上节课工程文件夹为demo.rar,并修改工程文件夹demo为demo2,及时备份源文件并在原基础上继续迭代开发是一种好习惯。

2.打开fmlp.cpp,修改其中的删除原来init函数中的代码,并加入以下代码:

runFFmpegHandle = CreateThread(NULL, 0, runFFmpegThreadProc, (LPVOID)this, 0, NULL);

如果把MFC对话框相关代码看作主线程函数的话,上述代码的作用是新建一个线程,并在新的线程中执行与ffmpeg及opencv有关的操作。这样做的好处就是实现了“各司其责”,MFC所在的主线程主要用来处理UI(界面)方面的工作,ffmpeg及opencv子线程主要用来处理网络连接、图形处理等方面的工作,互不影响,简洁高效。

3.因为我们需要连接rtmp服务器,所以我们需要在fmlp.h中增加一个字符串类型的rtmp地址;另外还需要定义子线程句柄及相关函数:

CString inRtmpURL;
HANDLE runFFmpegHandle;
static DWORD WINAPI runFFmpegThreadProc(LPVOID lpParam);
int runFFmpeg();
BOOL isRunning = false;

4.在fmlp.cpp中加入对应的函数,调试输出“runFFmpeg...”则表示子线程正常运行。

5.FFmpeg作为开源的跨平台音视频处理工具,提供了丰富的API来处理音视频文件。下面是利用FFmpeg API播放rtmp或rtsp流或文件的工作流程:

(1)打开输入文件:使用avformat_open_input函数打开输入文件,该函数会自动检测文件格式并初始化相应的解码器。

(2)查找流信息:使用avformat_find_stream_info函数查找输入文件中的音视频流信息,包括编码格式、帧率、分辨率等。

(3)查找解码器:根据流信息中的编码格式,使用avcodec_find_decoder函数查找相应的解码器。

(4)打开解码器:使用avcodec_open2函数打开解码器,准备解码音视频数据。

(5)取数据包:使用av_read_frame函数读取音视频数据包,每个数据包包含一个或多个音视频帧。

(6)解码数据包:对于音频数据包,使用avcodec_send_packet和avcodec_receive_frame函数解码。

(7)处解码后的数据:对于音频数据,可以进行音频处理,如音频播放、音频重采样等;对于视频数据,可以进行视频处理,如视频叠加水印、视频滤镜效果等。

(8)编码数据包:对于音频数据,可以使用avcodec_send_frame和avcodec_receive_packet函数进行编码。

(9)写入数据包:使用av_write_frame函数将编码后的数据包写入输出文件或使用av_interleaved_write_frame函数将编码后的数据包推送到rmtp流服务器。

(10)关闭解码器和输入文件:使用avcodec_close函数关闭解码器,使用avformat_close_input函数关闭输入文件。

(11)释放资源:使用avformat_free_context函数释放AVFormatContext结构体和相关资源。

根据上述流程,我们就可以在runFFmpeg函数中正式开始我们的工作了:


int fmlp::runFFmpeg(){

	//返回值
	int ret;
	//rtmp地址,也可以是本地文件
	const char *inFileName = "rtmp://192.168.0.100/vod/sample.mp4";

	//输入文件上下文结构体
	AVFormatContext *inFormatCtx = NULL;

	//视频解码相关
	int videoIndex = -1;
	AVCodec *vDecodec;
	AVCodecContext *vDecodeCtx = NULL;
	//图像转换上下文结构体
	struct SwsContext* bgrSwsCtx = NULL;
	struct SwsContext* yuvSwsCtx = NULL;
	//图像数据数组
	uint8_t* bgrBuff = NULL;
	//读取的数据包
	AVPacket normalPkt;
	//Mat对象
	cv::Mat srcMat;


	//开始时间和当前时间
	int64_t startTime = 0;
	int64_t currentTime = 0;

	//FFmpeg初始化
	av_register_all();
	avcodec_register_all();
	avformat_network_init();


	inFormatCtx = avformat_alloc_context();
	AVDictionary* options = NULL;
	av_dict_set(&options, "buffer_size", "10240", 0);
	av_dict_set(&options, "max_delay", "1000", 0);
	av_dict_set(&options, "max_analyze_duration", "10000", 0);
	av_dict_set(&options, "probesize", "20480", 0);
	av_dict_set(&options, "stimeout", "5000", 0);
	av_dict_set(&options, "listen_time", "5000", 0);
	av_dict_set(&options, "initial_timeout", "5000", 0);
	av_dict_set(&options, "preset", "ultrafast", 0);
	av_dict_set(&options, "tune", "zerolatency", 0);

	if ((ret = avformat_open_input(&inFormatCtx, inFileName, 0, &options)) < 0)
	{
		TRACE("无法打开输入流.\n");
		return -1;
	}

	if (ret == 0){
		isRunning = true;
	}
	else{
		isRunning = false;
	}

	if ((ret = avformat_find_stream_info(inFormatCtx, 0)) < 0)
	{
		TRACE("查找输入流信息失败.\n");
		return -1;
	}
	//获取音视频流通道ID
	for (int i = 0; i < inFormatCtx->nb_streams; i++){

		if (inFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
		{
			videoIndex = i;
		}
		
	}

	TRACE("视频流通道索引%d\n", videoIndex);
	//初始化并打开视频解码器
	vDecodec = avcodec_find_decoder(inFormatCtx->streams[videoIndex]->codecpar->codec_id);
	vDecodeCtx = avcodec_alloc_context3(vDecodec);
	avcodec_parameters_to_context(vDecodeCtx, inFormatCtx->streams[videoIndex]->codecpar);
	avcodec_open2(vDecodeCtx, vDecodec, 0);

	av_dump_format(inFormatCtx, 0, inFileName, 0);

	//解码后的原始视频帧
	AVFrame *deVideoFrame = av_frame_alloc();
	//缩放后的视频帧
	AVFrame bgrFrame = { 0 };
	bgrFrame.width = 960;
	bgrFrame.height = 540;
	bgrFrame.format = AV_PIX_FMT_BGR24;
	int bgrFrameSize = av_image_get_buffer_size((AVPixelFormat)bgrFrame.format, bgrFrame.width, bgrFrame.height, 1);
	bgrBuff = (uint8_t*)av_malloc(bgrFrameSize);
	av_image_fill_arrays(bgrFrame.data, bgrFrame.linesize, bgrBuff, (AVPixelFormat)bgrFrame.format, bgrFrame.width, bgrFrame.height, 1);
	//获取图像转换上下文
	bgrSwsCtx = sws_getContext(vDecodeCtx->width, vDecodeCtx->height, vDecodeCtx->pix_fmt, bgrFrame.width, bgrFrame.height, (AVPixelFormat)bgrFrame.format, SWS_BICUBIC, NULL, NULL, NULL);

	//获取开始时间
	startTime = av_gettime();
	while (isRunning)
	{
		ret = av_read_frame(inFormatCtx, &normalPkt);
		if (ret < 0){
			break;
		}

		//当数据包时间快于当前时间则延当延时
		currentTime = (av_gettime() - startTime) / 1000;
		if (normalPkt.pts > currentTime){
			Sleep(normalPkt.pts - currentTime);
		}

		if (normalPkt.stream_index == videoIndex)
		{
			ret = avcodec_send_packet(vDecodeCtx, &normalPkt);
			ret = avcodec_receive_frame(vDecodeCtx, deVideoFrame);
			av_packet_unref(&normalPkt);
			ret = sws_scale(bgrSwsCtx, (const uint8_t* const*)deVideoFrame->data, deVideoFrame->linesize, 0, deVideoFrame->height, bgrFrame.data, bgrFrame.linesize);
			srcMat = cv::Mat(bgrFrame.height, bgrFrame.width, CV_8UC3, bgrFrame.data[0]);
			imshow("viceo", srcMat);
			cv::waitKey(10);
			//mainDlg->drawMatOfPlay(srcMat);
			//av_frame_unref(deVideoFrame);
		}
	}


	av_dict_free(&options);
	avformat_close_input(&inFormatCtx);
	isRunning = false;
	return 0;
}

6.先不用管那么多,先运行起来看看效果吧。如果能弹出窗口显示图像则表示连接rtmp服务器成功并成功拿到视频数据。

7.上面的视频显示是利用openCV的内置函数来imshow来实现的,会弹出一个新的窗口,这样会显得会怪异。为了让视频显示在MFC对话框中,需要先在对话框中添加一个名为IDC_playPic的Picture Control 控件,并加入显示函数:

void CdemoDlg::drawMatOfPlay(cv::Mat &img)
{
	CDC *playCDC;
	CRect rectForPlay;
	cv::Mat scaleMatForPlay;
	playCDC = myWnd->GetDlgItem(IDC_playPic)->GetDC();
	myWnd->GetDlgItem(IDC_playPic)->GetClientRect(&rectForPlay);
	cv::resize(img, scaleMatForPlay, cv::Size(rectForPlay.Width(), rectForPlay.Height()));	
	switch (scaleMatForPlay.channels())
	{
	case 1:
		cv::cvtColor(scaleMatForPlay, scaleMatForPlay, CV_GRAY2BGRA);
		break;
	case 3:
		cv::cvtColor(scaleMatForPlay, scaleMatForPlay, CV_BGR2BGRA); 
		break;
	default:
		break;
	}

	int pixelBytes = scaleMatForPlay.channels()*(scaleMatForPlay.depth() + 1);
	
	BITMAPINFO bitInfo;
	bitInfo.bmiHeader.biBitCount = 8 * pixelBytes;
	bitInfo.bmiHeader.biWidth = scaleMatForPlay.cols;
	bitInfo.bmiHeader.biHeight = -scaleMatForPlay.rows;
	bitInfo.bmiHeader.biPlanes = 1;
	bitInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
	bitInfo.bmiHeader.biCompression = BI_RGB;
	bitInfo.bmiHeader.biClrImportant = 0;
	bitInfo.bmiHeader.biClrUsed = 0;
	bitInfo.bmiHeader.biSizeImage = 0;
	bitInfo.bmiHeader.biXPelsPerMeter = 0;
	bitInfo.bmiHeader.biYPelsPerMeter = 0;	
	StretchDIBits(
		playCDC->GetSafeHdc(),
		0, 0, rectForPlay.Width(), rectForPlay.Height(),
		0, 0, rectForPlay.Width(), rectForPlay.Height(),
		scaleMatForPlay.data,
		&bitInfo,
		DIB_RGB_COLORS,
		SRCCOPY
		);
	ReleaseDC(playCDC);
}

8.在fmlp.cpp中调用显示函数:

//imshow("viceo", srcMat);
//cv::waitKey(10);
mainDlg->drawMatOfPlay(srcMat);
av_frame_unref(deVideoFrame);

再次运行,效果立现:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1346183.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

蓝桥杯嵌入式KEY

1.按键原理图 2.按键GPIO引脚设置成输入&#xff0c;上拉模式 3.设置TIM4时钟源为外部时钟源 PSC为80-1 Period为10000-1 打开NVIC 中断时间为10ms 4.在bsp文件中添加interrupt.c文件 5.按键单击代码 6.长按键 7.按键过程和显示过程

动态规划 典型例题

总结 动态规划的的四个解题步骤是&#xff1a; 定义子问题写出子问题的递推关系确定 DP 数组的计算顺序空间优化&#xff08;可选&#xff09; from functools import cache cache #缓存&#xff0c;避免重复运算 def dfs(i)->int:if 终止: return 0 #具体返回什么值要看…

在线尺码计算

在线衣服尺码计算 尺码不确定的话&#xff0c;可以填写身高、体重生成可以参考的尺码还是不错的 工具简介 选购时请综合参考尺码表中的各项参数&#xff0c;这有助您选择到更好的尺码。 该尺码计算工具仅供参考&#xff0c;测量脚时请注意用适当力度轻踩水平面上。因测量方法不…

Tomcat和Servlet

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、Tomcat是什么&#xff1f;1.1下载&#xff1a;1.2 tomcat是什么1.3启动服务器&#xff1a; 二.部署三、Servlet3.1创建项目3.2引入依赖pom.xml的所有代码 3…

electron——查看electron的版本(代码片段)

electron——查看electron的版本(代码片段)1.使用命令行&#xff1a; npm ls electron 操作如下&#xff1a; 2.在软件内使用代码&#xff0c;如下&#xff1a; console.log(process) console.log(process.versions.electron) process 里包含很多信息&#xff1a; process详…

Python装饰器的专业解释

装饰器&#xff0c;其实是用到了闭包的原理来进行操作的。 单个装饰器&#xff1a; 以下是一个简单的例子&#xff1a; def outer(func):print("OUTER enter ...")def wrapper(*args, **kwargs):print("调用之前......")result func(*args, **kwargs)p…

4.31 构建onnx结构模型-Tile

前言 构建onnx方式通常有两种&#xff1a; 1、通过代码转换成onnx结构&#xff0c;比如pytorch —> onnx 2、通过onnx 自定义结点&#xff0c;图&#xff0c;生成onnx结构 本文主要是简单学习和使用两种不同onnx结构&#xff0c; 下面以 Tile 结点进行分析 方式 方法一…

用Xshell连接虚拟机的Ubuntu20.04系统记录。虚拟机Ubuntu无法上网。本机能ping通虚拟机,反之不能。互ping不通

先别急着操作&#xff0c;看完再试。 如果是&#xff1a;本机能ping通虚拟机&#xff0c;反之不能。慢慢看到第8条。 如果是&#xff1a;虚拟机不能上网&#xff08;互ping不通&#xff09;&#xff0c;往下一直看。 系统是刚装的&#xff0c;安装步骤&#xff1a;VMware虚拟机…

DevOps系列 之 Python与Java互相调用的案例

Python和Java是两种非常流行的编程语言。Python是一种解释型语言&#xff0c;而Java则是一种编译型语言。两者都有广泛的应用&#xff0c;尤其是在测试领域。在本文中&#xff0c;我们将讨论如何使用Python测试Java源代码&#xff0c;Java如何调用Python脚本。 单元测试 单元…

redis的基本使用

一、 Redis简介 Redis是一个基于内存的 key-value 结构数据库。Redis是一款采用key-value数据存储格式的内存级NoSQL数据库&#xff0c;重点关注数据存储格式&#xff0c;是key-value格式&#xff0c;也就是键值对的存储形式。与MySQL数据库不同&#xff0c;MySQL数据库有表、…

WEB 3D技术 three.js 雾 基础使用讲解

本文 我们说一下 雾 在three.js中有一个 Fog类 它可以创建线性雾的一个效果 她就是模仿现实世界中 雾的一个效果 你看到远处物体会组件模糊 直到完全被雾掩盖 在 three.js 中 有两种雾的形式 一种是线性的 一种是指数的 个人觉得 线性的会看着自然一些 他是 从相机位置开始 雾…

Unity坦克大战开发全流程——开始场景——音效数据逻辑

开始场景——音效数据逻辑 从这里开始到后面的三小节我们都将干一件很重要的事——数据存储&#xff0c;只有实现了数据存储才能在再次进入游戏时保持游戏数据不被丢失。 类图分析&#xff1a;数据管理类是一个大类&#xff0c;它其中关联了两个类&#xff08;这两个类都是数据…

2023年终总结 —— 我和CSDN相遇的第一年之“技术学习和个人成长的回顾与展望”

​ ​ &#x1f308;个人主页: Aileen_0v0 &#x1f525;热门专栏: 华为鸿蒙系统学习|计算机网络|数据结构与算法 &#x1f4ab;个人格言:"没有罗马,那就自己创造罗马~" 目录 &#x1f38a;对2023的总结与回顾&#x1f38a; &#x1f3c5;获奖记录 &#x1f4da;学…

test mock-03-wiremock 模拟 HTTP 服务的开源工具 flexible and open source API mocking

拓展阅读 test 之 jmockit-01-overview jmockit-01-test 之 jmockit 入门使用案例 mockito-01-overview mockito 简介及入门使用 PowerMock Mock Server ChaosBlade-01-测试混沌工程平台整体介绍 jvm-sandbox 入门简介 wiremock WireMock是一个流行的开源工具&#xf…

蚂蚁实习一面面经

蚂蚁实习一面面经 希望可以帮助到大家 tcp建立连接为什么要三次握手&#xff1f; 三次握手的过程 注意&#xff1a;三次握手的最主要目的是保证连接是双工的&#xff0c;可靠更多的是通过重传机制来保证的 所谓三次握手&#xff0c;即建立TCP连接&#xff0c;需要客户端和…

Centos7部署Keepalived+lvs服务

IP规划&#xff1a; 服务器IP地址主服务器20.0.0.22/24从服务器20.0.0.24/24Web-120.0.0.26/24Web-220.0.0.27/24 一、主服务器安装部署keepalivedlvs服务 1、调整/proc响应参数 关闭Linux内核的重定向参数&#xff0c;因为LVS负载服务器和两个页面服务器需要共用一个VIP地…

力扣刷题记录(23)LeetCode:718、1143、1035

718. 最长重复子数组 要想到用一个二维数组dp去表示数组nums1和nums2的公共子数组的最大长度。其中二维数组的索引 i、j 分别表示nums1中[0,i-1]数组、nums2中[0,j-1]数组。如果满足nums1[i-1]nums2[j-1],那么dp[i][j]dp[i-1][j-1]1 class Solution { public:int findLength(v…

科技创新实验室数据管理优选:高效企业网盘推荐

科技创新实验室建设是国家加强科技创新基本能力建设的重要措施&#xff0c;企业网盘等高效办公工具的应用是保证科技创新实验室正常运行、提高科研项目团队合作效率的重要手段。 本文将介绍企业网盘Zoho WorkDrive提供的解决方案&#xff1a; 行业痛点1&#xff1a;分散的数据…

CentOS虚拟机硬盘管理

CentOS虚拟机硬盘管理 一、创建虚拟机时分配硬盘 创建虚拟机时&#xff0c;在下图这个页面需要重新选择一下硬盘&#xff0c;可以对硬盘进行配置。 默认自动分区 ![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/e9ce72af3d934e75be95f7f86860e92b.png 选择确认分…

GitHub的2FA验证问题解决工具

文章目录 前言认识2FA开源工具使用&#xff1a;AuthenticatorPro获取AuthenticatorPro的安卓APK如何使用 参考文章 前言 打开GitHub跳出来这个提示&#xff0c;需要进行验证&#xff1a; 如何解决呢&#xff1f;方案有很多&#xff0c;我们可以使用开源的一个工具&#xff1a;…