FFmpeg音频的编码流程详解及demo

        本文主要讲解FFmpeg的音频编码具体流程,API使用。最后再以一个非常简单的demo演示将一个音频原始数据pcm文件编码为AAC格式的音频文件。 本文主要基于FFmpeg音频编码新接口。

一、FFmpeg音频编码API调用流程图

        音频编码的API调用流程图如下:

          API接口简单大体讲解如下:

av_register_all():注册FFmpeg所有编解码器。
 
avformat_alloc_context():初始化输出码流的AVFormatContext。
 
avio_open():打开输出文件。
 
av_new_stream():创建输出码流的AVStream。
 
avcodec_find_encoder():查找编码器。
 
avcodec_open2():打开编码器。
 
avformat_write_header():写文件头(对于某些没有文件头的封装格式,不需要此函数。比如说MPEG2TS)。
 
avcodec_send_frame():编码核心接口新接口,发送一帧音频给编码器。即是AVFrame(存储PCM数据)。
 
avcodec_receive_packet():编码核心接口新接口,接收编码器编码后的一帧音频,AVPacket(存储AAC等格式的数据)。
 
av_write_frame():将编码后的音频数据写入文件。
 
flush_encoder():输入的像素数据读取完成后调用此函数。用于输出编码器中剩余的AVPacket。
 
av_write_trailer():写文件尾(对于某些没有文件头的封装格式,不需要此函数。比如说MPEG2TS)。

        从上面可以看到,其实音频的编码流程基本与视频的编码流程一致。

二、音频编码过程API调用流程

1、注册各大组件

        这一步是ffmpeg的任何程序的第一步都是需要先注册ffmpeg相关的各大组件的:

    //注册各大组件
    av_register_all();

2、打开pcm原始数据文件

        这里除了打开pcm原始数据文件外,还定义了一个framenum变量,这个是为了简单验证需要,我们只编码framenum帧,不然若pcm太大,编码太久,可以指定自己想要的多少帧来做验证即可,赋值framenum。

    FILE *in_file=NULL;	                        //Raw PCM data
    in_file= fopen(inputPath, "rb");

    int framenum=10000;                          //Audio frame number

3、初始化输出码流的AVFormatContext

        与视频一样,有两种方式,这里我们用的方式一。

        方式一:

    //方式1
    pFormatCtx = avformat_alloc_context();
    //Guess Format
    fmt = av_guess_format(NULL, outputPath, NULL);
    pFormatCtx->oformat = fmt;

        方式二:

    //方式2
    avformat_alloc_output_context2(&pFormatCtx, NULL, NULL, out_file);
    fmt = pFormatCtx->oformat;

4、打开输出文件

    //Open output
    if (avio_open(&pFormatCtx->pb,outputPath, AVIO_FLAG_READ_WRITE) < 0){
        LOGE("Failed to open output file!");
        return false;
    }

5、创建输出码流的AVStream

    audio_st = avformat_new_stream(pFormatCtx, 0);
    if (audio_st==NULL){
        return false;
    }

6、查找编码器并打开

        查找编码器之前,需要先指定配置编码器的一些参数。像音频的话,比如它的采样率,声道数,编码格式等,需要配置输入的PCM的原始数据的参数,也需要配置编码后输出的数据的参数。如下,我们这里参数的设定是输入的PCM是44100,FLT,STEREO,输出AAC是44100,FLTP,STEREO:

    //{ 这个参数的设定是输入的PCM是44100,FLT,STEREO,输出AAC是44100,FLTP,STEREO
    pCodecCtx = audio_st->codec;
    pCodecCtx->codec_id = fmt->audio_codec;
    pCodecCtx->codec_type = AVMEDIA_TYPE_AUDIO;
    pCodecCtx->sample_fmt = AV_SAMPLE_FMT_FLTP;
    pCodecCtx->sample_rate= 44100;
    pCodecCtx->channel_layout=AV_CH_LAYOUT_STEREO;
    pCodecCtx->channels = av_get_channel_layout_nb_channels(pCodecCtx->channel_layout);
    pCodecCtx->bit_rate = 64000;
    pCodecCtx->profile=FF_PROFILE_AAC_MAIN;
    pCodecCtx->strict_std_compliance = FF_COMPLIANCE_EXPERIMENTAL;
    //Show some information
    av_dump_format(pFormatCtx, 0, outputPath, 1);

    swr = swr_alloc();
    av_opt_set_int(swr, "in_channel_layout",  AV_CH_LAYOUT_STEREO, 0);
    av_opt_set_int(swr, "out_channel_layout", AV_CH_LAYOUT_STEREO,  0);
    av_opt_set_int(swr, "in_sample_rate",     44100, 0);
    av_opt_set_int(swr, "out_sample_rate",    44100, 0);
    av_opt_set_sample_fmt(swr, "in_sample_fmt",  AV_SAMPLE_FMT_FLT, 0);
    av_opt_set_sample_fmt(swr, "out_sample_fmt", AV_SAMPLE_FMT_FLTP,  0);
    swr_init(swr);
    //}

        这里中间我们也使用了av_dump_format()这个API,打印出配置后的参数格式,方便调试查看。

    //Show some information
    av_dump_format(pFormatCtx, 0, outputPath, 1);

        参数配置好后调用avcodec_find_encoder寻找编码器

    pCodec = avcodec_find_encoder(fmt->audio_codec);
    if (!pCodec){
        LOGE("Can not find encoder!");
        return false;
    }

        最后打开编码器

    ret = avcodec_open2(pCodecCtx, pCodec,NULL);
    if (ret < 0){
        LOGE("Failed to open encoder!");
        return false;
    }

7、写文件头

        对于某些没有文件头的封装格式,不需要此函数。

    //Write Header
    avformat_write_header(pFormatCtx,NULL);

8、开始音频编码

        准备就绪后,我们就可以开始编码。音频编码的新接口同样是通过发送给编码器,然后接收的方式进行编码。编码的核心代码如下:

        ret = avcodec_send_frame(pCodecCtx, pFrame);
        if (ret < 0){
            LOGE("Error sending a frame for encoding");
            return false;
        }

        while (ret >= 0){
            ret = avcodec_receive_packet(pCodecCtx, pkt);
            if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF){
                break;
            }
            else if (ret < 0){
                LOGE("Error during encoding\n");
                break;
            }

            ret = av_write_frame(pFormatCtx, pkt);
            av_packet_unref(pkt);
        }

9、读取PCM原数据文件,编码,写入文件

        我们一帧一帧地读取PCM原数据文件,然后进行编码,拿到编码后的数据通过av_write_frame写入文件。

ret = av_write_frame(pFormatCtx, pkt);

        同时为了确保数据齐全,获取到最后数据的时候,需要将缓冲区中的数据清理出来

    //Flush Encoder
    ret = audio_flush_encoder(pFormatCtx,0);
    if (ret < 0) {
        LOGE("Flushing encoder failed");
        return false;
    }

        完整的编码,写入文件流程如下:

for (i=0; i<framenum; i++){
        //Read PCM data
        if ((ret = fread(frame_buf, 1, size, in_file)) <= 0){
            LOGE("fread pcm raw data failed\n");
            getc(in_file);
            if(feof(in_file)) {
                LOGE(" -> Because this is the file feof!");
                break;
            }
            return false;
        }

        int count=swr_convert(swr, outs,len*4,(const uint8_t **)&frame_buf,len/4);//len 为4096
        pFrame->data[0] =(uint8_t*)outs[0];//audioFrame 是VFrame
        pFrame->data[1] =(uint8_t*)outs[1];

        //pFrame->data[0] = frame_buf;  //PCM Data
        pFrame->pts=i*100;

        ret = avcodec_send_frame(pCodecCtx, pFrame);
        if (ret < 0){
            LOGE("Error sending a frame for encoding");
            return false;
        }

        while (ret >= 0){
            ret = avcodec_receive_packet(pCodecCtx, pkt);
            if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF){
                break;
            }
            else if (ret < 0){
                LOGE("Error during encoding\n");
                break;
            }

            ret = av_write_frame(pFormatCtx, pkt);
            av_packet_unref(pkt);
        }
    }

    //Flush Encoder
    ret = audio_flush_encoder(pFormatCtx,0);
    if (ret < 0) {
        LOGE("Flushing encoder failed");
        return false;
    }

        其中audio_flush_encoder其实与视频编码的flush_encoder一样的作用:

int audio_flush_encoder(AVFormatContext *fmt_ctx,unsigned int stream_index){
    int ret;
    int got_frame;
    AVPacket enc_pkt;
    if (!(fmt_ctx->streams[stream_index]->codec->codec->capabilities &
          AV_CODEC_CAP_DELAY))
        return 0;
    while (1) {
        enc_pkt.data = NULL;
        enc_pkt.size = 0;
        av_init_packet(&enc_pkt);
        ret = avcodec_encode_audio2 (fmt_ctx->streams[stream_index]->codec, &enc_pkt,
                                     NULL, &got_frame);
        av_frame_free(NULL);
        if (ret < 0)
            break;
        if (!got_frame){
            ret=0;
            break;
        }
        LOGE("Flush Encoder: Succeed to encode 1 frame!\tsize:%5d\n",enc_pkt.size);
        /* mux encoded frame */
        ret = av_write_frame(fmt_ctx, &enc_pkt);
        if (ret < 0)
            break;
    }
    return ret;
}

10、写文件尾

         对于某些没有文件头的封装格式,不需要此步。

    //Write file trailer
    av_write_trailer(pFormatCtx);

11、收尾,释放相关资源

    //Clean
    if (audio_st){
        avcodec_close(audio_st->codec);
        av_free(pFrame);
        av_free(frame_buf);
    }
    avio_close(pFormatCtx->pb);
    avformat_free_context(pFormatCtx);

    fclose(in_file);

        以上便是pcm编码为AAC格式的整个流程,可以看到与视频非常相似。若是输出其他编码文件,步骤类似,但是需要调整打开编码器那一步的相关参数。若要做成通用性demo,可以在这一步加上对编码格式的判断,从而进行参数的选择。

三、demo运行

        demo中指定了编码的输入文件和输出文件,输入文件是/sdcar/audioIn.pcm,输出文件是/sdcard/audioOut.aac,需要改的话,需要在这里改:

@Override
public void onClick(View view) {
	runOnUiThread(new Runnable() {
		@Override
		public void run() {
			String PATH = Environment.getExternalStorageDirectory().getPath();
			//音频编码
			String input = PATH + File.separator + "audioIn.pcm";
			String output = PATH + File.separator + "audioOut.aac";
			encode_audio(input,output);
			Toast.makeText(MainActivity.this, "音频编码完成,请自行从手机中拉取aac文件", Toast.LENGTH_SHORT).show();
		}
	});
}

        测试用的pcm数据文件,可以通过一些设备进行一些录音获取原始数据。或者通过上篇解码的demo先把一个mp3文件给解码为pcm,拿来用。

        运行demo,截图如下:

        点击“START AUDIO ENCODE”按钮,开始编码。编码过程可能比较久,界面不会有变化,可以通过看log看过程:

        编码结束后,界面上会有提示词:“音频编码完成,请自行从手机中拉取aac文件”,同时log也能看到"--- audio encode finished ---"

        然后我们看到/sdcard/下已经有编码后的文件,看到编码后的文件比原始pcm文件就要小很多:

        我们把它拉取到电脑上,可以看到aac格式的文件,是可以直接用普通音乐播放器播放的,如下,可以打开播放,若音乐播放正常,说明编码过程没有问题。

        完整例子已经放到github上,如下

https://github.com/weekend-y/FFmpeg_Android_Demo/tree/master/demo7_audioEncode

猜你喜欢

转载自blog.csdn.net/weekend_y45/article/details/125210318