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 <unistd.h>
#include <libavdevice/avdevice.h>
#include <libswscale/swscale.h>
#include <libavutil/imgutils.h>
#include <libswresample/swresample.h>
#include <libavutil/audio_fifo.h>

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, streamid = -1;
    int64_t pts = 0;
    AVDictionary *options = NULL;
    AVInputFormat *fmt = NULL;
    AVFormatContext *in_context = NULL, *out_context = NULL;
    AVCodec *c = NULL;
    AVCodecContext *ctx = NULL;
    struct SwrContext *swr_ctx = NULL;
    AVStream *out_stream = NULL;
    AVFrame *output_frame = NULL;
    int fsize = 0;
    AVAudioFifo *fifo = 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, (enum AVSampleFormat)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;
    }

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

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

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

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

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

    // 将编码器参数复制到流
    ret = avcodec_parameters_from_context(out_stream->codecpar, ctx);
    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;
    }
    // 设置帧参数, av_frame_get_buffer 在分配缓冲区时会用到
    output_frame->format = ctx->sample_fmt;
    output_frame->nb_samples = ctx->frame_size;
    output_frame->channel_layout = ctx->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 = ctx->frame_size * av_get_bytes_per_sample(stream->codecpar->format) *
            stream->codecpar->channels; // 重采样前的大小
    printf("fsize size: %d\n", fsize);

    fifo = av_audio_fifo_alloc((enum AVSampleFormat)stream->codecpar->format,
                               stream->codecpar->channels, ctx->frame_size * 5);
    if (!fifo)
    {
        printf("av_audio_fifo_alloc 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;
    }

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

    // 计算每个样本的字节数
    int sample_size = av_get_bytes_per_sample(stream->codecpar->format);
    // 读取帧
    AVPacket read_pkt;
    while (av_read_frame(in_context, &read_pkt) >= 0)
    {
        if (read_pkt.stream_index == streamid)
        {
            // printf("read_pkt.size: %d\n", read_pkt.size);
            // printf("nb_samples: %d\n", read_pkt.size / sample_size);
            av_audio_fifo_write(fifo, (void **)&read_pkt.buf->data,
                                read_pkt.size / sample_size);
            if (av_audio_fifo_size(fifo) < ctx->frame_size)
            {
                // 不够一帧aac编码所需的数据
                continue;
            }
            av_audio_fifo_read(fifo, (void **)&buf, ctx->frame_size);
            // 重采样
            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(ctx, output_frame);
            if (ret < 0)
            {
                printf("avcodec_send_frame failed\n");
                goto end;
            }

            // 接收编码后的数据包
            while (ret >= 0)
            {
                ret = avcodec_receive_packet(ctx, 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, ctx->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);
            }
        }
        av_packet_unref(&read_pkt);
    }

end:
    if (ctx)
        avcodec_free_context(&ctx);
    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 && !(out_context->flags & AVFMT_NOFILE))
        avio_close(out_context->pb);
    if (out_context)
        avformat_free_context(out_context);
    if (in_context)
        avformat_close_input(&in_context);
    if (fifo)
        av_audio_fifo_free(fifo);
    if (buf)
        av_free(buf);

    return 0;
}

相关博客:FFmpeg PCM编码为AAC 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值