FFmpeg PCM编码为AAC

使用FFmpeg库(版本号为:4.4.2-0ubuntu0.22.04.1)把PCM文件编码为AAC文件。

代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/opt.h>
#include <libavutil/mem.h>
#include <libavutil/samplefmt.h>
#include <libswresample/swresample.h>

int main(int argc, char *argv[])
{
    int ret = -1;
    int64_t pts = 0;
    const char *input_file = argv[1];
    const char *output_file = argv[2];
    FILE *input_pcm = NULL;
    AVFormatContext *format_context = NULL;
    AVCodecContext *codec_context = NULL;
    AVStream *stream = NULL;
    AVCodec *codec = NULL;
    struct SwrContext *swr_ctx = NULL;
    const AVOutputFormat *ofmt = NULL;

    enum AVSampleFormat in_sample_fmt = AV_SAMPLE_FMT_S16; // 输入pcm文件的采样格式
    int in_sample_rate = 44100;                            // 输入pcm文件的采样率
    int in_channels = 2;                                   // 输入pcm文件的声道数

    if (argc < 3)
    {
        printf("Usage: %s <input file> <output file>\n", argv[0]);
        return -1;
    }

    // 打印ffmpeg版本信息
    printf("ffmpeg version: %s\n", av_version_info());

    input_pcm = fopen(input_file, "rb");
    if (!input_pcm)
    {
        printf("open input file failed\n");
        goto end;
    }

    // 分配输出格式上下文
    avformat_alloc_output_context2(&format_context, NULL, NULL, output_file);
    if (!format_context)
    {
        printf("avformat_alloc_output_context2 failed\n");
        goto end;
    }
    ofmt = format_context->oformat;

    // 查找编码器, 也可以使用 avcodec_find_encoder_by_name 按名称查找
    codec = avcodec_find_encoder(AV_CODEC_ID_AAC);
    if (!codec)
    {
        printf("Codec not found\n");
        goto end;
    }
    printf("codec name: %s\n", codec->name);

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

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

    /* 设置编码器参数 */
    // 编码器ID
    codec_context->codec_id = AV_CODEC_ID_AAC;
    // 媒体类型
    codec_context->codec_type = AVMEDIA_TYPE_AUDIO;
    // 设置编码器接收的输入音频的采样格式,这里设置为内置aac编码器支持的采样格式,
    // 不同的编码器支持的采样格式可能不同。
    codec_context->sample_fmt = AV_SAMPLE_FMT_FLTP;
    // 设置采样率
    codec_context->sample_rate = in_sample_rate;
    // 设置声道数
    codec_context->channels = in_channels;
    // 设置声道布局
    codec_context->channel_layout = av_get_default_channel_layout(codec_context->channels);
    // 设置比特率
    codec_context->bit_rate = 128000;
    // 设置AAC的profile
    codec_context->profile = FF_PROFILE_AAC_LOW;

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

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

    // 初始化重采样上下文
    swr_ctx = swr_alloc_set_opts(NULL,
                                 codec_context->channel_layout, codec_context->sample_fmt, codec_context->sample_rate,
                                 codec_context->channel_layout, in_sample_fmt, codec_context->sample_rate,
                                 0, NULL);
    if (!swr_ctx || swr_init(swr_ctx) < 0)
    {
        printf("allocate resampler context failed\n");
        goto end;
    }

    // 分配内存
    AVFrame *frame = av_frame_alloc();
    if (!frame)
    {
        printf("av_frame_alloc failed\n");
        goto end;
    }
    AVPacket *packet = av_packet_alloc();
    if (!packet)
    {
        printf("av_packet_alloc failed\n");
        goto end;
    }

    // 设置帧参数
    frame->nb_samples = codec_context->frame_size; // 一帧音频数据的采样个数,AAC编码器的frame_size默认为1024
    frame->format = codec_context->sample_fmt;
    frame->channel_layout = codec_context->channel_layout;

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

    // 计算编码每帧aac所需的pcm数据的大小 = 采样个数 * 采样格式大小 * 声道数
    int fsize = frame->nb_samples * av_get_bytes_per_sample(in_sample_fmt) * codec_context->channels;
    // 分配缓冲区
    uint8_t *input_buffer = (uint8_t *)av_malloc(fsize);
    if (!input_buffer)
    {
        printf("av_malloc failed\n");
        goto end;
    }

    // 打开输出文件
    if (!(ofmt->flags & AVFMT_NOFILE))
    {
        ret = avio_open(&format_context->pb, output_file, AVIO_FLAG_WRITE);
        if (ret < 0)
        {
            printf("open output file failed\n");
            goto end;
        }
    }

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

    // 编码循环
    while (1)
    {
        // 读取PCM数据到缓冲区
        ret = fread(input_buffer, 1, fsize, input_pcm);
        if (ret <= 0)
        {
            break;
        }

        // 处理不足一帧的情况,不足一帧的数据用0填充
        if (ret < fsize)
        {
            memset(input_buffer + ret, 0, fsize - ret);
        }

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

        frame->pts = pts;
        pts += frame->nb_samples; // 计算下一帧的pts,每个帧的时间戳应该是前一个帧的时间戳加上该帧的样本数

        // 发送帧到编码器
        ret = avcodec_send_frame(codec_context, frame);
        if (ret < 0)
        {
            printf("avcodec_send_frame error (errmsg '%s')\n", av_err2str(ret));
            goto end;
        }

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

            packet->stream_index = stream->index;
            // 将时间戳从编码器时间基转换到流时间基
            av_packet_rescale_ts(packet, codec_context->time_base, stream->time_base);
            // 写数据包到输出文件
            ret = av_interleaved_write_frame(format_context, packet);
            if (ret < 0)
            {
                printf("av_interleaved_write_frame failed\n");
                av_packet_unref(packet);
                goto end;
            }

            av_packet_unref(packet);
        }
    }

    // 发送NULL到编码器,刷新编码器内部缓冲区
    ret = avcodec_send_frame(codec_context, NULL);
    while (ret >= 0)
    {
        ret = avcodec_receive_packet(codec_context, packet);
        if (ret == AVERROR_EOF)
        {
            break;
        }
        else if (ret < 0)
        {
            printf("avcodec_receive_packet error (errmsg '%s')\n", av_err2str(ret));
            goto end;
        }

        packet->stream_index = stream->index;
        av_packet_rescale_ts(packet, codec_context->time_base, stream->time_base);
        ret = av_interleaved_write_frame(format_context, packet);
        if (ret < 0)
        {
            printf("av_interleaved_write_frame failed\n");
            av_packet_unref(packet);
            goto end;
        }

        av_packet_unref(packet);
    }

    // 写文件尾
    av_write_trailer(format_context);

end:
    if (input_buffer)
        av_free(input_buffer);
    if (frame)
        av_frame_free(&frame);
    if (packet)
        av_packet_free(&packet);
    if (codec_context)
        avcodec_free_context(&codec_context);
    if (ofmt && !(ofmt->flags & AVFMT_NOFILE))
        avio_close(format_context->pb);
    if (format_context)
        avformat_free_context(format_context);
    if (swr_ctx)
        swr_free(&swr_ctx);
    if (input_pcm)
        fclose(input_pcm);

    return 0;
}

相关博客:FFmpeg 实现从麦克风获取流并通过RTMP推流 

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值