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

使用FFmpeg库实现从USB摄像头获取流并通过RTMP推流,FFmpeg版本为4.4.2-0。RTMP服务器使用的是SRS,拉流端使用VLC。

在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或v4l2
    const 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;
    }

    // 查找视频流索引
    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帧时设置为0
    av_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和sps
    if (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;
    }

    // 打开url
    if (!(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;
            }

            // 转换为 YUV420P
            sws_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.mfbz.cn/a/769807.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

数据泄露时代的安全之道:访问认证的重要性

引言 想象一下&#xff1a;你一觉醒来&#xff0c;收到一条通知——你的公司遭遇了数据泄露。你感到恐惧&#xff0c;因为这意味着客户数据被曝光&#xff0c;公司声誉受损&#xff0c;还有巨额罚款在等着你。在当今的数字化环境中&#xff0c;这种情况太常见了。全球各地的组…

Android sdk 安装已经环境配置

&#x1f34e;个人博客&#xff1a;个人主页 &#x1f3c6;个人专栏&#xff1a;Android ⛳️ 功不唐捐&#xff0c;玉汝于成 目录 正文 一、下载 二、安装 三、环境配置 我的其他博客 正文 一、下载 1、大家可去官网下载 因为需要魔法 所以就不展示了 2、去下面这…

【启明智显分享】启明智显2.8寸COF触摸串口屏:一体黑设计与塑料框架相结合

在我们不断追求智能化、科技化生活的年代&#xff0c;越来越多的高科技产品为我们的日常生活带来了无与伦比的便利。智能屏已经成为智能化进程中的重要组成部分&#xff0c;其设计的美观性、便携性、耐用性都成为产品选型的重要依据。 启明智显近期推出了一款基于乐鑫ESP32-S3R…

城镇居民社区再生资源回收系统-计算机毕业设计源码04175

摘 要 本论文介绍了一个基于SSM&#xff08;Spring Spring MVC MyBatis&#xff09;技术的城镇居民社区再生资源回收系统的设计与实现。随着社会对环境保护意识的不断提高&#xff0c;再生资源回收成为了一种重要的环保行动。然而&#xff0c;传统的再生资源回收方式存在着信…

ELISA实验前,需要做好哪些准备?

进行ELISA试剂盒实验前&#xff0c;需要进行周密的准备工作以确保实验的顺利进行和实验的准确性。那么&#xff0c;具体应该做哪些准备呢&#xff1f;欣博盛生物为您总结了一些关键的准备工作步骤&#xff1a; 1. 阅读说明书 仔细阅读ELISA试剂盒的说明书&#xff0c;了解试剂…

Day03-Jenkins与集成案例

Day03-Jenkins与集成案例 6. CD持续交付&#xff0c;持续部署实现方案7. 案例04: basketball案例,搭建开发测试专用的任务7.1 任务要求7.2 步骤7.3 详细步骤1&#xff09;安装插件2&#xff09;创建任务 7.4 gitlab配置钩子1) 解除钩子局域网访问限制2) gitlab配置钩子 7.5 与部…

IDEA开发必备的插件,实测非常好用

1、Lombok -- 简化Java代码开发 推荐指数&#xff1a; ★★★★★ Lombok&#xff1a;首当其冲的非常推荐的当然是Lombok Lombok能以简单的注解形式来简化Java代码&#xff0c;提高开发人员的开发效率。例如开发中经常需要写的JavaBean&#xff0c;都需要花时间去添加相应的ge…

实战大数据:分布式大数据分析处理系统的开发与应用

&#x1f482; 个人网站:【 摸鱼游戏】【网址导航】【神级代码资源网站】&#x1f91f; 一站式轻松构建小程序、Web网站、移动应用&#xff1a;&#x1f449;注册地址&#x1f91f; 基于Web端打造的&#xff1a;&#x1f449;轻量化工具创作平台&#x1f485; 想寻找共同学习交…

linux服务器安全级别提升配置修改

linux服务器安全级别提升配置修改 说明修改登录密码策略步骤 设置用户密码定期更新登录失败次数锁定步骤 锁定系统重要文件文件说明锁定文件解锁文件 防火墙设置firewalld防火墙查看防火墙状态开启防火墙关闭防火墙重新加载防火墙配置添加端口移除端口添加IP到白名单移除白名单…

JAVA 实现拍卖框架及拍卖详情流程介绍(包含代码示咧)

本人详解 作者:王文峰,参加过 CSDN 2020年度博客之星,《Java王大师王天师》 公众号:JAVA开发王大师,专注于天道酬勤的 Java 开发问题中国国学、传统文化和代码爱好者的程序人生,期待你的关注和支持!本人外号:神秘小峯 山峯 转载说明:务必注明来源(注明:作者:王文峰…

Java项目:基于SSM框架实现的网上医院预约挂号系统【ssm+B/S架构+源码+数据库+毕业论文】

一、项目简介 本项目是一套基于SSM框架实现的网上医院预约挂号系统 包含&#xff1a;项目源码、数据库脚本等&#xff0c;该项目附带全部源码可作为毕设使用。 项目都经过严格调试&#xff0c;eclipse或者idea 确保可以运行&#xff01; 该系统功能完善、界面美观、操作简单、…

9.计算机视觉—目标检测

目录 1.物体检测边缘框目标检测数据集总结边缘框代码实现2.锚框:目标检测的一种方法IoU—交并比赋予锚框标号使用非极大值抑制(NMS)输出总结代码实现1.物体检测 边缘框 一个边缘框可以通过四个数字定义 (左上x,左上y),(右下x,右下y)(左上x,左上y,宽,高)(中间x,中间y…

铜排载流量计算

母线载流量的理论计算 有些设计规范给出了根据电流密度确定母线大小的标准&#xff0c;一般铜母线的要求是每平方毫米载流量1.55A&#xff0c;但只可以作为设计“自由空气中的单导体母线”的参考&#xff0c;不可以作为实际设备中选择母线截面积的方法。也有些设计手册里给出了…

使用Ckman部署ClickHouse集群介绍

使用Ckman部署ClickHouse集群介绍 1. Ckman简介 ClickHouse Manager是一个为ClickHouse数据库量身定制的管理工具&#xff0c;它是由擎创科技数据库团队主导研发的一款用来管理和监控ClickHouse集群的可视化运维工具。目前该工具已在github上开源&#xff0c;开源地址为&…

WordPress主题大前端DUX v8.7源码下载

全新&#xff1a;用户注册流程&#xff0c;验证邮箱&#xff0c;设置密码 新增&#xff1a;列表显示小视频和横幅视频 新增&#xff1a;文章内容中的外链全部增加 nofollow 新增&#xff1a;客服功能中的链接添加 nofollow 优化&#xff1a;产品分类的价格显示

《C++20设计模式》桥接模式经验分享

文章目录 一、前言二、探讨一个类有多个抽象父类的情况&#xff08;为什么会有桥接&#xff09;三、桥接模式3.1 UML类图3.2 实现 四、最后 一、前言 怎么判断你是否理解桥接模式了呢&#xff1f;&#x1f9d0; 试着回答下面这个问题吧&#xff01;&#x1f60b; 桥接模式到底…

Mybatis Plus 自动填充注解 @TableField(fill = FieldFill.INSERT_UPDATE)

第一步&#xff1a;在需要自动填充的位置加上注解 通过在创建时间和修改时间上添加 fill 填充字段 进行自动填充 第二步&#xff1a;要想实现自动填充还需要实现MetaObjectHandler接口&#xff0c;在这里实现自动填充的逻辑 Component public class MyMetaObjectHandler …

pydub、ffmpeg 音频文件声道选择转换、采样率更改

快速查看音频通道数和每个通道能力判断具体哪个通道说话&#xff1b;一般能量大的那个算是说话 import wave from pydub import AudioSegment import numpy as npdef read_wav_file(file_path):with wave.open(file_path, rb) as wav_file:params wav_file.getparams()num_cha…

通过卷防水上限,解锁手机的新玩法?IP68之间亦有不同

当手机的日常防水已经成了基本功&#xff0c;防水能力的上限便成了新的赛道。 毕竟再谨慎的人&#xff0c;也可能会有手滑的时候。这个时候&#xff0c;一台有着IP68级防水的手机&#xff0c;就能给你提供一份安心。 【IP68是标准上限&#xff0c;不是手机防水上限】 IP68是…

前端三件套开发模版——产品介绍页面

今天有空&#xff0c;使用前端三件套html、css、js制作了一个非常简单的产品制作页面&#xff0c;与大家分享&#xff0c;希望可以满足大家应急的需求。本页面可以对产品进行“抢购”、对产品进行介绍&#xff0c;同时可以安排一张产品的高清大图&#xff0c;我也加入了页面的背…