FFmpeg与OpenCV联合开发

news2024/11/17 4:34:27

本文讲述如何利用FFmpeg SDK与OpenCV 从RTSP流中获取图像(OpenCV MAT 对象格式)。

一,构造RTSP视频流

因为是在本机实验,所以我自己构造了一个RTSP流。如果你有现成的RTSP流也可以的。
实验用的源视频是黑神话·悟空的《云宫讯音》。
在这里插入图片描述
自己产生一个RTSP视频流得需要一个RTSP流媒体服务器。目前可以用的RTSP流媒体服务器有两个,分别是ZLMediaKit和MediaMTX两个。ZLMediaKit需要编译,我编译好了。MediaMTX下载即用。这里我选了MediaMTX,下载下来就可以用,比较轻量化。

1,启动流媒体服务器

在MediaMTX的安装目录下启动即可。
在这里插入图片描述

2,启动FFmpeg推流

ffmpeg -re -stream_loop -1 -i “wukong.mp4” -vcodec copy -acodec copy -f rtsp -rtsp_transport tcp rtsp://192.168.76.189:8554/live/test
在这里插入图片描述
这是流的相关信息:
Input #0, rtsp, from ‘rtsp://192.168.76.189:8554/live/test’:f=0/0
Metadata:
title : No Name
Duration: N/A, start: 0.000000, bitrate: N/A
Stream #0:0: Video: h264 (Main), yuv420p(tv, bt709, progressive), 1920x1080 [SAR 1:1 DAR 16:9], 30 fps, 30 tbr, 90k tbn
Stream #0:1: Audio: aac (LC), 44100 Hz, stereo, fltp

3,用FFplay播放一下

在这里插入图片描述

二,源代码

#include <stdio.h>
#include <thread>
#include <string>
//#include <io.h>
//#include <vector>

//#define __STDC_CONSTANT_MACROS
#include <iostream>
using namespace std;
extern "C"
{
#include "libavformat/avformat.h"
#include "libavcodec/avcodec.h"
#include "libavutil/time.h"
//#include "libavutil/log.h"
//#include "libavutil/mathematics.h"
#include "libswscale/swscale.h"
//#include "libavutil/imgutils.h"
}

#include <opencv2/opencv.hpp>

using namespace cv;


int main(int argc, char* argv[])
{
	Mat pCvMat;
	//const string sourceWindow = "test";
	//namedWindow(sourceWindow,1);

	//cout<<avcodec_version()<<endl;
	//cout<<FFMPEG_VERSION<<endl;
	unsigned codecVer = avcodec_version();
	int ver_major, ver_minor, ver_micro;
	ver_major = (codecVer >> 16) & 0xff;
	ver_minor = (codecVer >> 8) & 0xff;
	ver_micro = (codecVer) & 0xff;
	printf("Current ffmpeg version is:    ,avcodec version is: %d=%d.%d.%d\n",
		codecVer, ver_major, ver_minor, ver_micro);

	int startTime = 0;                           // 记录播放开始
	int currentFrame = 0;                           // 当前帧序号
	double fps = 0;                                 // 帧率
	double interval = 0;                            // 帧间隔

	const AVInputFormat* p_ifmt_v = NULL;
	// ffmpeg相关变量预先定义与分配
	AVFormatContext* pAVFormatContext = 0;          // ffmpeg的全局上下文,所有ffmpeg操作都需要
	AVStream* pAVStream = 0;                        // ffmpeg流信息
	AVCodecContext* pAVCodecContext = avcodec_alloc_context3(NULL);            // ffmpeg编码上下文
	const AVCodec* pAVCodec = 0;                          // ffmpeg编码器
	AVPacket* pAVPacket = 0;                        // ffmpag单帧数据包
	AVFrame* pAVFrame = 0;                          // ffmpeg单帧缓存
	//AVFrame *pAVFrameBGR24 = 0;                     // ffmpeg单帧缓存转换颜色空间后的缓存
	struct SwsContext* pSwsContext = 0;             // ffmpeg编码数据格式转换
	AVDictionary* pAVDictionary = 0;                // ffmpeg数据字典,用于配置一些编码器属性等

	//unsigned char * outBuffer = 0;                           // 解码后的数据存放缓存区
	//const char* url_v = "video.sdp";
	int ret = 0;                                    // 函数执行结果
	int videoIndex = -1;                            // 音频流所在的序号
	//int numBytes = 0;                               // 解码后的数据长度


	pAVFormatContext = avformat_alloc_context();    // 分配
	//pAVFormatContext->flags |= AVFMT_NOFILE;
	//添加白名单,这里很重要,如果不申请内存,在avformat_close_input中会宕
	//pAVFormatContext->protocol_whitelist = (char*)av_malloc(sizeof("file,udp,rtp"));
	//memcpy(pAVFormatContext->protocol_whitelist, "file,udp,rtp", sizeof("file,udp,rtp"));

	//pAVPacket = av_packet_alloc();                  // 分配
	pAVPacket = (AVPacket*)av_malloc(sizeof(AVPacket));
	pAVFrame = av_frame_alloc();                    // 分配
	//pAVFrameBGR24 = av_frame_alloc();               // 分配
	
	p_ifmt_v = av_find_input_format("rtsp");

	//if(!pAVFormatContext || !pAVPacket || !pAVFrame || !pAVFrameBGR24)
	if (!pAVFormatContext || !pAVPacket || !pAVFrame)
	{
		cout << "Failed to alloc" << endl;
		system("pause");
		return -1;
	}
	// 步骤一:注册所有容器和编解码器(也可以只注册一类,如注册容器、注册编码器等)
	//av_register_all();
	avformat_network_init();
	// 步骤二:打开文件(ffmpeg成功则返回0)

	//std::string src = "rtsp://192.168.76.215/live/test";
	std::string src = argv[1];
	cout << "打开:" << src << endl;

	AVDictionary* format_opts = NULL;
	av_dict_set(&format_opts, "rtsp_transport", "tcp", 0); //设置推流的方式,默认udp。
	ret = avformat_open_input(&pAVFormatContext, src.c_str(), p_ifmt_v, &format_opts);//nullptr

	//ret = avformat_open_input(&pAVFormatContext, src.c_str(), p_ifmt_v, nullptr);
	if (ret)
	{
		cout << "Failed" << endl;
		system("pause");
		return -1;
	}
	// 步骤三:探测流媒体信息
	ret = avformat_find_stream_info(pAVFormatContext, 0);
	if (ret < 0)
	{
		cout << "Failed to avformat_find_stream_info(pAVFormatContext, 0)" << endl;
		system("pause");
		return -1;
	}
	printf("******************\n");
	av_dump_format(pAVFormatContext, 0, src.c_str(), 0);
	printf("******************\n");
	// 步骤四:提取流信息,提取视频信息
	for (int index = 0; index < pAVFormatContext->nb_streams; index++)
	{
		if (pAVFormatContext->streams[index]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
		{
			cout << "流序号:" << index << "\n类型为:" << "AVMEDIA_TYPE_VIDEO" << endl;
			//pAVCodecContext = pAVFormatContext->streams[index]->codecpar->;
			avcodec_parameters_to_context(pAVCodecContext, pAVFormatContext->streams[index]->codecpar);
			pAVStream = pAVFormatContext->streams[index];
			videoIndex = index;
			break;
		}
	}

	if (videoIndex == -1 || !pAVCodecContext)
	{
		cout << "Failed to find video stream" << endl;
		system("pause");
		return -1;
	}

	// 步骤五:对找到的视频流寻解码器
	pAVCodec = avcodec_find_decoder(pAVCodecContext->codec_id);
	if (!pAVCodec)
	{
		cout << "Fialed to avcodec_find_decoder(pAVCodecContext->codec_id):"
			<< pAVCodecContext->codec_id << endl;
		system("pause");
		return -1;
	}

	// 步骤六:打开解码器
	// 设置缓存大小 1024000byte
	av_dict_set(&pAVDictionary, "buffer_size", "8192000", 0);
	// 设置超时时间 20s
	av_dict_set(&pAVDictionary, "stimeout", "20000000", 0);
	// 设置最大延时 3s
	av_dict_set(&pAVDictionary, "max_delay", "30000000", 0);

	// 设置打开方式 tcp/udp
	av_dict_set(&pAVDictionary, "rtsp_transport", "tcp", 0);

	//ret = avcodec_open2(pAVCodecContext, pAVCodec, &pAVDictionary);
	ret = avcodec_open2(pAVCodecContext, pAVCodec, &pAVDictionary);

	if (ret)
	{
		cout << "Failed to avcodec_open2(pAVCodecContext, pAVCodec, pAVDictionary)" << endl;
		system("pause");
		return -1;
	}

	// 显示视频相关的参数信息(编码上下文)
	cout << "比特率:" << pAVCodecContext->bit_rate << endl;
	cout << "宽高:" << pAVCodecContext->width << "x" << pAVCodecContext->height << endl;
	cout << "格式:" << pAVCodecContext->pix_fmt << endl;  // AV_PIX_FMT_YUV420P 0
	cout << "帧率分母:" << pAVCodecContext->time_base.den << endl;
	cout << "帧率分子:" << pAVCodecContext->time_base.num << endl;
	cout << "帧率分母:" << pAVStream->avg_frame_rate.den << endl;
	cout << "帧率分子:" << pAVStream->avg_frame_rate.num << endl;
	cout << "总时长:" << pAVStream->duration / 10000.0 << "s" << endl;
	cout << "总帧数:" << pAVStream->nb_frames << endl;
	// 有总时长的时候计算帧率(较为准确)
	//    fps = pAVStream->nb_frames / (pAVStream->duration / 10000.0);
	//    interval = pAVStream->duration / 10.0 / pAVStream->nb_frames;
	// 没有总时长的时候,使用分子和分母计算
	fps = pAVStream->avg_frame_rate.num * 1.0f / pAVStream->avg_frame_rate.den;
	interval = 1 * 1000 / fps;
	cout << "平均帧率:" << fps << endl;
	cout << "帧间隔:" << interval << "ms" << endl;
	// 步骤七:对拿到的原始数据格式进行缩放转换为指定的格式高宽大小

	pAVCodecContext->pix_fmt = (AVPixelFormat)AV_PIX_FMT_YUV420P;
	pSwsContext = sws_getContext(pAVCodecContext->width,
		pAVCodecContext->height,
		pAVCodecContext->pix_fmt,
		pAVCodecContext->width,
		pAVCodecContext->height,
		AV_PIX_FMT_BGR24,
		SWS_FAST_BILINEAR,
		0,
		0,
		0);

	cout << "sws_getContext!" << endl;


	pCvMat = Mat(pAVCodecContext->height, pAVCodecContext->width, CV_8UC3);

	int cvLinesizes[1];
	cvLinesizes[0] = pCvMat.step1();

	// 步骤八:读取一帧数据的数据包
	//av_free_packet(pAVPacket);
	//av_packet_unref(pAVPacket);
	int frame_count = 0;
	//cout << "now av_read_frame!" << endl;
	//namedWindow("img", 0);
	while (av_read_frame(pAVFormatContext, pAVPacket) >= 0)
	{
		if (pAVPacket->stream_index == videoIndex)
		{
			//cout << "av_read_frame!" << endl;
			// 步骤八:对读取的数据包进行解码
			ret = avcodec_send_packet(pAVCodecContext, pAVPacket);
			//ret = avcodec_decode_video2(pAVCodecContext, pAVFrame, &got_picture, pAVPacket);
			if (ret)
			{
				cout << "Failed to avcodec_send_packet(pAVCodecContext, pAVPacket) ,ret ="
					<< ret << endl;
				break;
			}
			//cout << "avcodec_send_packet!" << endl;
			av_frame_unref(pAVFrame);

			while (!avcodec_receive_frame(pAVCodecContext, pAVFrame))
			{
				//pCvMat.data=NULL;
				sws_scale(pSwsContext,
					(const uint8_t* const*)pAVFrame->data,
					pAVFrame->linesize,
					0,
					pAVCodecContext->height,
					&pCvMat.data,
					cvLinesizes);
				//cout << "avcodec_receive_frame!" << endl;
				frame_count++;
				if (frame_count % int(25) == 0) {
					//sprintf()
					//char * temp;
					//itoa(frame_count,temp,10);
					//string sss=temp;
					imwrite("./image/test_" + std::to_string(frame_count) + ".bmp", pCvMat);
					//imshow("img", pCvMat);

					cout << "SAVE: " << std::to_string(frame_count) << endl;
				}
				//threshold(pCvMat,pCvMat,128,255,THRESH_BINARY);
				//imshow(sourceWindow,pCvMat);
				//waitKey(1);
				av_frame_unref(pAVFrame);
			}
			// 下一帧
			currentFrame++;
			while (av_gettime() - startTime < currentFrame * interval)
			{
				//Sleep(1);
				std::this_thread::sleep_for(std::chrono::milliseconds(1));
			}
			//cout << "current:" << currentFrame <<"," << time << av_gettime()- startTime <<endl;

		}
		//av_free_packet(pAVPacket);
		av_packet_unref(pAVPacket);
	}

	cout << "释放回收资源" << endl;

	if (pSwsContext)
	{
		sws_freeContext(pSwsContext);
		pSwsContext = 0;
		cout << "sws_freeContext(pSwsContext)" << endl;
	}
	if (pAVFrame)
	{
		av_frame_free(&pAVFrame);
		pAVFrame = 0;
		cout << "av_frame_free(pAVFrame)" << endl;
	}
	if (pAVPacket)
	{
		//av_free_packet(pAVPacket);
		pAVPacket = 0;
		cout << "av_free_packet(pAVPacket)" << endl;
	}
	if (pAVCodecContext)
	{
		avcodec_free_context(&pAVCodecContext);
		avcodec_close(pAVCodecContext);
		pAVCodecContext = 0;
		cout << "avcodec_close(pAVCodecContext);" << endl;
	}
	if (pAVFormatContext)
	{
		avformat_close_input(&pAVFormatContext);
		avformat_free_context(pAVFormatContext);
		pAVFormatContext = 0;
		cout << "avformat_free_context(pAVFormatContext)" << endl;
	}
	pCvMat = NULL;
	system("pause");
	return 0;
}

三,运行一下

TestFFmpegOpencv_RTSP.exe rtsp://192.168.76.189:8554/live/test
在这里插入图片描述

四,结果

提取出的帧结果保存在image文件夹下:
在这里插入图片描述

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

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

相关文章

苹果CMS vs. 海洋CMS:哪个系统更易于百度收录?

在选择网站内容管理系统&#xff08;影视网站选择那个CMS&#xff1f;&#xff09;时&#xff0c;收录效率和优化能力是关键考量因素。苹果CMS和海洋CMS都是受欢迎的选项&#xff0c;但在百度收录效果上&#xff0c;苹果CMS表现得更为出色。以下将详细探讨苹果CMS为何在百度收录…

房产销售系统|基于java和vue的房产销售系统(源码+数据库+文档)

房产销售|房地产|卖房系统 目录 基于java和vue的房产销售系统 一、前言 二、系统设计 三、系统功能设计 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、源码获取&#xff1a; 博主介绍&#xff1a;✌️大厂码农|毕设布道师&#xff0c;…

【网络安全】-ssrf服务器请求伪造攻击-burp

SSRF攻击服务器请求伪造攻击 CSRF攻击跨站请求伪造攻击也称客户端请求伪造攻击 两种攻击最主要的区别是一个在服务器&#xff0c;一个在客户端。 文章目录 前言 什么是SSRF攻击? 1.分类&#xff1a; 针对服务器的 SSRF 攻击&#xff1a; 针对后端系统的SSRF攻击&#xff1a; …

Kafka高吞吐量的原因

文章目录 生产者&#xff08;写入数据&#xff09;顺序写入Memory Mapped Files 消费者&#xff08;读取数据&#xff09;Kafka是如何巧妙设计的? 总结 众所周知kafka的吞吐量比一般的消息队列要高&#xff0c;号称the fastest&#xff0c;那他是如何做到的&#xff0c;让我们…

Java多线程-(线程的创建,线程安全,线程状态)

第一章.创建线程的方式 1.第一种方式_extends Thread 1.定义一个自定义线程类继承Thread 2.重写run方法(run方法是用于设置线程任务的) 3.创建自定义线程类对象 4.调用Thread类中的start方法(start方法:开启线程,jvm自动执行run方法) public class MyThread extends Thread{…

【SSRF漏洞】——gopherus工具伪造

改变的确很难&#xff0c;但结果值得冒险 本文如有错误之处&#xff0c;还请各位师傅指正 目录 一.gopherus概述 二.gopherus安装使用 三.gopherus覆盖的服务 四.使用案例 web359&#xff1a; web360&#xff1a; 一.gopherus概述 Gopherus是一个专为生成Gopher协议Payloa…

Leetcode 每日一题:Count Complete Tree Nodes

写在前面&#xff1a; 今天带来一道 Leetcde Easy 的题&#xff0c;但别觉得我在水帖&#xff0c;这道题目在 Google 的面试题中甚至可以升级到 Leetcode medium to hard 的级别&#xff0c;而今天我要带来的正是他的高阶要求&#xff0c;怎么样利用 Complete Binary Tree 的特…

经典负载调制平衡放大器(LMBA)设计-从理论到ADS仿真

经典负载调制平衡放大器&#xff08;LMBA&#xff09;设计-从理论到ADS仿真 ADS工程下载&#xff1a;经典负载调制平衡放大器&#xff08;LMBA&#xff09;设计-从理论到ADS仿真-ADS工程 参考论文: An Efficient Broadband Reconfigurable Power Amplifier Using Active Load…

华为 HCIP 认证费用和报名资格

在当今竞争激烈的信息技术领域&#xff0c;华为 HCIP认证备受关注。它不仅能提升个人的技术实力与职业竞争力&#xff0c;也为企业选拔优秀人才提供了重要依据。以下将详细介绍华为 HCIP 认证的费用和报名资格。 一、HCIP 认证费用 华为HCIP认证的费用主要由考试费和培训费构成…

似然函数与先验概率、后验概率的关系

似然函数、先验概率、后验概率这三个概念是贝叶斯统计中的核心概念&#xff0c;它们共同描述了如何根据已有数据更新我们对某个事件或参数的认识。下面用简单的语言解释这三个概念&#xff0c;并描述它们之间的关系。 1. 先验概率&#xff08;Prior Probability&#xff09; …

Debian11.9镜像基于jre1.8的Dockerfile

Debian11.9基于jre1.8的Dockerfile编写 # 使用Debian 11.9作为基础镜像 FROM debian:11.9 # 维护者信息&#xff08;建议使用LABEL而不是MAINTAINER&#xff0c;因为MAINTAINER已被弃用&#xff09; LABEL maintainer"caibingsen" # 创建一个目录来存放jre …

vue中v-bind和v-model的区别和应用

1.区别 v-bind&#xff1a; vue2中&#xff0c;v-bind是单向数据绑定&#xff0c;用于动态绑定HTML属性和组件属性&#xff0c;只能将vue实例中的数据同步到HTML元素上&#xff0c;实现数据的动态更新和响应式渲染。v-bind的简写形式使用冒号前缀&#xff08;&#xff1a;&am…

VSCode好用的插件推荐

1. Chinese 将vscode翻译成简体中文 2. ESLint 自动检查规范 3. Prettier - Code formatter 可以自动调整代码的缩进、换行和空格&#xff0c;确保代码风格统一。通过配置&#xff0c;Prettier可以在保存文件时自动格式化代码 https://juejin.cn/post/74025724757198274…

【时间盒子】-【7.标题菜单栏】自定义任务页面顶部的标题菜单栏组件

Tips&#xff1a; media媒体资源的使用&#xff1b; float.json、color.json资源文件的使用&#xff1b; 组件属性的定义。 预览效果&#xff1a; 一、创建组件文件 右击component目录 >> 新建 >> ArkTS File&#xff0c;文件命名为TitleContainer.ets。 Prev…

JZ2440开发板——S3C2440的时钟体系

参考博客 &#xff08;1&#xff09;S3C2440-裸机篇-05 | S3C2440时钟体系详解&#xff08;FCLK、PCLK、HCLK&#xff09; 一、三种时钟&#xff08;FCLK、HCLK、PCLK&#xff09; 如下图所示&#xff0c;S3C2440的时钟控制逻辑&#xff0c;给整个芯片提供三种时钟&#xff1…

通过防火墙分段增强网络安全

什么是网络分段‌ 随着组织规模的扩大&#xff0c;管理一个不断扩大的网络成为一件棘手的事情&#xff0c;同时确保安全性、合规性、性能和不间断的运行可能是一项艰巨的任务。为了克服这一挑战&#xff0c;网络管理员部署了网络分段&#xff0c;这是一种将网络划分为更小且易…

QT::QComboBox自定义左击事件信号

因为QComboBox没有自定义的clink信号&#xff0c;所以自己新建一个MyComBox类继承QComboBox&#xff0c;并且添加自定义的左击信号&#xff0c;以及使用该信号连接一个槽函数 mycombobox.h #ifndef MYCOMBOBOX_H #define MYCOMBOBOX_H#include <QComboBox> #include &l…

使用程序集解析的方式内嵌dll到exe中

选择一个项目&#xff08;demo3&#xff09;&#xff0c;来进行内嵌。正常dll文件是可以在Bin–Debug里面看到的。 在Program里面添加内容 Program.cs里的全部代码 using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using Syste…

【面试八股总结】Redis持久化

Redis 实现了数据持久化的机制&#xff0c;这个机制会把数据存储到磁盘&#xff0c;这样在 Redis 重启就能够从磁盘中恢复原有的数据。 Redis 共有三种数据持久化的⽅式&#xff1a; AOF 日志&#xff1a;每执行一条写操作命令&#xff0c;就把该命令以追加的方式写入到⼀个文…

7.5图像缩放

实验原理 在OpenCV&#xff08;Open Source Computer Vision Library&#xff09;中&#xff0c;resize函数用于调整图像的尺寸。这个函数非常有用&#xff0c;尤其是在进行图像预处理时&#xff0c;比如在图像识别或机器学习任务中需要统一输入图像的大小。 下面是基于C的re…