写一个代码可以接收rtsp流
那么,什么是rtsp流?
乍一看都是p话。首先了解一下各种知识,rtsp流,h264,一大堆
先看看rtsp是什么吧
流媒体协议RTSP初篇(一)
看着看着会问,rtp和rtcp是什么
RTSP+RTP协议浅析
协议篇—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。
要么用vlc推流做rtsp server,要么直接用外设做rtsp server
简单拿一台tp或者海康的摄像头来预览做实验看看。把摄像头接入局域网,使用vlc去拉个子码流,抓包
摄像头接入,先搜搜它的ip
扫描网络设备
使用arp-scan工具来扫描
sudo arp-scan -I eth0 -l
其中eth0改为当前网卡
然后就是SETUP啊,PLAY啊。play后就是rtp狂发视频数据
关闭VLC时,可见TEARDOWN
VLC搭建RTSP服务器的过程
下面这篇可以正常推流桌面,就是比较卡
设置VLC播放器进行RTSP推流桌面(共享桌面)
OK,搞好了之后,会发现接收到的流是H264/H265的,现在需要把它解码出来播放
ffmpeg使用教程
[总结]FFMPEG视音频编解码零基础学习方法
看着看着又会问,这H264里面怎么又搞个NALU啥啥的
H264相关知识
NALU简介
github一顿搜,搜到好几个看起来不错的。myrtspclient、ZLMediaKit等等都不错
clone下来编了看看吧
现在开始用VSCode开发,又来了一堆问题
Ubuntu下VSCode开发涉及的知识
试了一下,参考这个开源库比较好,myrtspclient
myrtspclient github
myrtspclient中文教程
按照它的demo,只要做好了推流服务器,可以从代码里直接拉出rtsp流,存成H264,可以用ffmpeg直接播放
这个开源库使用了很多东西,包括JRTPLIB、JTHREAD、base64、md5,看起来主要是在这些个基础上,自己实现了rtsp client
目前只实现了吧接收到的H264写到硬盘,没有实现解码播放
那我们采用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
我的开源视音频项目汇总
装一个6.0
最新版本ffmpeg6.0源码安装+vscode开发环境搭建!
ffmpeg官网
这里的安装不是指装个可执行程序,是要从源码中编出静态库和头文件,以便后续代码使用。
首先源码下下来,要开VPN才能下
FFmpeg github
先git clone下来,然后安装要看教程
Ubuntu下FFmpeg的安装(支持libfdk_acc)
编译 ffmpeg 方法
主要就是先config设置一下,然后make安装,再install导到usr/local/lib这些里面去
./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)
这两个东西不能用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
这篇只是教你怎么找问题,并未解决
ERROR: x265 not found using pkg-config的解决方法
这篇有x265的安装法
Ubuntu安装X265+FFMPEG
ccmake:未找到指令
编译x265 ccmake: command not found
编译时出现: 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动态库就没这问题了。
./ffmpeg: error while loading shared libraries: libx264.so.138: cannot open shared object file: No s
它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和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