当前位置: 首页 > news >正文

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

使用FFmpeg库(版本号为:4.4.2-0ubuntu0.22.04.1)实现从麦克风获取流并通过RTMP推流。
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;// 打印ffmpeg版本信息printf("ffmpeg version: %s\n", av_version_info());// 注册所有设备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;}// 分配内存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;}// 打开urlif (!(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;}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){// printf("read_pkt.size: %d\n", read_pkt.size);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;
}

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 递归(五)—— 初识暴力递归之“如何利用递归实现栈逆序”
  • 【React】Ant Design -- Table分页功能实现
  • DBeaver操作MySQL无法同时执行多条语句的解决方法
  • STM32-I2C硬件外设
  • 软件源码购买一般在哪个网站?避坑指南
  • 快手可图模型的要点
  • Django 实现子模版继承父模板
  • 无损音频格式 FLAC 转 MP3 音频图文教程
  • 2024.7.7刷题记录
  • 选择排序(C语言版)
  • 【AI应用探讨】—逻辑回归应用场景
  • Java内存区域与内存溢出异常(补充)
  • 01 企业网站架构部署与优化之Apache配置与应用
  • Apache Hadoop文件上传、下载、分布式计算案例初体验
  • 【深度学习(42)】通过vscode使用anaconda的python环境
  • github指令
  • iOS高仿微信项目、阴影圆角渐变色效果、卡片动画、波浪动画、路由框架等源码...
  • Java新版本的开发已正式进入轨道,版本号18.3
  • js如何打印object对象
  • magento 货币换算
  • Netty+SpringBoot+FastDFS+Html5实现聊天App(六)
  • Vue实战(四)登录/注册页的实现
  • 解决iview多表头动态更改列元素发生的错误
  • 前端面试总结(at, md)
  • 如何胜任知名企业的商业数据分析师?
  •  一套莫尔斯电报听写、翻译系统
  • 一文看透浏览器架构
  • 用 Swift 编写面向协议的视图
  • ​​​​​​​sokit v1.3抓手机应用socket数据包: Socket是传输控制层协议,WebSocket是应用层协议。
  • ​MPV,汽车产品里一个特殊品类的进化过程
  • ‌前端列表展示1000条大量数据时,后端通常需要进行一定的处理。‌
  • # Swust 12th acm 邀请赛# [ E ] 01 String [题解]
  • #Datawhale AI夏令营第4期#多模态大模型复盘
  • (04)Hive的相关概念——order by 、sort by、distribute by 、cluster by
  • (26)4.7 字符函数和字符串函数
  • (4) openssl rsa/pkey(查看私钥、从私钥中提取公钥、查看公钥)
  • (pojstep1.3.1)1017(构造法模拟)
  • (vue)el-checkbox 实现展示区分 label 和 value(展示值与选中获取值需不同)
  • (第61天)多租户架构(CDB/PDB)
  • (六) ES6 新特性 —— 迭代器(iterator)
  • (十一)c52学习之旅-动态数码管
  • (一)kafka实战——kafka源码编译启动
  • (原創) 如何刪除Windows Live Writer留在本機的文章? (Web) (Windows Live Writer)
  • (中等) HDU 4370 0 or 1,建模+Dijkstra。
  • (转)http协议
  • (转)一些感悟
  • *_zh_CN.properties 国际化资源文件 struts 防乱码等
  • .net core 6 集成和使用 mongodb
  • .net core MVC 通过 Filters 过滤器拦截请求及响应内容
  • .net php 通信,flash与asp/php/asp.net通信的方法
  • .Net Redis的秒杀Dome和异步执行
  • .NET/C# 获取一个正在运行的进程的命令行参数
  • .netcore 如何获取系统中所有session_如何把百度推广中获取的线索(基木鱼,电话,百度商桥等)同步到企业微信或者企业CRM等企业营销系统中...
  • .NET学习全景图
  • @antv/g6 业务场景:流程图