【FFmpeg】PCM編碼成AAC

使用FFmpeg把PCM裸數(shù)據(jù)編碼成AAC音頻流,具體步驟跟YUV編碼成H264差不多。

一、命令行

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

-f PCM數(shù)據(jù)為s16le

-ar 采樣率為44100

-ac 通道數(shù)為2

這樣就通過命令把PCM數(shù)據(jù)編碼成AAC了。

二、使用API編碼

FFmpeg內(nèi)部AAC格式只支持AV_SAMPLE_FMT_FLTP格式的PCM,由于我們的PCM數(shù)據(jù)是s16le的,因此我們需要把s16le格式轉(zhuǎn)換成fltp格式再進(jìn)行編碼。我們可以在AVCodec結(jié)構(gòu)體中的sample_fmts字段中判斷編碼器是否支持你的格式。

  • 初始化輸出文件上下文

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

    ctx 輸出文件的上下文

    oformat 輸出文件的AVOutputFormat,傳NULL,F(xiàn)Fmpeg會根據(jù)filename的格式初始化oformat

    format_name 輸出文件的格式, 傳NULL,F(xiàn)Fmpeg會根據(jù)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內(nèi)部AAC音頻流只支持fltp格式的PCM,使用check_sample_fmt函數(shù)可以檢測編碼器是否支持AV_SAMPLE_FMT_FLTP,通過select_channel_layout函數(shù)選擇最佳的音頻通道布局,通過select_sample_rate函數(shù)選擇最佳的采樣率。

    檢測是否支持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;
    }
    
  • 創(chuàng)建輸入文件音頻流

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

    把編碼器上下文參數(shù)拷貝給新建的AVSteam

  • 打開輸出文件

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

    avformat_write_header(ofmt_ctx, NULL);
    
  • 讀取PCM數(shù)據(jù),放到AVFrame中

    • 初始化AVFrame用來存放PCM數(shù)據(jù)

      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的參數(shù)要與你的PCM數(shù)據(jù)參數(shù)一致,我用到的PCM數(shù)據(jù)是s16le、采樣率44100Hz、通道數(shù)為2。

    • 從文件中讀取PCM數(shù)據(jù)

      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 音頻數(shù)據(jù)

      nb_channels 音頻通道數(shù)

      nb_samples 音頻采樣數(shù)

      sample_fmt 音頻數(shù)據(jù)格式

      align buffer的對齊方式 默認(rèn)為0,不對齊傳1

  • s16le->fltp格式轉(zhuǎn)換

    • 創(chuàng)建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即可,會自動分配空間創(chuàng)建SwrContext

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

      in_sample_fmt out_sample_fmt 輸入、輸出的PCM數(shù)據(jù)格式

      in_sample_rate out_sample_rate 輸入、輸出的采樣率

    • 初始化SwrContext

      int swr_init(struct SwrContext *s);
      
    • 格式轉(zhuǎn)換

      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 輸入、輸出的采樣數(shù),需要注意的是,這里傳的是一個通道的采樣數(shù),而不是多個通道數(shù)相加的。

  • 編碼

    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);
    }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容