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

news/2024/6/3 17:40:18 标签: ffmpeg, opencv, c++

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

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

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

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

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

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(){
	TRACE("runFFmpeg...\n");
	AVCodec *vDecodec;
	AVCodecContext *vDecodeCtx = NULL;
	AVFormatContext *inFormatCtx = NULL;
	AVPacket normalPkt;
	const char *inFileName;
	int ret;
	int videoIndex = -1;	
	struct SwsContext* bgrSwsCtx = NULL;
	struct SwsContext* yuvSwsCtx = NULL;
	uint8_t* bgrBuff = NULL;
	uint8_t* yuvBuff = NULL;
	cv::Mat srcMat;
	int64_t startTime = 0;
	int64_t currentTime = 0;
	inRtmpURL = "rtmp://192.168.0.100/vod/xhn.mp4";
	CW2A  inputURL(inRtmpURL);
	char inputStr[1000] = { 0 };
	sprintf(inputStr, "%s", inputURL);
	inFileName = inputStr;	
	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;
	}

	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 bgrFrame = { 0 };
	bgrFrame.width = 640;
	bgrFrame.height = 480;
	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);



	AVFrame *deVideoFrame = av_frame_alloc();
	while (isRunning)
	{
		ret = av_read_frame(inFormatCtx, &normalPkt);
		if (ret < 0){
			break;
		}	

		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);			
		}
		
	}

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

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


http://www.niftyadmin.cn/n/5290262.html

相关文章

php如何在header增加key,sign,timestamp,怎么实现鉴权?

一、要在PHP中的header中增加key、sign和timestamp&#xff0c;并实现鉴权 &#xff0c;可以按照以下步骤进行操作&#xff1a; 生成timestamp&#xff1a;使用time()函数获取当前时间戳。 生成sign&#xff1a;将需要鉴权的数据&#xff08;例如请求参数&#xff09;按照一定…

centos 编译安装 python 和 openssl

安装环境&#xff1a; centos 7.9 &#xff1a; python 3.10.5 和 openssl 3.0.12 centos 6.10 &#xff1a; python 3.10.5 和 openssl 1.1.1 两个环境都能安装成功&#xff0c;可以正常使用。 安装 openssl 下载地址 下载后解压&#xff0c;进入到解压目录 执行&#xf…

Spring Boot+RocketMQ 实现多实例分布式环境下的事件驱动

为什么要使用MQ&#xff1f; 在Spring Boot Event这篇文章中已经通过Guava或者SpringBoot自身的Listener实现了事件驱动&#xff0c;已经做到了对业务的解耦。为什么还要用到MQ来进行业务解耦呢&#xff1f; 首先无论是通过Guava还是Spring Boot自身提供的监听注解来实现的事…

gulimall-002 分布式基础概念

1、微服务概念 微服务是一种非常流行的架构风格。 拒绝大型单体应用&#xff0c;基于业务边界进行服务微化拆分&#xff0c;各个服务独立部署运行。 每个服务运行在自己的单个进程使用轻量级机制通信可以使用不同的编程语言编写以及不同的数据存储技术 2、集群&分布式&…

Hive实战:统计总分与平均分

文章目录 一、实战概述二、提出任务三、完成任务&#xff08;一&#xff09;准备数据文件1、在虚拟机上创建文本文件2、将文本文件上传到HDFS指定目录 &#xff08;二&#xff09;实现步骤1、启动Hive Metastore服务2、启动Hive客户端3、创建Hive表&#xff0c;加载HDFS数据文件…

Flask 账号管理列表

Flask 账号管理列表 web/controllers/account/Account.py /index route_account Blueprint( account_page,__name__ )route_account.route( "/index" ) def index():resp_data {}req request.valuespage int( req[p] ) if ( p in req and req[p] ) else 1qu…

C(C++)数组越界但能正常运行?关于数组越界和变量内存地址的一点研究:何时地址连续

C(C)数组越界但能正常运行&#xff1f;关于数组越界和变量内存地址的一点研究&#xff1a;何时地址连续 前言 今天美丽的本科同班同学xyy问了我一个问题&#xff1a; 她出了一道C基础题&#xff1a; 第一行输入一个正整数 n n n&#xff08; 1 ≤ n ≤ 100 1\leq n\leq 100 …

DRF(Django Rest Framework)框架基于restAPI协议规范的知识点总结

Django Rest Framework学习 一、初识DRF组件 1.1 FBV和CBV FBV&#xff1a;function based view from django.shortcuts import render, HttpResponse # 这种是基于FBV模式 函数模式实现Views函数 def login(request):if request.method GET:return HttpResponse("Get…