美文网首页
【FFmpeg】PCM编码成AAC

【FFmpeg】PCM编码成AAC

作者: iOS开发之FFmpeg | 来源:发表于2021-04-26 13:52 被阅读0次

    使用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);
        }
    }
    

    相关文章

      网友评论

          本文标题:【FFmpeg】PCM编码成AAC

          本文链接:https://www.haomeiwen.com/subject/xkcerltx.html