FFMPEG 音频封装编码

FFMPEG 4.0 for Android 准备工作

FFMPEG4.0 音频解码解封装

下面的函数方法基于最新的FFMPEG 4.0(4.X):

本文主要讲如何从一个pcm文件中拿到原始数据,用原始数据生成一个我们需要的音频格式文件,结合上一篇的FFMPEG4.0 音频解码解封装,你将能够实现音频格式转换.

从PCM文件中读取数据生成MP3格式文件。
一、初始化输出

 AVFormatContext *fmt_ctx;
    int ret = avformat_alloc_output_context2(&fmt_ctx,NULL,NULL,out_file);
 ret = avio_open(&fmt_ctx->pb,out_file,AVIO_FLAG_WRITE);

下面的变量不是必须的,里面存了输出格式的信息,包含生成的音视频编码格式。

AVOutputFormat *ofmt;
ofmt = fmt_ctx->oformat;

二、准备编码器、流,设置编码参数

encodec = avcodec_find_encoder(AV_CODEC_ID_MP3);//可通过ofmt->audio_codec得到格式
st = avformat_new_stream(fmt_ctx,encodec);
encodec_ctx = avcodec_alloc_context3(encodec);

encodec_ctx->sample_rate = 44100;
encodec_ctx->bit_rate = 64000;
encodec_ctx->sample_fmt = AV_SAMPLE_FMT_FLTP;
encodec_ctx->channel_layout = AV_CH_LAYOUT_STEREO;
encodec_ctx->channels = av_get_channel_layout_nb_channels(encodec_ctx->channel_layout);

三、打开编码器,得到一帧数据的采样数

ret = avcodec_open2(encodec_ctx,encodec,NULL);
int encode_nb_sample = encodec_ctx->frame_size;

四、初始化frame与packet

    frame = av_frame_alloc();
    pkt = av_packet_alloc();
    frame->nb_samples = encode_nb_sample;
    frame->format = encodec_ctx->sample_fmt;
    frame->channel_layout = encodec_ctx->channel_layout;

    //frame.data 需要申请的字节数
    int size = av_samples_get_buffer_size(NULL,encodec_ctx->channels,encode_nb_sample,encodec_ctx->sample_fmt,1);
    uint8_t *frame_buf = (uint8_t *) av_malloc(size);
avcodec_fill_audio_frame(frame,encodec_ctx->channels,encodec_ctx->sample_fmt,frame_buf,size,1);

上面的给frame内data分配内存的方法可以通过调用如下方法达到(sample内方法):
ret = av_frame_get_buffer(frame, 0);

重采样的数据从pcm文件中读取,这里根据生成的一帧数据的样本数计算得出转换一帧数据需要读取的样本数(pcm样本的采样率是44100)(网络上的示例这里都是错的!他们的例子在不改变采样率时没问题,一改变就有播放时间变化):
int in_nb_sample = av_rescale_rnd(frame->nb_samples,44100,encodec_ctx->sample_rate,AV_ROUND_UP);

计算转换需要的一帧数据buf大小:

int readSize = in_nb_sample * av_get_channel_layout_nb_channels(AV_CH_LAYOUT_STEREO) * av_get_bytes_per_sample(in_fmt);
char *read_buf = (char*)malloc(readSize);

五、复制参数、写头信息

    avcodec_parameters_from_context(st->codecpar,encodec_ctx);
    avformat_write_header(fmt_ctx,NULL);

六、设置重采样参数

    swc = swr_alloc();
    av_opt_set_int(swc,"in_channel_layout",AV_CH_LAYOUT_STEREO,0);
    av_opt_set_int(swc,"in_sample_rate",in_sample_rate,0);
    av_opt_set_sample_fmt(swc,"in_sample_fmt",in_fmt,0);

    av_opt_set_int(swc,"out_channel_layout",encodec_ctx->channel_layout,0);
    av_opt_set_int(swc,"out_sample_rate",encodec_ctx->sample_rate,0);
    av_opt_set_sample_fmt(swc,"out_sample_fmt",encodec_ctx->sample_fmt,0);
ret = swr_init(swc);

七、编码 (下面是一帧编码,实际编码过程应该是反复循环下面的行为,直到文件读完)
1.读取pcm文件,准备重采样的数组指针,有些做法是利用ffmpeg的接口生成frame,对frame进行data内存分配,实质都是一样:

        if (fread(read_buf, 1, readSize, infile) < 0) {
            printf("文件读取错误!\n");
            return -1;
        } else if (feof(infile)) {
            break;
        }
        //重采样源数据
        const uint8_t *indata[AV_NUM_DATA_POINTERS] = {0};
        indata[0] = (uint8_t *) read_buf;

2.重采样,设置pts

        int len = swr_convert(swc, frame->data, frame->nb_samples,indata, in_nb_sample);
        LOGV("len = %d\n",len);
        frame->pts = apts;
        apts += av_rescale_q(len,(AVRational){1,encodec_ctx->sample_rate},encodec_ctx->time_base);

3.编码(也许不用while循环。注意文件读完后还需呀send一次,frame传NULL,主要为了flush编码器)

ret = avcodec_send_frame(encodec_ctx, frame);

        while(ret >= 0) {
            LOGV("receiver\n");
            ret = avcodec_receive_packet(encodec_ctx, pkt);
            if (ret < 0) {
                av_log(NULL, AV_LOG_ERROR, "%s,ret = %d\n", "avcodec_receive_packet!error ",ret);
                break;
            }
            pkt->stream_index = st->index;
            av_log(NULL, AV_LOG_DEBUG, "第%d帧\n", i);
            pkt->pts = av_rescale_q(pkt->pts, encodec_ctx->time_base, st->time_base);
            pkt->dts = av_rescale_q(pkt->dts, encodec_ctx->time_base, st->time_base);
            pkt->duration = av_rescale_q(pkt->duration, encodec_ctx->time_base, st->time_base);
            LOGV("duration = %d,dts=%d,pts=%d\n",pkt->duration,pkt->dts,pkt->pts);
            ret = av_write_frame(fmt_ctx, pkt);
            if (ret < 0) {
                av_log(NULL, AV_LOG_ERROR, "av_write_frame error!");
            }
            av_packet_unref(pkt);
        }

4.写结束符
av_write_trailer(fmt_ctx);

猜你喜欢

转载自blog.51cto.com/4095821/2402673