pcm数据编码成为aac格式文件(可以在酷狗播放)

pcm数据编码成为aac格式文件(可以在酷狗播放)

关于其中的aac adts格式可以参考:AAC ADTS格式分析

main.c


#include <stdio.h>
#include <stdlib.h>
#include <stdlib.h>

#include <libavcodec/avcodec.h>
#include <libavutil/channel_layout.h>
#include <libavutil/common.h>
#include <libavutil/frame.h>
#include <libavutil/samplefmt.h>
#include <libavutil/opt.h>



//生成adts头部信息
static void make_adts_header(AVCodecContext* ctx, uint8_t* adts_header, int aac_length)
{
    
    
    //采样率索引
    uint8_t freq_idx = 0;
    switch (ctx->sample_rate) {
    
    
        case 96000:
            freq_idx = 0;
            break;
        case 88200:
            freq_idx = 1;
            break;
        case 64000:
            freq_idx = 2;
            break;
        case 48000:
            freq_idx = 3;
            break;
        case 44100:
            freq_idx = 4;
            break;
        case 32000:
            freq_idx = 5;
            break;
        case 24000:
            freq_idx = 6;
            break;
        case 22050:
            freq_idx = 7;
            break;
        case 16000:
            freq_idx = 8;
            break;
        case 12000:
            freq_idx = 9;
            break;
        case 11025:
            freq_idx = 10;
            break;
        case 8000:
            freq_idx = 11;
            break;
        case 7350:
            freq_idx = 12;
            break;
        default:
            freq_idx = 4;
            break;
    }
    uint8_t chanCfg = ctx->channels;//通道数
    uint32_t frame_length = aac_length + 7;//整个adts帧长度,包括header 和 body
    adts_header[0] = 0xFF;
    adts_header[1] = 0xF1;
    adts_header[2] = ((ctx->profile) << 6) + (freq_idx << 2) + (chanCfg >> 2);
    adts_header[3] = (((chanCfg & 3) << 6) + (frame_length  >> 11));
    adts_header[4] = ((frame_length & 0x7FF) >> 3);
    adts_header[5] = (((frame_length & 7) << 5) + 0x1F);
    adts_header[6] = 0xFC;
}

static int encode(AVCodecContext* ctx, AVFrame* frame, AVPacket* pkt, FILE* outFile)
{
    
    
    int ret;

    //发送一帧进行编码
    ret = avcodec_send_frame(ctx, frame);
    if(ret < 0)
    {
    
    
        fprintf(stderr, "avcodec_send_frame failed! errorcode : %d\n", ret);

        char buf[128] = {
    
    0};
        av_strerror(ret, buf, 128);
        printf("---------%s\n", buf);
        return -1;
    }

    while (ret >= 0)
    {
    
    
        //获取编码后数据包
        ret = avcodec_receive_packet(ctx, pkt);
        if(ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
        {
    
    
            return 0;
        }
        else if(ret < 0)
        {
    
    
            fprintf(stderr, "avcodec_receive_packet failed! errorcode : %d\n", ret);
            return -1;
        }

        //aac adts 头部信息
        uint8_t aac_header[7];
        make_adts_header(ctx, aac_header, pkt->size);

        size_t len = 0;
        //写出头部信息
        len = fwrite(aac_header, 1, 7, outFile);
        if(len != 7)
        {
    
    
            fprintf(stderr, "fwrite aac_header failed!\n");
            return -1;
        }
        //写出aac数据
        len = fwrite(pkt->data, 1, pkt->size, outFile);
        if(len != pkt->size)
        {
    
    
            fprintf(stderr, "fwrite aac data failed!\n");
            return -1;
        }
    }
    return -1;
}

#define USE_CODEC_NAME 1
int main()
{
    
    
    printf("Hello Audio Encodeer!\n");



    FILE* infile = NULL;
    FILE* outfile = NULL;
    const AVCodec* codec = NULL;
    AVCodecContext* codec_ctx = NULL;
    AVFrame* frame = NULL;
    AVPacket* pkt = NULL;
    int ret = 0;

    //注意:
    //   输入的pcm文件的采样点格式必须要与相应编码器支持的格式匹配才可以的,
    //   否则在avcodec_send_frame时就有可能出错。
    //   aac这个编码器就是要求格式:float, planar
#if USE_CODEC_NAME
    char* codec_name = "aac";
#else
    char* codec_name = "";
#endif
    char* in_pcm_file = "test.pcm";
    char* out_aac_file = "out_test.aac";


    enum AVCodecID code_id = AV_CODEC_ID_AAC;

    //如果有指定的编码器就根据名字查找
    if(codec_name && strlen(codec_name))
    {
    
    
        codec = avcodec_find_encoder_by_name(codec_name);
    }
    else
    {
    
    
        //否则根据ID查找是在编码器链表中找的第一个匹配code_id的编码器
        codec = avcodec_find_encoder(code_id);
    }
    //如果没有找到编码器就是没有安装这个编码器
    if(!codec)
    {
    
    
        fprintf(stderr, "avcodec_find_encoder failed!\n");
        return 0;
    }

    //分配编码器上下文
    codec_ctx = avcodec_alloc_context3(codec);
    if(!codec_ctx)
    {
    
    
        fprintf(stderr, "avcodec_alloc_context3 failed!\n");
        return 0;
    }

    codec_ctx->codec_id = code_id;
    codec_ctx->codec_type = AVMEDIA_TYPE_AUDIO;
    codec_ctx->bit_rate = 128 * 1024;//比特率
    codec_ctx->channel_layout = AV_CH_LAYOUT_STEREO;//立体声
    codec_ctx->sample_rate = 48000;//采样率
    codec_ctx->channels = av_get_channel_layout_nb_channels(codec_ctx->channel_layout);//通道数
    codec_ctx->profile = FF_PROFILE_AAC_LOW;//质量

    //因为不同编码器支持不同的采样格式,可以在源码中查看得到,做个判断
    if(strcmp(codec->name, "aac") == 0)
    {
    
    
        //aac 编码器 : 只支持AV_SAMPLE_FMT_FLTP这一种格式
        codec_ctx->sample_fmt = AV_SAMPLE_FMT_FLTP;//float, planar
    }
    else
    {
    
    
        sprintf(stderr, "codec->name failed!\n");
        return 0;
    }

    printf("use codec name :%s\n", codec_name);
    printf("bit rate : %ldkbps\n", codec_ctx->bit_rate / 1024);
    printf("sample_rate : %d\n", codec_ctx->sample_rate);
    printf("sample_fmt : %s\n", av_get_sample_fmt_name(codec_ctx->sample_fmt));
    printf("channels : %d\n", codec_ctx->channels);


    if(avcodec_open2(codec_ctx, codec, NULL) < 0)
    {
    
    
        fprintf(stderr, "avcodec_open2 failed!\n");
        return 0;
    }
    //每个音频帧在单个通道下的采样数,只有音频这个参数才有效(在调用avcodec_open2()后就有效了)
    printf("frame_size : %d\n", codec_ctx->frame_size);

    //打开输入输出文件
    infile = fopen(in_pcm_file, "rb");
    if(!infile)
    {
    
    
        fprintf(stderr, "fopen(in_pcm_file) failed!\n");
        return 0;
    }
    outfile = fopen(out_aac_file, "wb");
    if(!outfile)
    {
    
    
        fprintf(stderr, "fopen(out_aac_file) failed!\n");
        return 0;
    }

    //分配AVPacket 本身结构的内存
    pkt = av_packet_alloc();
    if(!pkt)
    {
    
    
        fprintf(stderr, "av_packet_alloc() failed!\n");
        return 0;
    }

    //分配AVFrame本身结构的内存
    frame = av_frame_alloc();
    if(!frame)
    {
    
    
        fprintf(stderr, "av_frame_alloc() failed!\n");
        return 0;
    }

    frame->nb_samples = codec_ctx->frame_size;//每帧单个通道的采样数
    frame->format =  codec_ctx->sample_fmt;//采样格式
    frame->channel_layout = codec_ctx->channel_layout;//通道布局
    frame->channels = av_get_channel_layout_nb_channels(frame->channel_layout);//通道数
    printf("frame nb_samples : %d\n", frame->nb_samples);
    printf("frame format : %d \n", frame->format);
    printf("frame channel_layout : %d\n", frame->channel_layout);

    //计算一帧的数据长度(字节数量)
    // av_get_bytes_per_sample(frame->format) : 一个采样点占用的字节数
    // 比如:AV_SAMPLE_FMT_S16  = 两个字节
    //      AV_SAMPLE_FMT_FLTP = 四个字节
    // frame->channels : 有几个通道
    // frame->nb_samples : 单个通道的一帧的采样数
    int frame_bytes = av_get_bytes_per_sample(frame->format) * frame->channels * frame->nb_samples;
    printf("frame_bytes : %d\n", frame_bytes);

    uint8_t* pcm_buf = (uint8_t*)malloc(frame_bytes);
    if(!pcm_buf)
    {
    
    
        printf("pcm_buf malloc failed!\n");
        return 0;
    }
    uint8_t* pcm_temp_buf = (uint8_t*)malloc(frame_bytes);
    if(!pcm_temp_buf)
    {
    
    
        printf("pcm_temp_buf malloc failed!\n");
        return 0;
    }

    int64_t pts = 0;
    while (1)
    {
    
    
        memset(pcm_buf, 0, frame_bytes);
        size_t read_byte = fread(pcm_buf, 1, frame_bytes, infile);
        if(read_byte <= 0)
        {
    
    
            sprintf(stderr, "read infile end!\n");
            break;
        }

        //将读取到的数据填充到frame,会根据不同输的不同参数填充到frame->data,frame->linesize
        ret = av_samples_fill_arrays(frame->data, frame->linesize,
                               pcm_buf, frame->channels,
                               frame->nb_samples, frame->format, 0);


        //设置每帧的pts
        pts += frame->nb_samples;
        frame->pts = pts;//使用采样率作为pts的单位,换算成实际时间(秒)pts * 1 / 采样率
        ret = encode(codec_ctx, frame, pkt, outfile);
        if(ret < 0)
        {
    
    
            sprintf(stderr, "encode failed!\n");
            break;
        }
    }

    //冲刷编码器
    encode(codec_ctx, NULL, pkt, outfile);

    fclose(infile);
    fclose(outfile);

    if(pcm_buf)
        free(pcm_buf);
    if(pcm_temp_buf)
        free(pcm_temp_buf);

    av_frame_free(&frame);
    av_packet_free(&pkt);
    avcodec_free_context(&codec_ctx);
    printf("aac encoder end!\n");



    return 0;

}

猜你喜欢

转载自blog.csdn.net/m0_37599645/article/details/112415305