【FFmpeg】PCM编码成AAC

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

1、命令行

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

-f PCM数据为s16le

-ar 采样率为44100

-ac 通道数为2

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

2、使用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

【学习地址】:FFmpeg/WebRTC/RTMP/NDK/Android音视频流媒体高级开发

【文章福利】:免费领取更多音视频学习资料包、大厂面试题、技术视频和学习路线图,资料包括(C/C++,Linux,FFmpeg webRTC rtmp hls rtsp ffplay srs 等等)有需要的可以点击1079654574加群领取哦~

  • 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 - 简书

猜你喜欢

转载自blog.csdn.net/irainsa/article/details/129289254