前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >如何将PCM格式的原始音频采样数据编码为MP3格式或AAC格式的音频文件?

如何将PCM格式的原始音频采样数据编码为MP3格式或AAC格式的音频文件?

作者头像
故乡的樱花开了
发布2023-10-22 13:48:07
3710
发布2023-10-22 13:48:07
举报
文章被收录于专栏:Android技术专栏Android技术专栏

一.打开和关闭输入文件和输出文件以及判断输入文件是否读取完毕

代码语言:javascript
复制
//io_data.cpp
static FILE* input_file= nullptr;
static FILE* output_file= nullptr;
int32_t open_input_output_files(const char* input_name,const char* output_name){
    if(strlen(input_name)==0||strlen(output_name)==0){
        cout<<"Error:empty input or output file name."<<endl;
        return -1;
    }
    close_input_output_files();
    input_file=fopen(input_name,"rb");//rb:读取一个二进制文件,该文件必须存在
    if(input_file==nullptr){
        cerr<<"Error:failed to open input file."<<endl;
        return -1;
    }
    output_file=fopen(output_name,"wb");//wb:打开或新建一个二进制文件,只允许写
    if(output_file== nullptr){
        cout<<"Error:failed to open output file."<<endl;
        return -1;
    }
    return 0;
}
void close_input_output_files(){
    if(input_file!= nullptr){
        fclose(input_file);
        input_file= nullptr;
    }
    if(output_file!= nullptr){
        fclose(output_file);
        output_file= nullptr;
    }
}
int32_t end_of_input_file(){
    return feof(input_file);
}

二.音频编码器的初始化

代码语言:javascript
复制
//audio_encoder_core.cpp
static const AVCodec* codec= nullptr;
static AVCodecContext* codec_ctx= nullptr;
static AVFrame* frame= nullptr;
static AVPacket* pkt= nullptr;
static enum AVCodecID audio_codec_id;
int32_t init_audio_encoder(const char* codec_name){
    if(strcasecmp(codec_name,"MP3")==0){
        audio_codec_id=AV_CODEC_ID_MP3;
        cout<<"Select codec id:MP3"<<endl;
    }
    else if(strcasecmp(codec_name,"AAC")==0){
        audio_codec_id=AV_CODEC_ID_AAC;
        cout<<"Select codec id:AAC"<<endl;
    }
    else{
        cerr<<"Error:invalid audio format."<<endl;
        return -1;
    }
    codec=avcodec_find_encoder(audio_codec_id);
    if(!codec){
        cerr<<"Error:could not find codec."<<endl;
        return -1;
    }
    codec_ctx=avcodec_alloc_context3(codec);
    if(!codec_ctx){
        cerr<<"Error:could not alloc codec_ctx."<<endl;
        return -1;
    }
    //设置音频编码器的参数
    codec_ctx->bit_rate=128000;
    codec_ctx->sample_fmt=AV_SAMPLE_FMT_FLTP;
    codec_ctx->sample_rate=44100;
    codec_ctx->channel_layout=AV_CH_LAYOUT_STEREO;
    codec_ctx->channels=2;

    int32_t result=avcodec_open2(codec_ctx,codec, nullptr);
    if(result<0){
        cerr<<"Error:could not open codec."<<endl;
        return -1;
    }
    frame=av_frame_alloc();
    if(!frame){
        cerr<<"Error:could not alloc frame."<<endl;
        return -1;
    }
    frame->nb_samples=codec_ctx->frame_size;//采样点数量
    frame->format=codec_ctx->sample_fmt;
    frame->channel_layout=codec_ctx->channel_layout;
    result= av_frame_get_buffer(frame,0);
    if(result<0){
        cerr<<"Error:AVFrame could not get buffer."<<endl;
        return -1;
    }
    pkt=av_packet_alloc();
    if(!pkt){
        cerr<<"Error:could not alloc packet."<<endl;
        return -1;
    }
    return 0;
}

三.编码循环体

  1.PCM文件的存储结构

    音频采样格式可以分为packed和planar两类。以packed格式保存的采样数据,各声道间按照采样值交替存储;以planar格式保存的采样数据,各个采样值按照不同声道连续存储

    下面以8bit为例展示planar和packed格式是如何保存音频采样数据的:

packed:

左声道0

右声道0

左声道1

右声道1

左声道2

右声道2

左声道3

右声道3

planar:

左声道0

左声道1

左声道2

左声道3

右声道0

右声道1

右声道2

右声道3

  2.读取PCM音频采样数据

    由于我们代码里设置了采样格式为fltp,即planar格式,而输入的PCM音频采样数据是packed格式的,因此我们需要将packed格式转化为planar格式进行保存:

代码语言:javascript
复制
//io_data.cpp
int32_t read_pcm_to_frame(AVFrame* frame,AVCodecContext* codec_ctx){
    int data_size= av_get_bytes_per_sample(codec_ctx->sample_fmt);
    if(data_size<0){
        cerr<<"Error:Failed to calculate data size."<<endl;
        return -1;
    }
    for(int i=0;i<frame->nb_samples;i++){
        for(int ch=0;ch<codec_ctx->channels;ch++){
            fread(frame->data[ch]+i*data_size,1,data_size,input_file);
        }
    }
    return 0;
}

  3.编码音频采样数据

代码语言:javascript
复制
//audio_encoder_core.cpp
static int32_t encode_frame(bool flushing){
    int32_t result=0;
    if(!flushing){
        cout<<"Send frame to encoder with pts:"<<frame->pts<<endl;
    }
    result=avcodec_send_frame(codec_ctx,flushing? nullptr:frame);
    if(result<0){
        cerr<<"Error:avcodec_send_frame failed."<<endl;
        return result;
    }
    while(result>=0){
        result= avcodec_receive_packet(codec_ctx,pkt);
        if(result==AVERROR(EAGAIN)||result==AVERROR_EOF){//尚未完成对新一帧的编码,要传入后续帧或编码器已完全输出内部缓存的码流
            return 1;
        }
        else if(result<0){
            cerr<<"Error:avcodec_receive_packet failed."<<endl;
            return result;
        }
        if(flushing){
            cout<<"Flushing:";
        }
        cout<<"Got encoded package with dts:"<<pkt->dts<<",pts:"<<pkt->pts<<", "<<endl;
        write_pkt_to_file(pkt);
    }
    return 0;
}

  4.写出码流数据  

代码语言:javascript
复制
//io_data.cpp
void write_pkt_to_file(AVPacket* pkt){
    fwrite(pkt->data,1,pkt->size,output_file);
}

  5.实现编码循环

代码语言:javascript
复制
//audio_encoder_core.cpp
int32_t audio_encoding(){
    int32_t result=0;
    while(!end_of_input_file()){
        result= read_pcm_to_frame(frame,codec_ctx);
        if(result<0){
            cerr<<"Error:read_pcm_to_frame failed."<<endl;
            return -1;
        }
        result=encode_frame(false);
        if(result<0){
            cerr<<"Error:encode_frame failed."<<endl;
            return result;
        }
    }
    result=encode_frame(true);//刷新缓存区
    if(result<0){
        cerr<<"Error:flushing failed."<<endl;
        return result;
    }
    return 0;
}

  6.关闭编码器

代码语言:javascript
复制
//audio_encoder_core.cpp
void destroy_audio_encoder(){
    av_frame_free(&frame);
    av_packet_free(&pkt);
    avcodec_free_context(&codec_ctx);
}

  7.最终main函数的实现如下:

代码语言:javascript
复制
int main(){
    const char* input_file_name="../input.pcm";
    const char* output_file_name="../output.mp3";
    const char* codec_name="MP3";
    int32_t result= open_input_output_files(input_file_name,output_file_name);
    if(result<0){
        return result;
    }
    result=init_audio_encoder(codec_name);
    if(result<0){
        return result;
    }
    result=audio_encoding();
    if(result<0){
        return result;
    }
    destroy_audio_encoder();
    close_input_output_files();
    return 0;
}

  与视频文件类似,可以使用ffplay播放输出的.mp3文件来测试效果。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2023-06-20,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档