使用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;
}
相关博客:FFmpeg PCM编码为AAC