【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

這樣就通過(guò)命令把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會(huì)根據(jù)filename的格式初始化oformat

    format_name 輸出文件的格式, 傳NULL孝凌,F(xiàn)Fmpeg會(huì)根據(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ù)可以檢測(cè)編碼器是否支持AV_SAMPLE_FMT_FLTP,通過(guò)select_channel_layout函數(shù)選擇最佳的音頻通道布局瓣赂,通過(guò)select_sample_rate函數(shù)選擇最佳的采樣率。

    檢測(cè)是否支持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用來(lái)存放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的對(duì)齊方式 默認(rèn)為0,不對(duì)齊傳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即可栗弟,會(huì)自動(dòng)分配空間創(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ù)改鲫,需要注意的是器腋,這里傳的是一個(gè)通道的采樣數(shù)钩杰,而不是多個(gè)通道數(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)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末措左,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子怎披,更是在濱河造成了極大的恐慌,老刑警劉巖瓶摆,帶你破解...
    沈念sama閱讀 216,372評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件凉逛,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡状飞,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門书斜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)诬辈,“玉大人,你說(shuō)我怎么就攤上這事焙糟。” “怎么了样屠?”我有些...
    開封第一講書人閱讀 162,415評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵穿撮,是天一觀的道長(zhǎng)缺脉。 經(jīng)常有香客問(wèn)我悦穿,道長(zhǎng)攻礼,這世上最難降的妖魔是什么咧党? 我笑而不...
    開封第一講書人閱讀 58,157評(píng)論 1 292
  • 正文 為了忘掉前任秘蛔,我火速辦了婚禮傍衡,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘负蠕。我一直安慰自己蛙埂,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評(píng)論 6 388
  • 文/花漫 我一把揭開白布绣的。 她就那樣靜靜地躺著,像睡著了一般欲账。 火紅的嫁衣襯著肌膚如雪屡江。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,125評(píng)論 1 297
  • 那天惩嘉,我揣著相機(jī)與錄音,去河邊找鬼踢故。 笑死文黎,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的殿较。 我是一名探鬼主播,決...
    沈念sama閱讀 40,028評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼淋纲,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼劳闹!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起洽瞬,我...
    開封第一講書人閱讀 38,887評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎片任,沒(méi)想到半個(gè)月后偏友,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,310評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡对供,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評(píng)論 2 332
  • 正文 我和宋清朗相戀三年氛濒,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片鹅髓。...
    茶點(diǎn)故事閱讀 39,690評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡舞竿,死狀恐怖窿冯,靈堂內(nèi)的尸體忽然破棺而出骗奖,到底是詐尸還是另有隱情,我是刑警寧澤执桌,帶...
    沈念sama閱讀 35,411評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站芜赌,受9級(jí)特大地震影響仰挣,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜缠沈,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評(píng)論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望洲愤。 院中可真熱鬧颓芭,春花似錦、人聲如沸柬赐。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)玛界。三九已至,卻和暖如春悼吱,著一層夾襖步出監(jiān)牢的瞬間慎框,已是汗流浹背后添。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評(píng)論 1 268
  • 我被黑心中介騙來(lái)泰國(guó)打工笨枯, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留遇西,地道東北人馅精。 一個(gè)月前我還...
    沈念sama閱讀 47,693評(píng)論 2 368
  • 正文 我出身青樓粱檀,卻偏偏與公主長(zhǎng)得像洲敢,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子压彭,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容