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

//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"<<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格式进行保存:

//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.编码音频采样数据

//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.写出码流数据  

//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文件来测试效果。

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

  1. 视音频数据处理入门:PCM音频采样数据处理

    ===================================================== 视音频数据处理入门系列文章: 视音频数据处理入门:RGB.YUV像素数据处理 视音频数据处理 ...

  2. 视音频编解码学习工程:AAC格式分析器

    =====================================================视音频编解码学习工程系列文章列表: 视音频编解码学习工程:H.264分析器 视音频编解码学习工 ...

  3. 视音频编解码学习工程:FLV封装格式分析器

    ===================================================== 视音频编解码学习工程系列文章列表: 视音频编解码学习工程:H.264分析器 视音频编解码学习 ...

  4. 音频采样中left-or right-justified(左对齐,右对齐), I2S时钟关系

    音频采样中left-or right-justified(左对齐,右对齐), I2S时钟关系 原创 2014年02月11日 13:56:51 4951 0 0 刚刚过完春节,受假期综合症影响脑袋有点发 ...

  5. WebRTC 音频采样算法 附完整C++示例代码

    之前有大概介绍了音频采样相关的思路,详情见<简洁明了的插值音频重采样算法例子 (附完整C代码)>. 音频方面的开源项目很多很多. 最知名的莫过于谷歌开源的WebRTC, 其中的音频模块就包 ...

  6. 视音频编解码学习工程:TS封装格式分析器

    =====================================================视音频编解码学习工程系列文章列表: 视音频编解码学习工程:H.264分析器 视音频编解码学习工 ...

  7. ffmpeg 合并aac格式音频文件

    1:连接到一起 'ffmpeg - i "concat:D:\learn\audio\1.aac|D:\learn\audio\2.aac" - acodec copy D:\le ...

  8. aac格式介绍

    AAC编码后数据打包到FLV很简单. 1. FLV音频Tag格式                              字节位置    意义0x08,                       ...

  9. aac格式解析

    AAC格式有以下两种: ADIF:Audio Data Interchange Format 音频数据交换格式.这种格式的特征是可以确定的找到这个音频数据的开始,不需进行在音频数据流中间开始的解码,即 ...

  10. C#文字转换语音朗读或保存MP3、WAV等格式

    最近遇到一个需求,需要把文字转换语音,参考很多大佬写的方法,最后经过自己改造实现文字在线朗读.保存MP3.WAV等格式. //需要引用System.Speech程序集 //引用using System ...

随机推荐

  1. Springboot集成MyBatis进行开发

    引入相关的依赖 <dependency> <groupId>junit</groupId> <artifactId>junit</artifact ...

  2. CSS3-页面布局基础二——Box Model、边距折叠、内联与块标签

    一.盒子模型(Box Model) 盒子模型也有人称为框模型,HTML中的多数元素都会在浏览器中生成一个矩形的区域,每个区域包含四个组成部分,从外向内依次是:外边距(Margin).边框(Border ...

  3. Python精品书籍

    目录 Python精品书籍 * 参考资料 基础 Python编程:从入门到实践(第2版) 笨办法学_Python Coding for Kids: Python: Learn to Code with ...

  4. TypeScript 学习笔记 — 数组常见的类型转换操作记录(十四)

    获取长度 length type LengthOfTuple<T extends any[]> = T["length"]; type A = LengthOfTupl ...

  5. 关于为element Tree组件实现仿文件夹效果及右键菜单

    <template> <div class="custom-tree-container" @contextmenu.native="handlePas ...

  6. TOF和结构光

    文章目录 TOF和结构光 一.ToF 二.结构光 三.测量距离.分辨率.开发周期的对比 TOF和结构光 一.ToF ToF(Time of Flight)飞行时间 字面理解就是通过光的飞行时间来计算距 ...

  7. CTFShow 反序列化 Web 255-266

    CTFShow 反序列化 255-266 漏洞原理 未队用户输入的序列化字符串进行检测,导致攻击者可以控制反序列化过程,从而导致代码执行,SQL注入,目录遍历等后果. 触发条件 unserialize ...

  8. Vue3项目的打包运行

    一.项目打包(vite创建的项目) 执行以下这条命令对项目进行打包 npm run build 生成dist文件夹,进入dist文件夹下的index.html文件,然后右键选择Open with Li ...

  9. 2023-03-26:给定一个二维数组matrix, 每个格子都是正数,每个格子都和上、下、左、右相邻。 你可以从任何一个格子出发,走向相邻的格子, 把沿途的数字乘起来,希望得到的最终数字中,结尾的0

    2023-03-26:给定一个二维数组matrix, 每个格子都是正数,每个格子都和上.下.左.右相邻. 你可以从任何一个格子出发,走向相邻的格子, 把沿途的数字乘起来,希望得到的最终数字中,结尾的0 ...

  10. 2023-02-24:请用go语言调用ffmpeg,解码mp4文件并保存为YUV420SP格式文件,采用YUV420P转YUV420SP的方式。

    2023-02-24:请用go语言调用ffmpeg,解码mp4文件并保存为YUV420SP格式文件,采用YUV420P转YUV420SP的方式. 答案2023-02-24: 使用 github.com ...