一.打开和关闭输入文件和输出文件以及判断输入文件是否读取完毕
//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);
}
二.音频编码器的初始化
//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"<
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格式进行保存:
//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."<
for(int ch=0;ch
fread(frame->data[ch]+i*data_size,1,data_size,input_file);
}
}
return 0;
}
3.编码音频采样数据
//audio_encoder_core.cpp
static int32_t encode_frame(bool flushing){
int32_t result=0;
if(!flushing){
cout<<"Send frame to encoder with pts:"<
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."<
write_pkt_to_file(pkt);
}
return 0;
}
4.写出码流数据
//io_data.cpp
void write_pkt_to_file(AVPacket* pkt){
fwrite(pkt->data,1,pkt->size,output_file);
}
5.实现编码循环
//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.关闭编码器
//audio_encoder_core.cpp
void destroy_audio_encoder(){
av_frame_free(&frame);
av_packet_free(&pkt);
avcodec_free_context(&codec_ctx);
}
7.最终main函数的实现如下:
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文件来测试效果。
手机扫一扫
移动阅读更方便
你可能感兴趣的文章