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

//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. 1.使用cookie简单实现单点登录流程

    1.动手 实现了简单使用多系统,单一位置同时登陆,以及注销 主要认证中心流程代码编写在为在sso-login包下的ViewConreoller和LoginController:各系统的用户名显示是写在 ...

  2. Python property、setter、deleter

    面向对象封装特点之一就是通过实现好的方法来访问,限制对数据的不合理访问,把对象状态私有化,仅供类的内部进行操作 下方示例,Test方法的number属性类实例的时候传递1,number是一个公开属性, ...

  3. 虚拟内存与malloc/new原理详解

    malloc malloc()函数并不是系统调用,而是 C 库里的函数,用于动态分配内存.malloc() 分配的是虚拟内存,而不是物理内存.如果分配后的虚拟内存没有被访问的话,是不会将虚拟内存映射到 ...

  4. 2023高效的mysql 随机语句 200万数据为例 用了 0.0030秒

    是的,如果数据表中有200万条记录,使用 ORDER BY RAND() 这种方式来随机选择记录会非常慢,因为 MySQL 需要对整个表进行排序,然后再返回指定数量的记录.这个过程需要消耗大量的时间和 ...

  5. 基于Mongodb分布式锁简单实现,解决定时任务并发执行问题

    前言 我们日常开发过程,会有一些定时任务的代码来统计一些系统运行数据,但是我们应用有需要部署多个实例,传统的通过配置文件来控制定时任务是否启动又太过繁琐,而且还经常出错,导致一些异常数据的产生 网上有 ...

  6. java LocalDateTime的使用

    1.LocalDateTime的基本使用 //获取当前时间 LocalDateTime localDateTime = LocalDateTime.now(); System.out.println( ...

  7. Html 设置标题栏顶部固定

    如何设置标题栏一直置顶固定显示? 只需要给标题栏所在的容器,以下设置:   position: fixed;   top: 0px;   left: 0px;   width: 100%; 位置固定在 ...

  8. KMP字符串匹配问题

    KMP算法 本文参考资料:https://www.zhihu.com/question/21923021 KMP算法是一种字符串匹配算法,可以在 \(O(n+m)\) 的时间复杂度内实现两个字符串的匹 ...

  9. 玩一玩 Ubuntu 下的 VSCode 编程

    一:背景 1. 讲故事 今天是五一的最后一天,想着长期都在 Windows 平台上做开发,准备今天换到 Ubuntu 系统上体验下,主要是想学习下 AT&T 风格的汇编,这里 Visual S ...

  10. JVM面试和学习中需要注意的部分

    内存结构 1.方法区用来存储类加载的数据,例如类的名称,方法入口 2.JVM虚拟机栈用于存储线程,包括局部变量和方法参数 3.堆内存用来存储对象 4.方法区的规范实现:永久代和元空间 5.方法区 JV ...