使用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;
}