pcm data encoding into aac format file (can be played in Kugou)

pcm data encoding into aac format file (can be played in Kugou)

For the aac adts format, please refer to: AAC ADTS format analysis

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;

}

Guess you like

Origin blog.csdn.net/m0_37599645/article/details/112415305