FFmpeg 实现从麦克风获取流并通过RTMP推流

news2024/9/21 11:11:43

使用FFmpeg库实现从麦克风获取流并通过RTMP推流,FFmpeg版本为4.4.2-0。RTMP服务器使用的是SRS,我这边是跑在Ubuntu上的,最好是关闭掉系统防火墙。拉流端使用VLC。如果想要降低延时,请看我另外一篇博客,里面有说降低延时的方法。

Linux上查看麦克风设备命令:

#列出系统中的录音设备
arecord -l

#列出设备的详细信息,比如采样规格等
pactl list sources

再记录下Linux下音频设备名 plughw 和 hw 的区别:

代码如下:

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <libavdevice/avdevice.h>
#include <libswscale/swscale.h>
#include <libavutil/imgutils.h>
#include <libswresample/swresample.h>
#include <libavutil/fifo.h>

AVFormatContext *out_context = NULL;
AVCodecContext *c = NULL;
struct SwrContext *swr_ctx = NULL;
AVStream *out_stream = NULL;
AVFrame *output_frame = NULL;
int fsize = 0, thread_encode_exit = 0;
AVFifoBuffer *fifo = NULL;
pthread_mutex_t lock;

void *thread_encode(void *);
int main(void)
{
    const char *input_format_name = "alsa";
    const char *device_name = "hw:1,0";
    const char *in_sample_rate = "16000";                     // 采样率
    const char *in_channels = "1";                            // 声道数
    const char *url = "rtmp://192.168.3.230/live/livestream"; // rtmp地址
    int ret = -1;
    int streamid = -1;
    AVDictionary *options = NULL;
    AVInputFormat *fmt = NULL;
    AVFormatContext *in_context = NULL;
    AVCodec *codec = NULL;

    // 注册所有设备
    avdevice_register_all();

    // 查找输入格式
    fmt = av_find_input_format(input_format_name);
    if (!fmt)
    {
        printf("av_find_input_format error");
        return -1;
    }

    // 设置麦克风音频参数
    av_dict_set(&options, "sample_rate", in_sample_rate, 0);
    av_dict_set(&options, "channels", in_channels, 0);

    // 打开输入流并初始化格式上下文
    ret = avformat_open_input(&in_context, device_name, fmt, &options);
    if (ret != 0)
    {
        // 错误的时候释放options,成功的话 avformat_open_input 内部会释放
        av_dict_free(&options);
        printf("avformat_open_input error\n");
        return -1;
    }

    // 查找流信息
    if (avformat_find_stream_info(in_context, 0) < 0)
    {
        printf("avformat_find_stream_info failed\n");
        return -1;
    }

    // 查找音频流索引
    streamid = av_find_best_stream(in_context, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
    if (streamid < 0)
    {
        printf("cannot find audio stream");
        goto end;
    }
    AVStream *stream = in_context->streams[streamid];
    printf("audio stream, sample_rate: %d, channels: %d, format: %s\n",
           stream->codecpar->sample_rate, stream->codecpar->channels,
           av_get_sample_fmt_name((enum AVSampleFormat)stream->codecpar->format));

    int64_t channel_layout = av_get_default_channel_layout(stream->codecpar->channels);
    // 初始化重采样上下文,需要把输入的音频采样格式转换为编码器需要的格式
    swr_ctx = swr_alloc_set_opts(NULL,
                                 channel_layout, AV_SAMPLE_FMT_FLTP, stream->codecpar->sample_rate,
                                 channel_layout, stream->codecpar->format, stream->codecpar->sample_rate,
                                 0, NULL);
    if (!swr_ctx || swr_init(swr_ctx) < 0)
    {
        printf("allocate resampler context failed\n");
        goto end;
    }

    // 分配输出格式上下文
    avformat_alloc_output_context2(&out_context, NULL, "flv", NULL);
    if (!out_context)
    {
        printf("avformat_alloc_output_context2 failed\n");
        goto end;
    }

    // 查找编码器
    codec = avcodec_find_encoder(AV_CODEC_ID_AAC);
    if (!codec)
    {
        printf("Codec not found\n");
        goto end;
    }
    printf("codec name: %s\n", codec->name);

    // 创建新的视频流
    out_stream = avformat_new_stream(out_context, NULL);
    if (!out_stream)
    {
        printf("avformat_new_stream failed\n");
        goto end;
    }

    // 分配编码器上下文
    c = avcodec_alloc_context3(codec);
    if (!c)
    {
        printf("avcodec_alloc_context3 failed\n");
        goto end;
    }

    // 设置编码器参数
    c->codec_id = AV_CODEC_ID_AAC;
    c->codec_type = AVMEDIA_TYPE_AUDIO;
    c->sample_fmt = AV_SAMPLE_FMT_FLTP;
    c->sample_rate = stream->codecpar->sample_rate;
    c->channels = stream->codecpar->channels;
    c->channel_layout = channel_layout;
    c->bit_rate = 64000;
    c->profile = FF_PROFILE_AAC_LOW;
    if (out_context->oformat->flags & AVFMT_GLOBALHEADER)
    {
        printf("set AV_CODEC_FLAG_GLOBAL_HEADER\n");
        c->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
    }

    // 打开编码器
    if (avcodec_open2(c, codec, NULL) < 0)
    {
        printf("avcodec_open2 failed\n");
        goto end;
    }

    // 将编码器参数复制到流
    ret = avcodec_parameters_from_context(out_stream->codecpar, c);
    if (ret < 0)
    {
        printf("avcodec_parameters_from_context failed\n");
        goto end;
    }

    // 打开url
    if (!(out_context->oformat->flags & AVFMT_NOFILE))
    {
        ret = avio_open(&out_context->pb, url, AVIO_FLAG_WRITE);
        if (ret < 0)
        {
            printf("avio_open error (errmsg '%s')\n", av_err2str(ret));
            goto end;
        }
    }

    // 写文件头
    ret = avformat_write_header(out_context, NULL);
    if (ret < 0)
    {
        printf("avformat_write_header failed\n");
        goto end;
    }

    output_frame = av_frame_alloc();
    if (!output_frame)
    {
        printf("av_frame_alloc failed\n");
        goto end;
    }
    AVPacket *recv_ptk = av_packet_alloc();
    if (!recv_ptk)
    {
        printf("av_packet_alloc failed\n");
        goto end;
    }

    // 设置帧参数, av_frame_get_buffer 在分配缓冲区时会用到
    output_frame->format = c->sample_fmt;
    output_frame->nb_samples = c->frame_size;
    output_frame->channel_layout = c->channel_layout;

    // 分配缓冲区
    ret = av_frame_get_buffer(output_frame, 0);
    if (ret < 0)
    {
        printf("av_frame_get_buffer failed\n");
        goto end;
    }

    // 计算编码每帧aac所需的pcm数据的大小 = 采样个数 * 采样格式大小 * 声道数
    fsize = c->frame_size * av_get_bytes_per_sample(stream->codecpar->format) *
            stream->codecpar->channels;
    printf("frame size: %d\n", fsize);

    fifo = av_fifo_alloc(fsize * 5);
    if (!fifo)
    {
        printf("av_fifo_alloc failed\n");
        goto end;
    }

    // 创建线程
    pthread_t tid;
    pthread_mutex_init(&lock, NULL);
    pthread_create(&tid, NULL, thread_encode, NULL);

    // 读取帧并进行重采样,编码,发送
    AVPacket read_pkt;
    while ((av_read_frame(in_context, &read_pkt) >= 0) && (!thread_encode_exit))
    {
        if (read_pkt.stream_index == streamid)
        {
            pthread_mutex_lock(&lock);
            av_fifo_generic_write(fifo, read_pkt.buf->data, read_pkt.size, NULL);
            pthread_mutex_unlock(&lock);
        }
        av_packet_unref(&read_pkt);
    }
    thread_encode_exit = 1;

end:
    pthread_join(tid, NULL);
    pthread_mutex_destroy(&lock);
    if (c)
        avcodec_free_context(&c);
    if (output_frame)
        av_frame_free(&output_frame);
    if (recv_ptk)
        av_packet_free(&recv_ptk);
    if (swr_ctx)
        swr_free(&swr_ctx);
    if (out_context)
        avformat_free_context(out_context);
    if (in_context)
        avformat_close_input(&in_context);
    if (fifo)
        av_fifo_free(fifo);

    return 0;
}

void *thread_encode(void *)
{
    int ret;
    int64_t pts = 0;

    uint8_t *buf = av_malloc(fsize);
    if (!buf)
    {
        printf("av_malloc failed\n");
        goto end;
    }
    AVPacket *recv_ptk = av_packet_alloc();
    if (!recv_ptk)
    {
        printf("av_packet_alloc failed\n");
        goto end;
    }

    while (!thread_encode_exit)
    {
        pthread_mutex_lock(&lock);
        if (av_fifo_size(fifo) < fsize)
        {
            // 不够一帧aac编码所需的数据
            pthread_mutex_unlock(&lock);
            usleep(2 * 1000);
            continue;
        }
        av_fifo_generic_read(fifo, buf, fsize, NULL);
        pthread_mutex_unlock(&lock);

        // 重采样
        ret = swr_convert(swr_ctx, output_frame->data, output_frame->nb_samples,
                          (const uint8_t **)&buf, output_frame->nb_samples);
        if (ret < 0)
        {
            printf("swr_convert failed\n");
            goto end;
        }

        output_frame->pts = pts;
        pts += output_frame->nb_samples;

        // 发送帧给编码器
        ret = avcodec_send_frame(c, output_frame);
        if (ret < 0)
        {
            printf("avcodec_send_frame failed\n");
            goto end;
        }

        // 接收编码后的数据包
        while (ret >= 0)
        {
            ret = avcodec_receive_packet(c, recv_ptk);
            if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
            {
                break;
            }
            else if (ret < 0)
            {
                printf("avcodec_receive_packet error (errmsg '%s')\n", av_err2str(ret));
                goto end;
            }

            recv_ptk->stream_index = out_stream->index;
            av_packet_rescale_ts(recv_ptk, c->time_base, out_stream->time_base);

            ret = av_interleaved_write_frame(out_context, recv_ptk);
            if (ret < 0)
            {
                printf("av_interleaved_write_frame failed\n");
                av_packet_unref(recv_ptk);
                goto end;
            }
            av_packet_unref(recv_ptk);
        }
    }

end:
    if (buf)
        av_free(buf);
    if (recv_ptk)
        av_packet_free(&recv_ptk);

    thread_encode_exit = 1;
    return NULL;
}

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

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

相关文章

【密码学基础】基于LWE(Learning with Errors)的全同态加密方案

学习资源&#xff1a; 全同态加密I&#xff1a;理论与基础&#xff08;上海交通大学 郁昱老师&#xff09; 全同态加密II&#xff1a;全同态加密的理论与构造&#xff08;Xiang Xie老师&#xff09; 现在第二代&#xff08;如BGV和BFV&#xff09;和第三代全同态加密方案都是基…

数据集 | 人脸公开数据集的介绍及下载地址

本文介绍了人脸相关算法的数据集。 1.人脸数据集详情 1.1.Labeled Faces in the Wild (LFW) 论文 下载地址&#xff1a;LFW Face Database : Main (umass.edu) 是目前人脸识别的常用测试集&#xff0c;其中提供的人脸图片均来源于生活中的自然场景&#xff0c;因此识别难度会…

表情包原理

https://unicode.org/Public/emoji/12.1/emoji-zwj-sequences.txt emoji 编码规则介绍_emoji编码-CSDN博客 UTS #51: Unicode Emoji C UTF-8编解码-CSDN博客 创作不易&#xff0c;小小的支持一下吧&#xff01;

数据结构练习

1. 快速排序的非递归是通过栈来实现的&#xff0c;则前序与层次可以通过控制入栈的顺序来实现&#xff0c;因为递归是会一直开辟栈区空间&#xff0c;所以非递归的实现只需要一个栈的大小&#xff0c;而这个大小是小于递归所要的&#xff0c; 非递归与递归的时间复杂度是一样的…

Docker Desktop如何换镜像源?

docker现在很多镜像源都出现了问题,导致无法拉取镜像,所以找到一个好的镜像源,尤为重要。 一、阿里镜像源 经过测试,目前,阿里云镜像加速地址还可以使用。如果没有阿里云账号,需要先注册一个账号。 地址:https://cr.console.aliyun.com/cn-hangzhou/instances/mirrors 二…

[Flink]三、Flink1.13

11. Table API 和 SQL 如图 11-1 所示&#xff0c;在 Flink 提供的多层级 API 中&#xff0c;核心是 DataStream API &#xff0c;这是我们开发流 处理应用的基本途径&#xff1b;底层则是所谓的处理函数&#xff08; process function &#xff09;&#xff0c;可以访…

User parameters 用户参数与Web监控

目录 一. 自定义键介绍 二. 制作步骤 1. 添加无可变部分参数 2. 添加有可变参数 3. 使用用户参数监控php-fpm 服务的状态 三. Web页面导入应用监控 四. Web监控 主要功能和操作&#xff1a; 开启方式 官方预定义监控项文档https://www.zabbix.com/documentation/6…

fastjson-1.2.24漏洞复现

文章目录 0x01 前言0x02 环境0x03漏洞复现环境准备 0x04 漏洞分析利用链源码分析 0x05 总结0x06 可能遇到的坑 0x01 前言 影响版本 fastjson < 1.2.24 本文出于学习fastjson漏洞的目的&#xff0c;为了能更好的复现漏洞&#xff0c;需要有以下前置知识。 springbootfastj…

刷代码随想录有感(129):动态规划——两个字符串的删除操作

题干&#xff1a; 代码&#xff1a; class Solution { public:int minDistance(string word1, string word2) {vector<vector<int>>dp(word1.size() 1, vector<int>(word2.size() 1, 0));for(int i 1; i < word1.size(); i){for(int j 1; j < wor…

15个最佳WooCommerce商城网站及其主要功能

正在寻找的WooCommerce商城网站来激发灵感&#xff1f; 在动态的在线购物世界中&#xff0c;WooCommerce 就像企业的超级英雄。它帮助他们轻松创建强大而可靠的在线商店&#xff0c;并与WordPress顺畅协作。 从创新的产品展示到简化的结账流程&#xff0c;每个特色网站都拥有…

724.力扣每日一题7/8 Java

博客主页&#xff1a;音符犹如代码系列专栏&#xff1a;算法练习关注博主&#xff0c;后期持续更新系列文章如果有错误感谢请大家批评指出&#xff0c;及时修改感谢大家点赞&#x1f44d;收藏⭐评论✍ 目录 思路 解题方法 时间复杂度 空间复杂度 Code 思路 主要基于数组的…

解析Xml文件并修改QDomDocument的值

背景&#xff1a; 我需要解决一个bug&#xff0c;需要我从xml中读取数据到QDomDocument&#xff0c;然后获取到我想要的目标信息&#xff0c;然后修改该信息。 ---------------------------------------------------------------------------------------------------------…

PPO控制人形机器人行走举例

PPO控制人形机器人行走 Proximal Policy Optimization (PPO) 是一种策略优化算法,在强化学习中广泛使用。它通过改进策略梯度方法,使得训练过程更加稳定和高效。 PPO算法原理介绍 PPO算法主要有两种变体:PPO-Clip 和 PPO-Penalty。这里主要介绍PPO-Clip,因为它更常用。 …

RecyclerView

1、导入RecyclerView包 2、在activity_main.xml中创建RecyclerView <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas.android.com/apk/res/android"android:layout_width"match_parent"…

Face_recognition实现人脸识别

这里写自定义目录标题 欢迎使用Markdown编辑器一、安装人脸识别库face_recognition1.1 安装cmake1.2 安装dlib库1.3 安装face_recognition 二、3个常用的人脸识别案例2.1 识别并绘制人脸框2.2 提取并绘制人脸关键点2.3 人脸匹配及标注 欢迎使用Markdown编辑器 本文基于face_re…

@Builder注解详解:巧妙避开常见的陷阱

欢迎来到我的博客&#xff0c;代码的世界里&#xff0c;每一行都是一个故事 &#x1f38f;&#xff1a;你只管努力&#xff0c;剩下的交给时间 &#x1f3e0; &#xff1a;小破站 Builder注解详解&#xff1a;巧妙避开常见的陷阱 前言1. Builder的基本使用使用示例示例类创建对…

Java——面试题

1、JDK 和 JRE 有什么区别&#xff1f; JDK&#xff08;Java Development Kit&#xff09;&#xff0c;Java开发工具包 JRE&#xff08;Java Runtime Environment&#xff09;&#xff0c;Java运行环境 JDK中包含JRE&#xff0c;JDK中有一个名为jre的目录&#xff0c;里面包含…

电子发票管理系统-计算机毕业设计源码99719

摘 要 本文旨在设计和实现一个基于SpringBoot的电子发票管理系统&#xff0c;以提升企业的发票管理效率和准确性。随着电子化发票管理的需求增加&#xff0c;企业需要一个高效、可靠且功能丰富的系统来帮助管理发票信息。基于SpringBoot的电子发票管理系统将提供诸如发票信息、…

多数据源及其连接池的配置、事务管理器的注册和使用

&#xff08;ps&#xff1a;如果只有这几个数据源&#xff0c;请选择一个默认的数据源和对应的事务管理器均加上Primary注解&#xff09;示例&#xff1a; 1.在yml文件中配置多数据源/池的信息 spring:datasource:type: com.alibaba.druid.pool.DruidDataSourcedruid:initia…

nodejs + vue3 模拟 fetchEventSouce进行sse流式请求

先上效果图: 前言: 在GPT爆发的时候,各项目都想给自己的产品加上AI,蹭上AI的风口,因此在最近的一个需求,就想要给项目加入Ai的功能,原本要求的效果是,查询到对应的数据后,完全展示出来,也就是常规的post请求,后来这种效果遇到了一个很现实的问题:长时间的等待。我…