【FFmpeg】PCM编码成AAC

使用FFmpeg把PCM裸数据编码成AAC音频流,具体步骤跟YUV编码成H264差不多。

一、命令行

ffmpeg -f s16le -ar 44100 -ac 2 -i bb1.pcm output.aac

-f PCM数据为s16le

-ar 采样率为44100

-ac 通道数为2

这样就通过命令把PCM数据编码成AAC了。

二、使用API编码

FFmpeg内部AAC格式只支持AV_SAMPLE_FMT_FLTP格式的PCM,由于我们的PCM数据是s16le的,因此我们需要把s16le格式转换成fltp格式再进行编码。我们可以在AVCodec结构体中的sample_fmts字段中判断编码器是否支持你的格式。

  • 初始化输出文件上下文

    int avformat_alloc_output_context2(AVFormatContext **ctx, ff_const59 AVOutputFormat *oformat,
                                       const char *format_name, const char *filename);
    

    ctx 输出文件的上下文

    oformat 输出文件的AVOutputFormat,传NULL,FFmpeg会根据filename的格式初始化oformat

    format_name 输出文件的格式, 传NULL,FFmpeg会根据filename的格式初始化format_name

    filename 输出文件路径

  • 初始化编码器上下文

    dec = avcodec_find_encoder(ofmt_ctx->oformat->audio_codec);
    if (!dec) {
        printf("avcodec_find_encoder fail \n");
        goto __FAIL;
    }
    dec_ctx = avcodec_alloc_context3(dec);
    dec_ctx->sample_fmt = AV_SAMPLE_FMT_FLTP;
    if (!check_sample_fmt(dec, dec_ctx->sample_fmt)) {
        fprintf(stderr, "Encoder does not support sample format %s",
                av_get_sample_fmt_name(dec_ctx->sample_fmt));
        goto __FAIL;
    }
    dec_ctx->channel_layout = select_channel_layout(dec);
    dec_ctx->channels = av_get_channel_layout_nb_channels(dec_ctx->channel_layout);
    dec_ctx->sample_rate = select_sample_rate(dec);
    dec_ctx->bit_rate = 64000;
    ret = avcodec_open2(dec_ctx, dec, NULL);
    

    FFmpeg内部AAC音频流只支持fltp格式的PCM,使用check_sample_fmt函数可以检测编码器是否支持AV_SAMPLE_FMT_FLTP,通过select_channel_layout函数选择最佳的音频通道布局,通过select_sample_rate函数选择最佳的采样率。

    检测是否支持AVSampleFormat

    static int check_sample_fmt(const AVCodec *codec, enum AVSampleFormat sample_fmt)
    {
        const enum AVSampleFormat *p = codec->sample_fmts;
    
        while (*p != AV_SAMPLE_FMT_NONE) {
            if (*p == sample_fmt)
                return 1;
            p++;
        }
        return 0;
    }
    

    选择最佳采样率

    static int select_sample_rate(const AVCodec *codec)
    {
        const int *p;
        int best_samplerate = 0;
    
        if (!codec->supported_samplerates)
            return 44100;
    
        p = codec->supported_samplerates;
        while (*p) {
            if (!best_samplerate || abs(44100 - *p) < abs(44100 - best_samplerate))
                best_samplerate = *p;
            p++;
        }
        return best_samplerate;
    }
    

    选择最佳通道布局

    static int select_channel_layout(const AVCodec *codec)
    {
        const uint64_t *p;
        uint64_t best_ch_layout = 0;
        int best_nb_channels   = 0;
    
        if (!codec->channel_layouts)
            return AV_CH_LAYOUT_STEREO;
    
        p = codec->channel_layouts;
        while (*p) {
            int nb_channels = av_get_channel_layout_nb_channels(*p);
    
            if (nb_channels > best_nb_channels) {
                best_ch_layout    = *p;
                best_nb_channels = nb_channels;
            }
            p++;
        }
        return best_ch_layout;
    }
    
  • 创建输入文件音频流

    AVStream *st = avformat_new_stream(ofmt_ctx, dec);
    ret = avcodec_parameters_from_context(st->codecpar, dec_ctx);
    if (ret<0) {
          printf("avcodec_parameters_from_context fail \n");
        goto __FAIL;
    }
    

    把编码器上下文参数拷贝给新建的AVSteam

  • 打开输出文件

    avio_open(&ofmt_ctx->pb, aacPath.UTF8String, AVIO_FLAG_WRITE);
    
  • 写入文件头

    avformat_write_header(ofmt_ctx, NULL);
    
  • 读取PCM数据,放到AVFrame中

    • 初始化AVFrame用来存放PCM数据

      AVFrame *s16_frame = av_frame_alloc();
      if (!s16_frame) {
          printf("av_frame_alloc fail \n");
          goto __FAIL;
      }
      s16_frame->nb_samples = dec_ctx->frame_size;
      s16_frame->format = AV_SAMPLE_FMT_S16;
      s16_frame->channel_layout = AV_CH_LAYOUT_STEREO;
      s16_frame->sample_rate = 44100;
      ret = av_frame_get_buffer(s16_frame, 0);
      

      AVFrame的参数要与你的PCM数据参数一致,我用到的PCM数据是s16le、采样率44100Hz、通道数为2。

    • 从文件中读取PCM数据

      size_t size = fread(pcm_buffer, 1, pcm_buffer_size, pcm_f);
      
    • 存放到AVFrame中去

      av_samples_fill_arrays(s16_frame->data, s16_frame->linesize, pcm_buffer, s16_frame->channels, s16_frame->nb_samples, s16_frame->format, 0);
      int av_samples_fill_arrays(uint8_t **audio_data, int *linesize,
                                 const uint8_t *buf,
                                 int nb_channels, int nb_samples,
                                 enum AVSampleFormat sample_fmt, int align);
      

      audio_data 输出buffer,传frame->data即可

      linesize 输出buffer的行大小,传frame->linesize即可

      buf 音频数据

      nb_channels 音频通道数

      nb_samples 音频采样数

      sample_fmt 音频数据格式

      align buffer的对齐方式 默认为0,不对齐传1

  • s16le->fltp格式转换

    • 创建SwrContext

      struct SwrContext *swr_alloc_set_opts(struct SwrContext *s,
                                            int64_t out_ch_layout, enum AVSampleFormat out_sample_fmt, int out_sample_rate,
                                            int64_t  in_ch_layout, enum AVSampleFormat  in_sample_fmt, int  in_sample_rate,
                                            int log_offset, void *log_ctx);
      

      s 传NULL即可,会自动分配空间创建SwrContext

      in_ch_layout out_ch_layout 输入、输出的通道布局

      in_sample_fmt out_sample_fmt 输入、输出的PCM数据格式

      in_sample_rate out_sample_rate 输入、输出的采样率

    • 初始化SwrContext

      int swr_init(struct SwrContext *s);
      
    • 格式转换

      int swr_convert(struct SwrContext *s, uint8_t **out, int out_count,
                                      const uint8_t **in , int in_count);
      

      in out 输入、输出的buffer

      in_count out_count 输入、输出的采样数,需要注意的是,这里传的是一个通道的采样数,而不是多个通道数相加的。

  • 编码

    int avcodec_send_frame(AVCodecContext *avctx, const AVFrame *frame);
    int avcodec_receive_packet(AVCodecContext *avctx, AVPacket *avpkt);
    
  • 写文件尾

    int av_write_trailer(AVFormatContext *s);
    

完整代码如下

/* check that a given sample format is supported by the encoder */
static int check_sample_fmt(const AVCodec *codec, enum AVSampleFormat sample_fmt)
{
    const enum AVSampleFormat *p = codec->sample_fmts;

    while (*p != AV_SAMPLE_FMT_NONE) {
        if (*p == sample_fmt)
            return 1;
        p++;
    }
    return 0;
}

/* just pick the highest supported samplerate */
static int select_sample_rate(const AVCodec *codec)
{
    const int *p;
    int best_samplerate = 0;

    if (!codec->supported_samplerates)
        return 44100;

    p = codec->supported_samplerates;
    while (*p) {
        if (!best_samplerate || abs(44100 - *p) < abs(44100 - best_samplerate))
            best_samplerate = *p;
        p++;
    }
    return best_samplerate;
}

/* select layout with the highest channel count */
static int select_channel_layout(const AVCodec *codec)
{
    const uint64_t *p;
    uint64_t best_ch_layout = 0;
    int best_nb_channels   = 0;

    if (!codec->channel_layouts)
        return AV_CH_LAYOUT_STEREO;

    p = codec->channel_layouts;
    while (*p) {
        int nb_channels = av_get_channel_layout_nb_channels(*p);

        if (nb_channels > best_nb_channels) {
            best_ch_layout    = *p;
            best_nb_channels = nb_channels;
        }
        p++;
    }
    return best_ch_layout;
}


+ (void)convert
{
    NSString *pcmPath = [[NSBundle mainBundle] pathForResource:@"bb1_44100_2_s16le.pcm" ofType:nil];
    NSString *aacPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:@"bb1.aac"];

    NSLog(@"%@", aacPath);
    int ret;
    AVFormatContext *ofmt_ctx = NULL;
    AVCodecContext *dec_ctx = NULL;
    AVCodec *dec = NULL;
    AVPacket *pkt = NULL;
    AVFrame *s16_frame = NULL;
    AVFrame *fltp_frame = NULL;
    SwrContext *swr_ctx = NULL;
    FILE *pcm_f = fopen(pcmPath.UTF8String, "rb+");
    ret = avformat_alloc_output_context2(&ofmt_ctx, NULL, NULL, aacPath.UTF8String);
    if (ret<0) {
        printf("avformat_alloc_output_context2 fail \n");
        goto __FAIL;
    }
    
    dec = avcodec_find_encoder(ofmt_ctx->oformat->audio_codec);
    if (!dec) {
        printf("avcodec_find_encoder fail \n");
        goto __FAIL;
    }
    dec_ctx = avcodec_alloc_context3(dec);
    dec_ctx->sample_fmt = AV_SAMPLE_FMT_FLTP;
    if (!check_sample_fmt(dec, dec_ctx->sample_fmt)) {
        fprintf(stderr, "Encoder does not support sample format %s",
                av_get_sample_fmt_name(dec_ctx->sample_fmt));
        goto __FAIL;
    }
    dec_ctx->channel_layout = select_channel_layout(dec);
    dec_ctx->channels = av_get_channel_layout_nb_channels(dec_ctx->channel_layout);
    dec_ctx->sample_rate = select_sample_rate(dec);
    dec_ctx->bit_rate = 64000;

    ret = avio_open(&ofmt_ctx->pb, aacPath.UTF8String, AVIO_FLAG_WRITE);
    if (ret<0) {
        printf("avio_open fail \n");
        goto __FAIL;
    }
    ret = avcodec_open2(dec_ctx, dec, NULL);
    if (ret<0) {
        printf("avcodec_open2 fail \n");
        goto __FAIL;
    }
    AVStream *st = avformat_new_stream(ofmt_ctx, dec);
    ret = avcodec_parameters_from_context(st->codecpar, dec_ctx);
    if (ret<0) {
        printf("avcodec_parameters_from_context fail \n");
        goto __FAIL;
    }
    
    ret = avformat_write_header(ofmt_ctx, NULL);
    if (ret<0) {
        printf("avformat_write_header fail \n");
        goto __FAIL;
    }
    
    s16_frame = av_frame_alloc();
    if (!s16_frame) {
        printf("av_frame_alloc fail \n");
        goto __FAIL;
    }
    s16_frame->nb_samples = dec_ctx->frame_size;
    s16_frame->format = AV_SAMPLE_FMT_S16;
    s16_frame->channel_layout = AV_CH_LAYOUT_STEREO;
    s16_frame->sample_rate = 44100;
//    s16_frame->channels = av_get_channel_layout_nb_channels(s16_frame->channel_layout);
    ret = av_frame_get_buffer(s16_frame, 0);
    if (ret<0) {
        printf("av_frame_get_buffer fail \n");
        goto __FAIL;
    }
    pkt = av_packet_alloc();
    if (!pkt) {
        printf("av_packet_alloc fail \n");
        goto __FAIL;
    }
    int pts_i = 0;
    
    swr_ctx = swr_alloc_set_opts(NULL, dec_ctx->channel_layout, dec_ctx->sample_fmt, dec_ctx->sample_rate, s16_frame->channel_layout, s16_frame->format, s16_frame->sample_rate, 0, NULL);
    if (!swr_ctx) {
        printf("swr_alloc_set_opts fail \n");
        goto __FAIL;
    }
    ret = swr_init(swr_ctx);
    if (ret<0) {
        printf("swr_init fail \n");
        goto __FAIL;
    }
    fltp_frame = av_frame_alloc();
    fltp_frame->nb_samples = dec_ctx->frame_size;
    fltp_frame->format = dec_ctx->sample_fmt;
    fltp_frame->channel_layout = dec_ctx->channel_layout;
    fltp_frame->sample_rate = dec_ctx->sample_rate;
//    fltp_frame->channels = av_get_channel_layout_nb_channels(s16_frame->channel_layout);
    ret = av_frame_get_buffer(fltp_frame, 0);
    if (ret<0) {
        printf("av_frame_get_buffer fail \n");
        goto __FAIL;
    }
    uint64_t pcm_buffer_size = s16_frame->nb_samples*av_get_bytes_per_sample(s16_frame->format)*s16_frame->channels;
    uint8_t *pcm_buffer = av_malloc(pcm_buffer_size);

    while (feof(pcm_f)==0) {
        
        size_t size = fread(pcm_buffer, 1, pcm_buffer_size, pcm_f);
        
        int nb_samples = size/(av_get_bytes_per_sample(s16_frame->format)*s16_frame->channels);
        s16_frame->nb_samples = nb_samples;
        fltp_frame->nb_samples = nb_samples;
        
        av_samples_fill_arrays(s16_frame->data, s16_frame->linesize, pcm_buffer, s16_frame->channels, s16_frame->nb_samples, s16_frame->format, 0);
        
        ret = swr_convert(swr_ctx, fltp_frame->data, fltp_frame->nb_samples, s16_frame->data, s16_frame->nb_samples);
        
        if (size==0) {
            printf("fread fail \n");
            break;
        }
        pts_i+=fltp_frame->nb_samples;
        fltp_frame->pts = pts_i;
        ret = avcodec_send_frame(dec_ctx, fltp_frame);
        if (ret<0) {
            printf("avcodec_send_frame fail \n");
            break;
        }
        while (1) {
            ret = avcodec_receive_packet(dec_ctx, pkt);
            if (ret==AVERROR(EAGAIN) || ret == AVERROR_EOF) {
                break;
            } else if (ret<0) {
                printf("avcodec_receive_packet fail \n");
                break;
            }
            ret = av_interleaved_write_frame(ofmt_ctx, pkt);
            if (ret<0) {
                printf("av_interleaved_write_frame fail \n");
                break;
            }
            av_packet_unref(pkt);
        }
    }
    ret = avcodec_send_frame(dec_ctx, NULL);
    if (ret<0) {
        printf("avcodec_send_frame fail \n");
        goto __FAIL;
    }
    while (1) {
        ret = avcodec_receive_packet(dec_ctx, pkt);
        if (ret==AVERROR(EINVAL) || ret == AVERROR_EOF) {
            break;
        } else if (ret<0) {
            printf("avcodec_receive_packet fail \n");
            break;
        }
        ret = av_interleaved_write_frame(ofmt_ctx, pkt);
        if (ret<0) {
            printf("av_interleaved_write_frame fail \n");
            break;
        }
        av_packet_unref(pkt);
    }
    ret = av_write_trailer(ofmt_ctx);
    if (ret<0) {
        printf("av_write_trailer fail \n");
    }
__FAIL:
    if (ofmt_ctx->pb) {
        avio_close(ofmt_ctx->pb);
    }
    if (dec_ctx) {
        avcodec_close(dec_ctx);
    }
    if (pcm_buffer) {
        av_free(pcm_buffer);
    }
    if (ofmt_ctx) {
        avformat_free_context(ofmt_ctx);
    }
    if (s16_frame) {
        av_frame_free(&s16_frame);
    }
    if (fltp_frame) {
        av_frame_free(&fltp_frame);
    }
    if (pkt) {
        av_packet_free(&pkt);
    }
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,716评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,558评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,431评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,127评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,511评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,692评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,915评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,664评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,412评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,616评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,105评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,424评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,098评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,096评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,869评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,748评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,641评论 2 271

推荐阅读更多精彩内容