FFmpeg 实现从摄像头获取流并通过RTMP推流

使用FFmpeg库实现从USB摄像头获取流并通过RTMP推流,FFmpeg版本为4.4.2-0。RTMP服务器使用的是SRS,拉流端使用VLC。如果想降低延时,首先需要配置SRS为Realtime模式,拉流的话就不要用VLC了,使用 ffplay 来拉流播放,拉流命令如下:

ffplay -fflags nobuffer -flags low_delay -framedrop -strict experimental rtmp://192.168.3.230/live/livestream

 局域网内用摄像头对着手机秒表实测延时只有零点几秒。

 

在Linux上查看摄像头信息可使用 v4l2-ctl 工具,查看命令如下:

v4l2-ctl --device=/dev/video0 --list-formats-ext

代码如下:

#include <libavdevice/avdevice.h>
#include <libswscale/swscale.h>
#include <libavutil/imgutils.h>int main(void)
{const char *input_format_name = "video4linux2";           // 输入格式名称,Linux下为video4linux2或v4l2const char *device_name = "/dev/video0";                  // 摄像头设备名称const char *camera_resolution = "640x480";                // 摄像头分辨率enum AVPixelFormat camera_pix_fmt = AV_PIX_FMT_YUYV422;   // 摄像头像素格式const char *url = "rtmp://192.168.3.230/live/livestream"; // rtmp地址int frame_rate = 25;                                      // 帧率int ret = -1, retVal = -1;int video_streamid = -1;int64_t frame_index = 0;AVDictionary *options = NULL;AVInputFormat *fmt = NULL;AVFormatContext *in_context = NULL, *out_context = NULL;struct SwsContext *sws_ctx = NULL;AVCodecContext *codec_context = NULL;AVStream *out_stream = NULL;AVCodec *codec = NULL;AVPacket *packet = NULL;// 注册所有设备avdevice_register_all();// 查找输入格式fmt = av_find_input_format(input_format_name);if (!fmt){fprintf(stderr, "av_find_input_format error");return -1;}// 设置分辨率av_dict_set(&options, "video_size", camera_resolution, 0);// 打开输入流并初始化格式上下文retVal = avformat_open_input(&in_context, device_name, fmt, &options);if (retVal != 0){// 打印错误信息fprintf(stderr, "avformat_open_input error");return -1;}av_dict_free(&options);// 查找流信息if (avformat_find_stream_info(in_context, 0) < 0){fprintf(stderr, "avformat_find_stream_info failed\n");return -1;}// 查找视频流索引video_streamid = av_find_best_stream(in_context, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);if (video_streamid < 0){fprintf(stderr, "cannot find video stream");goto end;}AVStream *video_stream = in_context->streams[video_streamid];printf("input stream, width: %d, height: %d, format: %s\n",video_stream->codecpar->width, video_stream->codecpar->height,av_get_pix_fmt_name((enum AVPixelFormat)video_stream->codecpar->format));// 检查实际获取到的格式是否为设置的摄像头像素格式if (video_stream->codecpar->format != camera_pix_fmt){fprintf(stderr, "pixel format error");goto end;}// 初始化转换上下文sws_ctx = sws_getContext(video_stream->codecpar->width, video_stream->codecpar->height, camera_pix_fmt,video_stream->codecpar->width, video_stream->codecpar->height, AV_PIX_FMT_YUV420P,SWS_BILINEAR, NULL, NULL, NULL);if (!sws_ctx){fprintf(stderr, "sws_getContext error\n");goto end;}// 创建帧AVFrame *input_frame = av_frame_alloc();AVFrame *frame_yuv420p = av_frame_alloc();if (!input_frame || !frame_yuv420p){fprintf(stderr, "av_frame_alloc error\n");goto end;}// 设置帧格式input_frame->format = camera_pix_fmt;input_frame->width = video_stream->codecpar->width;input_frame->height = video_stream->codecpar->height;frame_yuv420p->format = AV_PIX_FMT_YUV420P;frame_yuv420p->width = video_stream->codecpar->width;frame_yuv420p->height = video_stream->codecpar->height;// 分配帧内存retVal = av_frame_get_buffer(frame_yuv420p, 0);if (retVal < 0){fprintf(stderr, "av_frame_get_buffer error\n");goto end;}// 分配输出格式上下文avformat_alloc_output_context2(&out_context, NULL, "flv", NULL);if (!out_context){fprintf(stderr, "avformat_alloc_output_context2 failed\n");goto end;}// 查找编码器codec = avcodec_find_encoder(AV_CODEC_ID_H264);if (!codec){fprintf(stderr, "Codec not found\n");goto end;}printf("codec name: %s\n", codec->name);// 创建新的视频流out_stream = avformat_new_stream(out_context, NULL);if (!out_stream){fprintf(stderr, "avformat_new_stream failed\n");goto end;}// 分配编码器上下文codec_context = avcodec_alloc_context3(codec);if (!codec_context){fprintf(stderr, "avcodec_alloc_context3 failed\n");goto end;}// 设置编码器参数codec_context->codec_id = AV_CODEC_ID_H264;codec_context->codec_type = AVMEDIA_TYPE_VIDEO;codec_context->pix_fmt = AV_PIX_FMT_YUV420P;codec_context->width = frame_yuv420p->width;codec_context->height = frame_yuv420p->height;codec_context->time_base = (AVRational){1, frame_rate};         // 设置时间基codec_context->framerate = (AVRational){frame_rate, 1};         // 设置帧率codec_context->bit_rate = 750 * 1000;                           // 设置比特率codec_context->gop_size = frame_rate;                           // 设置GOP大小codec_context->max_b_frames = 0;                                // 设置最大B帧数,不需要B帧时设置为0av_opt_set(codec_context->priv_data, "profile", "baseline", 0); // 设置h264画质级别av_opt_set(codec_context->priv_data, "tune", "zerolatency", 0); // 设置h264编码优化参数// 检测输出上下文的封装格式,判断是否设置 AV_CODEC_FLAG_GLOBAL_HEADER// AV_CODEC_FLAG_GLOBAL_HEADER:由原来编码时在每个关键帧前加入pps和sps,改变为在extradate这个字节区加入pps和spsif (out_context->oformat->flags & AVFMT_GLOBALHEADER){printf("set AV_CODEC_FLAG_GLOBAL_HEADER\n");codec_context->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;}// 打开编码器if (avcodec_open2(codec_context, codec, NULL) < 0){fprintf(stderr, "avcodec_open2 failed\n");goto end;}// 将编码器参数复制到流int result = avcodec_parameters_from_context(out_stream->codecpar, codec_context);if (result < 0){fprintf(stderr, "avcodec_parameters_from_context failed\n");goto end;}// 打开urlif (!(out_context->oformat->flags & AVFMT_NOFILE)){result = avio_open(&out_context->pb, url, AVIO_FLAG_WRITE);if (result < 0){fprintf(stderr, "avio_open error (errmsg '%s')\n", av_err2str(result));goto end;}}// 写文件头result = avformat_write_header(out_context, NULL);if (result < 0){fprintf(stderr, "avformat_write_header failed\n");goto end;}packet = av_packet_alloc();if (!packet){fprintf(stderr, "av_packet_alloc failed\n");goto end;}// 读取帧并进行转换AVPacket pkt;while (av_read_frame(in_context, &pkt) >= 0){if (pkt.stream_index == video_streamid){// 把读取的帧数据(AVPacket)拷贝到输入帧(AVFrame)中retVal = av_image_fill_arrays(input_frame->data, input_frame->linesize, pkt.data, camera_pix_fmt,video_stream->codecpar->width, video_stream->codecpar->height, 1);if (retVal < 0){av_packet_unref(&pkt);fprintf(stderr, "av_image_fill_arrays error\n");break;}// 转换为 YUV420Psws_scale(sws_ctx, (const uint8_t *const *)input_frame->data, input_frame->linesize, 0,input_frame->height, frame_yuv420p->data, frame_yuv420p->linesize);frame_yuv420p->pts = frame_index;frame_index++;// 发送帧到编码器result = avcodec_send_frame(codec_context, frame_yuv420p);if (result < 0){fprintf(stderr, "avcodec_send_frame error (errmsg '%s')\n", av_err2str(result));break;}// 接收编码后的数据包while (result >= 0){result = avcodec_receive_packet(codec_context, packet);if (result == AVERROR(EAGAIN) || result == AVERROR_EOF){break;}else if (result < 0){fprintf(stderr, "avcodec_receive_packet error (errmsg '%s')\n", av_err2str(result));goto end;}packet->stream_index = out_stream->index;// 将时间戳从编码器时间基转换到流时间基av_packet_rescale_ts(packet, codec_context->time_base, out_stream->time_base);packet->pos = -1;// 推送到RTMP服务器result = av_interleaved_write_frame(out_context, packet);if (result < 0){fprintf(stderr, "av_interleaved_write_frame error (errmsg '%d')\n", result);av_packet_unref(packet);goto end;}av_packet_unref(packet);}}av_packet_unref(&pkt);}end:// 释放资源if (input_frame)av_frame_free(&input_frame);if (frame_yuv420p)av_frame_free(&frame_yuv420p);if (sws_ctx)sws_freeContext(sws_ctx);if (in_context)avformat_close_input(&in_context);if (packet)av_packet_free(&packet);if (codec_context)avcodec_free_context(&codec_context);if (out_context)avformat_free_context(out_context);return ret;
}

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

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

相关文章

Python28-9 XGBoost算法

XGBoost&#xff08;eXtreme Gradient Boosting&#xff0c;其正确拼写应该是 "Extreme Gradient Boosting"&#xff0c;而XGBoost 的作者在命名时故意使用了不规范的拼写&#xff0c;将“eXtreme”中的“X”大写&#xff0c;以突出其极限性能和效率&#xff09;是一…

概论(二)随机变量

1.名词解释 1.1 样本空间 一次具体实验中所有可能出现的结果&#xff0c;构成一个样本空间。 1.2 随机变量 把结果抽象成数值&#xff0c;结果和数值的对应关系就形成了随机变量X。例如把抛一次硬币的结果&#xff0c;正面记为1&#xff0c;反面记为0。有变量相对应的就有自…

机器学习-利用 scikit-learn 使用梯度下降实现线性回归

线性回归是一种基本的统计学习方法&#xff0c;用于建立特征与连续目标变量之间的线性关系模型。其核心思想是通过最小化预测值与实际观测值之间的误差来拟合数据。这种模型假设自变量&#xff08;特征&#xff09;与因变量&#xff08;目标&#xff09;之间的关系是线性的&…

基于字典学习的地震数据降噪(MATLAB R2021B)

稀疏表示基于研究者们提出了许多变换基函数的方法逐渐成型&#xff0c;比如小波域&#xff0c;曲波域&#xff0c;dreamlet 域等&#xff0c;其原理是利用地震信号在变换域内的稀疏性和可分离性以去除噪声。继 Donoho发表非线性去噪方法-小波阈值萎缩方法&#xff0c;在后续的研…

半导体芯闻--20240707

1、韩国基础科学研究所团队开发出的亚纳米级半导体逻辑电路技术&#xff0c;实现了宽度小于1纳米的一维金属材料在二维电路中的应用。这一突破标志着下一代半导体及基础材料科学的重大进展&#xff0c;对解决传统半导体制造过程中的技术问题具有重要意义。 2、英特尔选择台积电…

HackTheBox--Headless

Headless测试过程 1 信息收集 NMAP端口扫描 nmap -sSCV 10.10.11.85000端口测试 检查页面功能&#xff0c;请求 For questions 功能&#xff0c;跳转到 /support 目录 目录扫描 发现 /dashboard 目录 访问 /dashboard 目录&#xff0c;显示未认证&#xff0c;如果通过认证…

MySQL之备份与恢复和MySQL用户工具(一)

备份与恢复 备份脚本化 为备份写一些脚本是标准做法。展示一个示例程序&#xff0c;其中必定有很多辅助内容&#xff0c;这只会增加篇幅&#xff0c;在这里我们更愿意列举一些典型的备份脚本功能&#xff0c;展示一些Perl脚本的代码片段。你可以把这些当作可重用的代码块&…

图解 RocketMQ 架构

写在前面 Kafka、RocketMQ都是很出名的中间件&#xff0c;上次我们讲解了Kafka&#xff0c;这次我们来讲讲RocketMQ的原理。 基本架构图 解析 RocketMQ 总共可以分成四个模块 NameServer&#xff1a;提供服务发现和路由功能&#xff0c;管理各种元数据信息。Broker&#xf…

基于最大相邻夹角的边缘点提取(matlab)

1、背景介绍 边缘点是指点云数据中代表物体或场景几何形状突变的那些点。在三维点云中&#xff0c;边缘点通常标志着不同表面或物体的分界&#xff0c;或者是物体表面上的不规则性&#xff0c;如裂缝、棱角、突起等。点云边缘检测的作用非常重要&#xff0c;最常见是进行特征点…

php校园二手交易网站-计算机毕业设计源码041148

目 录 摘要 1 绪论 1.1 研究背景 1.2国内外研究现状 1.3论文结构与章节安排 2 校园二手交易网站系统分析 2.1 可行性分析 2.2 系统流程分析 2.2.1 数据流程 3.3.2 业务流程 2.3 系统功能分析 2.3.1 功能性分析 2.3.2 非功能性分析 2.4 系统用例分析 2.5本章小结…

AI学习指南机器学习篇-K均值聚类聚类数目选择

AI学习指南机器学习篇-K均值聚类聚类数目选择 在机器学习领域&#xff0c;K均值聚类是一种常用的无监督学习方法&#xff0c;用于将数据集分成K个类别。然而&#xff0c;选择适当的聚类数目K是一个常见的问题&#xff0c;因为不恰当的聚类数目选择可能导致聚类结果不理想。本文…

nginx配置代理

nginx配置代理 1. 安装并启动Nginx&#xff1a;2. 加载Nginx配置文件&#xff1a;3. 创建虚拟主机配置文件&#xff1a;4. 创建符号链接以启用虚拟主机&#xff1a;5. 检查配置文件语法是否正确&#xff1a;6. 重新加载Nginx配置&#xff1a;7. 配置反向代理&#xff1a;8. 高级…

MySQL安全加固

安全加固 禁止MySQL以管理员的身份账号权限运行 #用普通账户运行mysqld#加固方法&#xff1a; my.cnf配置文件中配置usermysql是否存在密码为空的用户 #1、sql_mode启用 NO_CREATE_AUTO_USER&#xff0c;这个参数&#xff08;NO_CREATE_AUTO_USER&#xff09;禁止自动创建密…

基于AOP的数据字典实现:实现前端下拉框的可配置更新

作者&#xff1a;后端小肥肠 创作不易&#xff0c;未经允许严禁转载。 目录 1. 前言 2. 数据字典 2.1. 数据字典简介 2.2. 数据字典如何管理各模块的下拉框 3. 数据字典核心内容解读 3.1. 表结构 3.2. 核心代码 3.2.1. 根据实体类名称获取下属数据字典 3.2.2. 数据字…

回顾 DTC 2024 大会——聚焦数据技术创新:揭秘下一代纯实时搜索引擎 INFINI Pizza

2024 年 4 月 12 日至 13 日&#xff0c;备受瞩目的第十三届“数据技术嘉年华”&#xff08;DTC2024&#xff09;在北京新云南皇冠假日酒店盛大开幕。本次大会由中国 DBA 联盟&#xff08;ACDU&#xff09;与墨天轮社区联合主办&#xff0c;以“智能云原生一体化——DB 与 AI 协…

在先企业字号被申请注册成商标!

今天一网友联系普推商标知产老杨&#xff0c;说自己注册的商标被某公司无效宣告了&#xff0c;去年联系老杨时&#xff0c;当时就给说这个商标名称存在风险&#xff0c;与别人的字号权存在高度近似&#xff0c;而且是同行业同地区在后面注册的。 十几年前某公司先成功注册成字号…

Ubuntu 安装CGAL

一、什么是CGAL CGAL&#xff08;Computational Geometry Algorithms Library&#xff09;是一个广泛使用的开源库&#xff0c;主要用于计算几何算法的实现。该库提供了一系列高效、可靠和易于使用的几何算法和数据结构&#xff0c;适用于各种应用领域。以下是 CGAL 的主要功能…

postcss-pxtorem

postcss-pxtorem屏幕自适应 //安装插件 npm install postcss postcss-pxtorem --save-dev//在根目录中新增postcss.config.cjs或postcss.config.js文件&#xff0c;并写上如下代码 module.exports {plugins: {"postcss-pxtorem": {rootValue: 16,selectorBlackList…

如何利用Java Stream API简化集合操作?

如何利用Java Stream API简化集合操作&#xff1f; 大家好&#xff0c;我是微赚淘客系统3.0的小编&#xff0c;也是冬天不穿秋裤&#xff0c;天冷也要风度的程序猿&#xff01; Java Stream API是Java 8引入的一种新的抽象概念&#xff0c;用于对集合对象进行函数式风格的操作…

算力共享和联邦学习的关系

目录 算力 共享和联邦学习的关系 算力共享 联邦学习 算力共享与联邦学习的关系 算力 共享和联邦学习的关系 算力共享和联邦学习之间存在着紧密的关系,它们都是现代数据处理和机器学习领域中的重要概念,尤其在处理大规模数据和保护数据隐私方面发挥着关键作用。 算力共享…