流媒体编程记录-RTSP

一、需求

写一个代码可以接收rtsp流

二、怎么搞

那么,什么是rtsp流?
流媒体编程记录-RTSP_第1张图片乍一看都是p话。首先了解一下各种知识,rtsp流,h264,一大堆

RTSP

先看看rtsp是什么吧
流媒体协议RTSP初篇(一)
看着看着会问,rtp和rtcp是什么
RTSP+RTP协议浅析
流媒体编程记录-RTSP_第2张图片

协议篇—RTP & RTCP & RTSP
流媒体协议RTSP末篇之RTP(三)
看着看着会问,http也能传东西,和http有啥区别?
要钱才能看 【视频】视频传输协议:RTSP、RTP、RTCP、RTMP、HTTP
看着看着会问,sdp是什么
流媒体协议RTSP中篇之SDP(二)

大概看到这里,知道了rtsp是用来控制的,rtp是用来发数据的。
rtsp的几个重要步骤
OPTIONS
DESCRIBE
SETUP
PLAY
PAUSE
TEARDOWN

准备做实验看看时,发现rtsp收流要有一个URL
一般就是 rtsp://IP:端口/一些特殊东西
官方海康RTSP取流URL格式
流媒体编程记录-RTSP_第3张图片

有密码咋办
流媒体编程记录-RTSP_第4张图片

流媒体编程记录-RTSP_第5张图片

摄像头实验

众所周知, 摄像头出流是用的rtsp。
要么用vlc推流做rtsp server,要么直接用外设做rtsp server
简单拿一台tp或者海康的摄像头来预览做实验看看。把摄像头接入局域网,使用vlc去拉个子码流,抓包
摄像头接入,先搜搜它的ip

扫描网络设备
使用arp-scan工具来扫描
sudo arp-scan -I eth0 -l
其中eth0改为当前网卡

拉流!
流媒体编程记录-RTSP_第6张图片开始播放,可见OPTIONS和DESCRIBE

流媒体编程记录-RTSP_第7张图片然后就是SETUP啊,PLAY啊。play后就是rtp狂发视频数据
流媒体编程记录-RTSP_第8张图片关闭VLC时,可见TEARDOWN
流媒体编程记录-RTSP_第9张图片

VLC做推流

VLC搭建RTSP服务器的过程
下面这篇可以正常推流桌面,就是比较卡
设置VLC播放器进行RTSP推流桌面(共享桌面)

OK,搞好了之后,会发现接收到的流是H264/H265的,现在需要把它解码出来播放
ffmpeg使用教程
[总结]FFMPEG视音频编解码零基础学习方法

H264

看着看着又会问,这H264里面怎么又搞个NALU啥啥的
H264相关知识
NALU简介

三、搞起

github一顿搜,搜到好几个看起来不错的。myrtspclient、ZLMediaKit等等都不错

clone下来编了看看吧
现在开始用VSCode开发,又来了一堆问题
Ubuntu下VSCode开发涉及的知识

myrtspclient

试了一下,参考这个开源库比较好,myrtspclient
myrtspclient github
myrtspclient中文教程
按照它的demo,只要做好了推流服务器,可以从代码里直接拉出rtsp流,存成H264,可以用ffmpeg直接播放

这个开源库使用了很多东西,包括JRTPLIB、JTHREAD、base64、md5,看起来主要是在这些个基础上,自己实现了rtsp client
目前只实现了吧接收到的H264写到硬盘,没有实现解码播放
那我们采用ffmpeg来播放

ffmpeg

顺带搜了一下ffmpeg指令,比较常用如下
使用ffmpeg生成测试视频和图片

直接播放
ffplay video.h264
ffplay拉流
ffplay rtsp://192.168.1.224/stream1
输出视频信息
ffmpeg -i video.mp4
mp4转flv      
ffmpeg -i test.mp4   -f flv output.flv
h264转mp4  
ffmpeg -i test.h264 -f mp4 output.mp4
ffmpeg -i test.mp4 -acodec copy -f mp4   output.mp4 //-acodec copy   音频流执行copy, 视频流会重新解码、编码
ffmpeg -i test.mp4 -vcodec copy -f mp4   output.mp4 //-vcodec copy   视频流执行copy, 音频流会重新解码、编码
ffmpeg -i test.mp4 -codec  copy -f mp4   output.mp4 //-codec  copy   音频流和视频流都不会重新解码、编码
视频转gif
ffmpeg -i test.mp4 out.gif //直接就能转成gif
ffmpeg -i test.mp4 -ss 0 -t 2 -r 10 out.gif //截取视频中前两秒的视频转为gif,并将帧率改为10fps -r修改帧率

使用ffplay播放pcm格式音频
ffplay -ar 44100 -ac 2 -f s16le -i test.pcm //必须在播放时把pcm相关参数都写明,不然会播放失败

jpg转yuv:
ffmpeg -i test.jpg -s 640x480 -pix_fmt yuv420p test.yuv

创建一个测试视频源文件 test.mp4:
ffmpeg -f lavfi -i testsrc=duration=5:size=1280x720:rate=30 test.mp4 
上述命令可以生成一个时长为5秒,分辨率为1280x720,帧率为30的测试视频源文件test.mp4。

对test.mp4进行压缩编码,生成输出文件output.mp4:
ffmpeg -i test.mp4 -c:v libx264 -crf 23 -preset fast -c:a copy output.mp4
上述命令使用H.264编码器对test.mp4进行压缩编码,输出文件output.mp4。其中-crf 23表示压缩质量因子为23,越小则压缩质量越高;-preset fast表示使用快速压缩模式。

参考雷神的博文
[总结]FFMPEG视音频编解码零基础学习方法
[总结]视音频编解码技术零基础学习方法
这篇有很多demo
我的开源视音频项目汇总

安装ffmpeg是个大坑

装一个6.0
最新版本ffmpeg6.0源码安装+vscode开发环境搭建!
ffmpeg官网
这里的安装不是指装个可执行程序,是要从源码中编出静态库和头文件,以便后续代码使用。
首先源码下下来,要开VPN才能下
FFmpeg github
先git clone下来,然后安装要看教程
Ubuntu下FFmpeg的安装(支持libfdk_acc)
编译 ffmpeg 方法
主要就是先config设置一下,然后make安装,再install导到usr/local/lib这些里面去
流媒体编程记录-RTSP_第10张图片

./configure --prefix=/usr/local/ffmpeg --enable-gpl --enable-nonfree --enable-libfdk-aac --enable-libx264 --enable-libx265 --enable-filter=delogo --enable-debug --disable-optimizations --enable-libspeex --enable-shared --enable-pthreads
sudo make
sudo make install

安装过程会遇到各种各样的问题

缺少依赖库

这篇讲的很好,缺yasm啊,libfdk_acc啊,都能解决
Ubuntu下FFmpeg的安装(支持libfdk_acc)

x264问题

这两个东西不能用sudo apt-get install x264来安装,得手动下包来make装

git clone http://git.videolan.org/git/x264.git
cd x264/
sudo ./configure --enable-shared --enable-static
sudo make
sudo make install

x265问题

这篇只是教你怎么找问题,并未解决
ERROR: x265 not found using pkg-config的解决方法
这篇有x265的安装法
Ubuntu安装X265+FFMPEG

ccmake:未找到指令
编译x265 ccmake: command not found

make出现静态库写不进去的问题还有什么fPIC问题

编译时出现: can not be used when making a shared object 和fPIC
ffmpeg编译错误问题,`.rodata’ can not be used when making a shared object; recompile with -fPIC
就是一个make时要求用fPIC重编的问题。问题根源在于编ffmpeg设置配置时,使用了–enable-shared,要求编出动态链接库,但默认是不开的。这时候由于fdk-aac没有编出动态库,导致问题。不编so动态库就没这问题了。

  • 已解决 https://blog.csdn.net/aaddggddaa/article/details/131893193

打不开ffmpeg --version

./ffmpeg: error while loading shared libraries: libx264.so.138: cannot open shared object file: No s


四、代码

用myrtspclient的demo实现

它myrtspclient自己的demo有makefile,但是我们要用他们的库的话,自己写g++编译指令,需要用到它的静态库
【gcc/g++】2.实现三中不同编译方式(直接编译库 静态链接库 动态链接库)
g++ 编译器创建静态和动态链接库
然后我们还在它本身基础上,提出rtsp拉流,自己又编成库
以下代码调用时给出rtsp地址就可以收帧并存mp4。由于不涉及显示,所以不需要ffmpeg和SDL2
这个不如ffmpeg那么方便,主要为学习rtsp

#include 
#include "rtspClient.h"
#include "pull_stream_api.h"
#include 
#include 
#include 
#include 
#include 

using std::cout;
using std::endl;

bool ByeFromServerFlag = false;
void ByeFromServerClbk()
{
	cout << "Server send BYE" << endl;
	ByeFromServerFlag = true;
}

int PullStreamByRtspSaveFile(string input_uri, void (*RecvFrameCb)(uint8_t*, size_t), string save_filename)
{
	// float scale = -1;
	// float start_time = 70.5;
	// float end_time = 50.5;
	string rtsp_uri(input_uri);

	RtspClient Client(rtsp_uri);

	// TODO 未实现账号密码相关
	/* Set rtsp access username */
	// Client.SetUsername("Ansersion");

	/* Set rtsp access password */
	// Client.SetPassword("AnsersionPassword");

	// OPTIONS
	if(Client.DoOPTIONS() != RTSP_NO_ERROR)
	{
		printf("DoOPTIONS error\n");
		return 0;
	}
	printf("%s\n", Client.GetResponse().c_str());
	if(!Client.IsResponse_200_OK())
	{
		printf("DoOPTIONS error\n");
		return 0;
	}

	// DESCRIBE
	if(Client.DoDESCRIBE() != RTSP_NO_ERROR)
	{
		printf("DoDESCRIBE error\n");
		return 0;
	}
	printf("%s\n", Client.GetResponse().c_str());
	if(!Client.IsResponse_200_OK())
	{
		printf("DoDESCRIBE error\n");
		return 0;
	}

	// Parse SDP message after sending DESCRIBE command
	printf("%s\n", Client.GetSDP().c_str());
	if(Client.ParseSDP() != RTSP_NO_ERROR)
	{
		printf("ParseSDP error\n");
		return 0;
	}

	// Send SETUP command to set up all 'audio' and 'video' sessions which SDP refers.
	if(Client.DoSETUP() != RTSP_NO_ERROR)
	{
		printf("DoSETUP error\n");
		return 0;
	}
	Client.SetVideoByeFromServerClbk(ByeFromServerClbk);
	printf("\n%s\n", Client.GetResponse().c_str());
	if(!Client.IsResponse_200_OK())
	{
		printf("DoSETUP error\n");
		return 0;
	}

	printf("start PLAY\n");
	/* Send PLAY command to play only 'video' sessions.
	 * note(FIXME): 
	 * if there are several 'video' session 
	 * refered in SDP, only play the first 'video' 
	 * session, the same as 'audio'.*/
	if(Client.DoPLAY("video", NULL, NULL, NULL) != RTSP_NO_ERROR)
	{
		printf("DoPLAY error\n");
		return 0;
	}
	printf("%s\n", Client.GetResponse().c_str());
	if(!Client.IsResponse_200_OK())
	{
		printf("DoPLAY error\n");
		return 0;
	}

	/* Receive 1000 RTP 'video' packets
	 * note(FIXME): 
	 * if there are several 'video' session 
	 * refered in SDP, only receive packets of the first 
	 * 'video' session, the same as 'audio'.*/
	// int packet_num = 0;
	int try_times = 0;
	const size_t BufSize = 98304;
	uint8_t buf[BufSize];
	size_t size = 0;
    size_t write_size = 0;

	// int fd = open("test_packet_recv.h264", O_CREAT | O_RDWR, 0);
	if (save_filename.size() == 0)
		save_filename.assign("test_packet_recv.h264");
	int fd = open(save_filename.c_str(), O_CREAT | O_WRONLY | O_TRUNC, S_IRUSR | S_IWUSR | S_IXUSR);

	while(true)
	{
		if(!Client.GetMediaData("video", buf + write_size, &size, BufSize))
		{
            if(ByeFromServerFlag)
			{
				printf("ByeFromServerFlag\n");
                break;
            }
            if(try_times > 5)
			{
				printf("try_times > 5\n");
				break;
			}
			try_times++;
			continue;
		}
        write_size += size;

        // 减少写入次数以提高性能
        if (write_size > 32768)
		{
            if (write(fd, buf, write_size) < 0)
			{
                perror("write");
            }
			// 如果定义有收帧回调,则进入
			if (RecvFrameCb != NULL)
			{
				RecvFrameCb(buf, write_size);
			}
            write_size = 0;
        }

		try_times = 0;
		printf("recv %lu\n", size);
	}
    if(write_size > 0)
	{
        if(write(fd, buf, write_size) < 0)
		{
            perror("write");
        }
    }
	close(fd);

	// 发送TEARDOWN
	printf("start TEARDOWN\n");
    int err = Client.DoTEARDOWN();
	if(err != RTSP_NO_ERROR && err != RTSP_INVALID_MEDIA_SESSION)
	{
		printf("DoTEARDOWN error: %d\n", err);
		return 0;
	}
	printf("%s\n", Client.GetResponse().c_str());
	if(!Client.IsResponse_200_OK()) {
		printf("DoTEARDOWN error\n");
		return 0;
	}

	return 0;
}

用ffmpeg实现

直接用ffmpeg实现就优美简单多了
需要正确安装ffmpeg和SDL2,以及ffmpeg的一系列依赖库(fdk_aac、x264、x265等)
ffmpeg可以包办rtsp收流,SDL2用来绘制显示
如下代码是ChatGpt写的,可以直接拉摄像头的rtsp流

#include 
extern "C" {
#include 
#include 
#include 
#include 
#include 
}
#include 

#define VIDEO_WIDTH 640
#define VIDEO_HEIGHT 480

int main()
{
    avformat_network_init();

    // 初始化 AVFormatContext
    AVFormatContext* formatContext = avformat_alloc_context();

    // 打开 RTSP 流
    const char* rtspUrl = "rtsp://192.168.1.224/stream1"; // RTSP URL
    if (avformat_open_input(&formatContext, rtspUrl, nullptr, nullptr) != 0)
    {
        std::cout << "无法打开 RTSP 流" << std::endl;
        return -1;
    }

    // 获取视频流信息
    if (avformat_find_stream_info(formatContext, nullptr) < 0)
    {
        std::cout << "无法获取视频流信息" << std::endl;
        return -1;
    }

    int videoStreamIndex = -1;
    AVCodecParameters* codecParams = nullptr;
    const AVCodec* codec = nullptr; //不清楚为什么下面还能赋值,不声明const又报错,直接赋值时声明无法判空
    AVCodecContext* codecContext = nullptr;

    // 寻找视频流索引
    for (unsigned int i = 0; i < formatContext->nb_streams; i++)
    {
        if (formatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
        {
            videoStreamIndex = i;
            codecParams = formatContext->streams[i]->codecpar;
            break;
        }
    }

    if (videoStreamIndex == -1 || codecParams == nullptr)
    {
        std::cout << "未找到视频流" << std::endl;
        return -1;
    }

    // 寻找解码器
    codec = avcodec_find_decoder(codecParams->codec_id);
    if (!codec)
    {
        std::cout << "未找到解码器" << std::endl;
        return -1;
    }

    // 初始化解码器上下文
    codecContext = avcodec_alloc_context3(codec);
    if (avcodec_parameters_to_context(codecContext, codecParams) < 0)
    {
        std::cout << "初始化解码器上下文失败" << std::endl;
        return -1;
    }

    // 打开解码器
    if (avcodec_open2(codecContext, codec, nullptr) < 0)
    {
        std::cout << "无法打开解码器" << std::endl;
        return -1;
    }

    // 创建视频帧和数据包
    AVFrame* frame = av_frame_alloc();
    AVPacket* packet = av_packet_alloc();

    // 初始化视频像素格式转换
    SwsContext* swsContext = sws_getContext(codecContext->width, codecContext->height, codecContext->pix_fmt,
                                            VIDEO_WIDTH, VIDEO_HEIGHT, AV_PIX_FMT_RGB24,
                                            SWS_BILINEAR, nullptr, nullptr, nullptr);

    // 初始化图像数据缓冲区
    int numBytes = av_image_get_buffer_size(AV_PIX_FMT_RGB24, VIDEO_WIDTH, VIDEO_HEIGHT, 1);
    uint8_t* buffer = (uint8_t*)av_malloc(numBytes);
    AVFrame* rgbFrame = av_frame_alloc();
    av_image_fill_arrays(rgbFrame->data, rgbFrame->linesize, buffer, AV_PIX_FMT_RGB24, VIDEO_WIDTH, VIDEO_HEIGHT, 1);

    // 创建窗口显示视频(此处使用SDL库,需要链接SDL库)
    SDL_Init(SDL_INIT_VIDEO);
    SDL_Window* window = SDL_CreateWindow("RTSP Video Player", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, VIDEO_WIDTH, VIDEO_HEIGHT, 0);
    SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, 0);
    SDL_Texture* texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGB24, SDL_TEXTUREACCESS_STREAMING, VIDEO_WIDTH, VIDEO_HEIGHT);

    // 播放视频
    while (av_read_frame(formatContext, packet) >= 0)
    {
        if (packet->stream_index == videoStreamIndex)
        {
            avcodec_send_packet(codecContext, packet);

            while (avcodec_receive_frame(codecContext, frame) == 0)
            {
                sws_scale(swsContext, frame->data, frame->linesize, 0, codecContext->height, rgbFrame->data, rgbFrame->linesize);
                SDL_UpdateTexture(texture, nullptr, rgbFrame->data[0], rgbFrame->linesize[0]);
                SDL_RenderClear(renderer);
                SDL_RenderCopy(renderer, texture, nullptr, nullptr);
                SDL_RenderPresent(renderer);

                SDL_Event event;
                SDL_PollEvent(&event);
                if (event.type == SDL_QUIT)
                    break;
            }
        }

        av_packet_unref(packet);
    }

    // 释放资源
    av_frame_free(&frame);
    av_packet_free(&packet);
    av_frame_free(&rgbFrame);
    av_free(buffer);
    sws_freeContext(swsContext);
    avcodec_close(codecContext);
    avformat_close_input(&formatContext);

    // 关闭SDL
    SDL_DestroyTexture(texture);
    SDL_DestroyRenderer(renderer);
    SDL_DestroyWindow(window);
    SDL_Quit();

    return 0;
}

编译指令为

g++ -o ffmpeg_play_rtsp_stream ffmpeg_play_rtsp_stream.cpp -I /usr/local/ffmpeg/include -L /usr/local/ffmpeg/lib -lavformat -lavdevice -lavcodec -lavfilter -lavutil -lswresample -lswscale -lSDL2

你可能感兴趣的:(网络)