草庐IT

ffmpeg推流摄像头数据至公网服务器

Prejudices 2023-05-30 原文

完整的推流代码已经托管到个人的Gitee,如有需要请自取

https://gitee.com/MonsterAKALei/push_video.git


ffmpeg推流摄像头数据

昨天实现用API分别实现了读取摄像头数据并保存将本地文件推流到公网两个功能,所以想着是否可以将这两个功能合并一下,读取摄像头数据后不保存而直接推流到公网

FFmpeg采集摄像头图像并推流(RTSP/RTMP)—开发总结

我的一篇博文《如何用FFmpeg API采集摄像头视频和麦克风音频。。。》已经介绍了如何从视音频采集设备获取数据,并且编码、保存文件到本地。但是,**有些应用并不是把流保存成文件,而是需要发送到网络**的,比如现在很典型的一种应用场景:把流推送到RTSP、RTMP、HLS服务器,由服务器转发给其他用户观看。很多开发者也是调用FFmpeg API来实现推流的,用FFmpeg 做一个推流器很简单,调用流程跟输出文件的基本相同,基于前面博文的例子稍微修改就可以做出一个采集+编码+推流的软件。这里,我先假设读者已经会用FFmpeg API保存或录制文件,但没有实现过推流功能,我将给大家说一下做推流跟录制文件的区别,还有说一下要注意的几个问题,希望能帮助大家在开发推流功能时减少一些问题的出现

上面这个博客里提到了我目前的需求,如红字突出部分,但是其内容讲的是如何将已有文件推流到公网,还是有区别的

注意到对于RTMPAVOutputFormatflv,这对后面的操作有很大的影响

我先将昨天的两个cpp文件做了简单的拼接

transmit_test.cpp

#include <iostream>
#include <string>

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是两种经常使用的像素格式
  • RGB:较为熟悉,具有3个通道R G B,分别对应红 绿 蓝三个分量,由三个分量的值决定颜色;通常,会给RGB图像加一个通道alpha,即透明度,于是共有四个分量共同控制颜色(常用的opencv库默认将图片以BGR的方式进行存储,只是通道顺序不一样而已)
  • YUV:(YCrCb)是指将亮度参量Y和色度参量U/V分开表示的像素格式,主要用于优化彩色视频信号的传输
  • YUV像素格式来源于RGB像素格式,通过公式运算,YUV三分量可以还原出RGB
  • FLV(Flash Video)是Adobe公司推出的一种流媒体格式,由于其封装后的音视频文件体积小、封装简单等特点,非常适合于互联网上使用。目前主流的视频网站基本都支持FLV。采用FLV格式封装的文件后缀为.flv

所以报错就很正常了,一种是像素格式一种是流媒体格式,怎么可能读出来就直接推流呢

查看ffmpeg支持的所有视频或音频文件类型

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步

  1. 获取摄像头数据,转换为OpenCV图像格式(BGR/BGRA)
  2. 通过ffmpeg编码器将OpenCV图像格式编码为H264格式
  3. 将H264格式封装为FLV格式进行RTMP推流

其实弄清了分为几个步骤,分别去查相应的解决方法,逐个击破即可

第一步最简单,几乎不用什么新知识,几个参考博客

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

有关ffmpeg推流摄像头数据至公网服务器的更多相关文章

  1. ruby - 使用 ruby​​ 和 savon 的 SOAP 服务 - 2

    我正在尝试使用ruby​​和Savon来使用网络服务。测试服务为http://www.webservicex.net/WS/WSDetails.aspx?WSID=9&CATID=2require'rubygems'require'savon'client=Savon::Client.new"http://www.webservicex.net/stockquote.asmx?WSDL"client.get_quotedo|soap|soap.body={:symbol=>"AAPL"}end返回SOAP异常。检查soap信封,在我看来soap请求没有正确的命名空间。任何人都可以建议我

  2. ruby - 具有身份验证的私有(private) Ruby Gem 服务器 - 2

    我想安装一个带有一些身份验证的私有(private)Rubygem服务器。我希望能够使用公共(public)Ubuntu服务器托管内部gem。我读到了http://docs.rubygems.org/read/chapter/18.但是那个没有身份验证-如我所见。然后我读到了https://github.com/cwninja/geminabox.但是当我使用基本身份验证(他们在他们的Wiki中有)时,它会提示从我的服务器获取源。所以。如何制作带有身份验证的私有(private)Rubygem服务器?这是不可能的吗?谢谢。编辑:Geminabox问题。我尝试“捆绑”以安装新的gem..

  3. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  4. ruby-on-rails - 启动 Rails 服务器时 ImageMagick 的警告 - 2

    最近,当我启动我的Rails服务器时,我收到了一长串警告。虽然它不影响我的应用程序,但我想知道如何解决这些警告。我的估计是imagemagick以某种方式被调用了两次?当我在警告前后检查我的git日志时。我想知道如何解决这个问题。-bcrypt-ruby(3.1.2)-better_errors(1.0.1)+bcrypt(3.1.7)+bcrypt-ruby(3.1.5)-bcrypt(>=3.1.3)+better_errors(1.1.0)bcrypt和imagemagick有关系吗?/Users/rbchris/.rbenv/versions/2.0.0-p247/lib/ru

  5. ruby-on-rails - s3_direct_upload 在生产服务器中不工作 - 2

    在Rails4.0.2中,我使用s3_direct_upload和aws-sdkgems直接为s3存储桶上传文件。在开发环境中它工作正常,但在生产环境中它会抛出如下错误,ActionView::Template::Error(noimplicitconversionofnilintoString)在View中,create_cv_url,:id=>"s3_uploader",:key=>"cv_uploads/{unique_id}/${filename}",:key_starts_with=>"cv_uploads/",:callback_param=>"cv[direct_uplo

  6. ruby - Ruby 有 `Pair` 数据类型吗? - 2

    有时我需要处理键/值数据。我不喜欢使用数组,因为它们在大小上没有限制(很容易不小心添加超过2个项目,而且您最终需要稍后验证大小)。此外,0和1的索引变成了魔数(MagicNumber),并且在传达含义方面做得很差(“当我说0时,我的意思是head...”)。散列也不合适,因为可能会不小心添加额外的条目。我写了下面的类来解决这个问题:classPairattr_accessor:head,:taildefinitialize(h,t)@head,@tail=h,tendend它工作得很好并且解决了问题,但我很想知道:Ruby标准库是否已经带有这样一个类? 最佳

  7. ruby - 用 Ruby 编写一个简单的网络服务器 - 2

    我想在Ruby中创建一个用于开发目的的极其简单的Web服务器(不,不想使用现成的解决方案)。代码如下:#!/usr/bin/rubyrequire'socket'server=TCPServer.new('127.0.0.1',8080)whileconnection=server.acceptheaders=[]length=0whileline=connection.getsheaders想法是从命令行运行这个脚本,提供另一个脚本,它将在其标准输入上获取请求,并在其标准输出上返回完整的响应。到目前为止一切顺利,但事实证明这真的很脆弱,因为它在第二个请求上中断并出现错误:/usr/b

  8. ruby-on-rails - 在 Rails 中调试生产服务器 - 2

    您如何在Rails中的实时服务器上进行有效调试,无论是在测试版/生产服务器上?我试过直接在服务器上修改文件,然后重启应用,但是修改好像没有生效,或者需要很长时间(缓存?)我也试过在本地做“脚本/服务器生产”,但是那很慢另一种选择是编码和部署,但效率很低。有人对他们如何有效地做到这一点有任何见解吗? 最佳答案 我会回答你的问题,即使我不同意这种热修补服务器代码的方式:)首先,你真的确定你已经重启了服务器吗?您可以通过跟踪日志文件来检查它。您更改的代码显示的View可能会被缓存。缓存页面位于tmp/cache文件夹下。您可以尝试手动删除

  9. ruby - 我如何添加二进制数据来遏制 POST - 2

    我正在尝试使用Curbgem执行以下POST以解析云curl-XPOST\-H"X-Parse-Application-Id:PARSE_APP_ID"\-H"X-Parse-REST-API-Key:PARSE_API_KEY"\-H"Content-Type:image/jpeg"\--data-binary'@myPicture.jpg'\https://api.parse.com/1/files/pic.jpg用这个:curl=Curl::Easy.new("https://api.parse.com/1/files/lion.jpg")curl.multipart_form_

  10. 世界前沿3D开发引擎HOOPS全面讲解——集3D数据读取、3D图形渲染、3D数据发布于一体的全新3D应用开发工具 - 2

    无论您是想搭建桌面端、WEB端或者移动端APP应用,HOOPSPlatform组件都可以为您提供弹性的3D集成架构,同时,由工业领域3D技术专家组成的HOOPS技术团队也能为您提供技术支持服务。如果您的客户期望有一种在多个平台(桌面/WEB/APP,而且某些客户端是“瘦”客户端)快速、方便地将数据接入到3D应用系统的解决方案,并且当访问数据时,在各个平台上的性能和用户体验保持一致,HOOPSPlatform将帮助您完成。利用HOOPSPlatform,您可以开发在任何环境下的3D基础应用架构。HOOPSPlatform可以帮您打造3D创新型产品,HOOPSSDK包含的技术有:快速且准确的CAD

随机推荐