完整的推流代码已经托管到个人的Gitee,如有需要请自取
https://gitee.com/MonsterAKALei/push_video.git
昨天实现用API分别实现了读取摄像头数据并保存
和将本地文件推流到公网
两个功能,所以想着是否可以将这两个功能合并一下,读取摄像头数据后不保存而直接推流到公网
FFmpeg采集摄像头图像并推流(RTSP/RTMP)—开发总结
我的一篇博文《如何用FFmpeg API采集摄像头视频和麦克风音频。。。》已经介绍了如何从视音频采集设备获取数据,并且编码、保存文件到本地。但是,**
有些应用并不是把流保存成文件,而是需要发送到网络
**的,比如现在很典型的一种应用场景:把流推送到RTSP、RTMP、HLS服务器,由服务器转发给其他用户观看。很多开发者也是调用FFmpeg API来实现推流的,用FFmpeg 做一个推流器很简单,调用流程跟输出文件的基本相同,基于前面博文的例子稍微修改就可以做出一个采集+编码+推流的软件。这里,我先假设读者已经会用FFmpeg API保存或录制文件,但没有实现过推流功能,我将给大家说一下做推流跟录制文件的区别,还有说一下要注意的几个问题,希望能帮助大家在开发推流功能时减少一些问题的出现
上面这个博客里提到了我目前的需求,如红字突出部分,但是其内容讲的是如何将已有文件推流到公网
,还是有区别的
注意到对于RTMP
,AVOutputFormat
是flv
,这对后面的操作有很大的影响
我先将昨天的两个cpp文件做了简单的拼接
#include
#include
extern "C"
{
#include "libavformat/avformat.h"
#include "libavutil/time.h"
#include "libavutil/avutil.h"
#include "libavdevice/avdevice.h"
#include "libavcodec/avcodec.h"
}
using namespace std;
#pragma comment(lib, "avformat.lib")
#pragma comment(lib, "avutil.lib")
#pragma comment(lib, "avcodec.lib")
#pragma comment(lib, "avdevic.lib")
int ff_Error(int errNum)
{
char buf[1024] = {0};
av_strerror(errNum, buf, sizeof(buf));
cout << buf << endl;
return -1;
}
static AVFormatContext *open_dev(const string &devicename)
{
int ret = 0;
// ctx
AVFormatContext *ictx = NULL;
AVDictionary *options = NULL;
// register audio device
avdevice_register_all();
// get format
AVInputFormat *iformat = av_find_input_format("video4linux2");
av_dict_set(&options, "video_size", "640x480", 0);
av_dict_set(&options, "framerate", "30", 0);
av_dict_set(&options, "pixel_format", "yuyv422", 0);
// open device
if ((ret = avformat_open_input(&ictx, devicename.data(), iformat, &options)) < 0)
{
ff_Error(ret);
}
else
{
cout << "相机打开成功!" << endl;
}
return ictx;
}
int main(int argc, char *argv[])
{
string outUrl = "rtmp://centos:7788/videotest";
av_register_all();
//初始化网络库
avformat_network_init();
AVFormatContext *ictx = NULL;
int ret = 0;
string devicename = "/dev/video2";
//打开设备
ictx = open_dev(devicename);
ret = avformat_find_stream_info(ictx, 0);
if (ret != 0)
{
return ff_Error(ret);
}
cout << "打印输入流信息:" << endl;
av_dump_format(ictx, 0, devicename.data(), 0);
//输出流
//创建输出流上下文
AVFormatContext *octx = NULL;
ret = avformat_alloc_output_context2(&octx, 0, "flv", outUrl.data());
if (!octx)
{
return ff_Error(ret);
}
cout << "输出上下文创建成功" << endl;
//配置输出流
// 遍历输入的AVStream
cout << "ictx->nb_streams:" << ictx->nb_streams << endl;
for (int i = 0; i < ictx->nb_streams; i++)
{
//创建输出流
AVStream *out = avformat_new_stream(octx, ictx->streams[i]->codec->codec);
if (!out)
{
return ff_Error(0);
}
//复制配置信息
ret = avcodec_parameters_copy(out->codecpar, ictx->streams[i]->codecpar);
out->codec->codec_tag = 0;
}
cout << "打印输出流信息:" << endl;
av_dump_format(octx, 0, outUrl.data(), 1);
// rtmp推流
//打开io
cout << "准备RTMP推流..." << endl;
ret = avio_open(&octx->pb, outUrl.data(), AVIO_FLAG_WRITE);
if (!octx->pb)
{
cout << "准备推流失败!" << endl;
return ff_Error(ret);
}
//写入头信息
ret = avformat_write_header(octx, 0);
if (ret < 0)
{
cout << "写入头信息失败!" << endl;
return ff_Error(ret);
}
// packet
AVPacket pkt;
while (true)
{
cout << "开始RTMP推流..." << endl;
ret = av_read_frame(ictx, &pkt);
if (ret != 0)
{
return ff_Error(ret);
break;
}
//推送帧数据
ret = av_interleaved_write_frame(octx, &pkt);
if (ret < 0)
{
return ff_Error(ret);
}
}
cout << "rtmp 推流结束" << endl;
return 0;
}
运行输出如下信息
redwall@redwall-G3-3500:~/Test/video_transmit/bin$ ./transmit_test
相机打开成功!
打印输入流信息:
Input #0, video4linux2,v4l2, from '/dev/video2':
Duration: N/A, start: 21117.813711, bitrate: 147456 kb/s
Stream #0:0: Video: rawvideo (YUY2 / 0x32595559), yuyv422, 640x480, 147456 kb/s, 30 fps, 30 tbr, 1000k tbn, 1000k tbc
输出上下文创建成功
ictx->nb_streams:1
打印输出流信息:
Output #0, flv, to 'rtmp://centos:7788/videotest':
Stream #0:0: Video: rawvideo (YUY2 / 0x32595559), yuyv422, 640x480, q=2-31, 147456 kb/s
准备RTMP推流...
[flv @ 0x5603fbb236c0] Video codec rawvideo not compatible with flv
写入头信息失败!
Function not implemented
提示Video codec rawvideo not compatible with flv
,也就是视频编解码器rawvideo不兼容flv
,于是我又去查了yuy2
以及flv
是什么
谈谈RGB、YUY2、YUYV、YVYU、UYVY、AYUV
常用视频像素格式NV12、NV21、I420、YV12、YUYV
音视频基础:FLV封装格式介绍及解析
像素数据
存储所用的格式,定义了像素在内存中的编码方式,RGB和YUV是两种经常使用的像素格式所以报错就很正常了,一种是像素格式
一种是流媒体格式
,怎么可能读出来就直接推流呢
ffmpeg所支持的所有视频或音频文件类型
ffmpeg -formats
redwall@redwall-G3-3500:~$ ffmpeg -formats|grep yu
ffmpeg version 3.4.11-0ubuntu0.1 Copyright (c) 2000-2022 the FFmpeg developers
D pgmyuv_pipe piped pgmyuv sequence
DE yuv4mpegpipe YUV4MPEG pipe
redwall@redwall-G3-3500:~$ ffmpeg -formats|grep h264
ffmpeg version 3.4.11-0ubuntu0.1 Copyright (c) 2000-2022 the FFmpeg developers
DE h264 raw H.264 video
redwall@redwall-G3-3500:~$ ffmpeg -formats|grep flv
ffmpeg version 3.4.11-0ubuntu0.1 Copyright (c) 2000-2022 the FFmpeg developers
DE flv FLV (Flash Video)
可以看到yuv4、h264、flv都是不同的编码格式,所以我就想能不能解决codec not compatible
的问题
linux下使用ffmpeg采集摄像头数据并编码成h264文件
上面的博客确实做到了将原始摄像头yuyv422
格式的数据转换为h264
格式的数据并写入文件,但存在两个问题:
1、仅写入文件,但并未实现推流
2、h264
格式的数据适用于RTSP
,并不适用于RTMP
,这一点是从下面的博客发现的
FFmpeg4入门27:捕获摄像头编码h264并推流
我下载并阅读了文中的代码,确实是推流到RTSP
服务
//编码器部分开始/
const char *outFile = "rtsp://192.168.1.31/test"; //输出URL
const char *ofmtName = "rtsp"; //输出格式;
if (avformat_alloc_output_context2(&outFmtCtx, NULL, ofmtName, outFile) < 0)
{
printf("Cannot alloc output file context.\n");
return -1;
}
outFmt = outFmtCtx->oformat;
所以没办法拿来稍微改改就能用,但还是有借鉴意义的,我看有300行代码和好多陌生的API,就没花时间去研究,后面看看有需要还是得研究下
关于ffmpeg的结构体
和API
,可以看网上的一些博客,也可以直接看官方文档,官方的比较简略,学习起来还是有一定的时间成本的
ffmpeg重要函数和结构体整理
ffmpeg官方文档
但是下面的博客又给了我一些新的思路
linux FFMPEG 摄像头采集数据推流
博客中使用ffmpeg命令
进行本地摄像头的推流,而拉流则是通过ffmpeg的API函数编程
实现的
然后我去看陆辉东robot_remote_control中的imagetransfer
代码,发现也是只有拉流的实现
void ImgTrancefer::transImg(){
av_register_all();
avformat_network_init();
iCtx = avformat_alloc_context();
int ret;
ret = avformat_open_input(&iCtx, rtmp_url.data(), NULL, NULL);
if(ret != 0){
emit TransIMGLog(QString("open input faild!"));
qDebug() << "open input faild!";
return;
}
if(avformat_find_stream_info(iCtx, NULL) < 0){
emit TransIMGLog(QString("find stream faild!"));
qDebug() << "find stream faild!";
return;
}
for(int i =0;i < iCtx->nb_streams;i++){
if(iCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO){
videoStream = i;
break;
}
}
后面则是一些解码和转码输出的内容,所以是不是只要有拉流输出
的代码实现就可以呢,这是个问题
中午吃饭的时候问了陆辉东,说是推流也是通过API实现,但在车上
是我自己的疏忽,应该想到推流代码应该在车上才对,还是要多思考
推流的代码在redwallbot-2
中的transfer_img
包中,确实给我提供了一些思路,最重要的是打破了我的认知误区,H264格式也可以进行RTMP推流
产生这样的认知误区主要还是因为自己盲目自信以及过于相信网络博客中的内容,其实随便检索一下
第一条就说明RTMP可以推H264格式,所以要时刻保持怀疑的态度
,对不熟悉的事物要多查多看
陆辉东代码里是订阅摄像头话题,转OpenCV图像格式,然后再编码为H264,最后封装为FLV进行RTMP推流,最重要的有3步
其实弄清了分为几个步骤,分别去查相应的解决方法,逐个击破即可
第一步最简单,几乎不用什么新知识,几个参考博客
cv_bridge用于ROS图像和OpenCV图像的转换
第二步也有一些博客,但比较杂,用C++实现的不多,还没有深入研究
OpenCV采集的视频流转化成H264格式裸码流
cv::Mat编码H264
第三步涉及到对H264、FLV等格式的解析,难度较大,代码上也少有较清晰的实现
RTMP 两种方式推流:推H.264、ACC和推FLV封装格式
RTMP推流H.264
H264 推流到RTMP服务器
但是踏破铁鞋无觅处,这样一篇博客从天而降
流媒体解码及H.264编码推流
有一说一真的顶,简直量身定制,我看时间是17年的博客,陆辉东的代码感觉就是在他的基础上改为类实现而已,我再简单优化一下即可
完整的推流代码已经托管到个人的Gitee,如有需要请自取
https://gitee.com/MonsterAKALei/push_video.git