从本地⽂件读取PCM数据进⾏AAC格式编码,然后将编码后的AAC数据存储到本地⽂件。
- //播放格式为f32le,双声道,采样频率48000Hz的PCM数据
- ffplay -f f32le -ac 2 -ar 48000 pcm_audio
使⽤ffmpeg -formats命令,获取ffmpeg⽀持的⾳视频格式,其中我们可以找到⽀持的PCM格式。
ffmpeg -formats | findstr PCM
- DE alaw PCM A-law
- DE f32be PCM 32-bit floating-point big-endian
- DE f32le PCM 32-bit floating-point little-endian
- DE f64be PCM 64-bit floating-point big-endian
- DE f64le PCM 64-bit floating-point little-endian
- DE mulaw PCM mu-law
- DE s16be PCM signed 16-bit big-endian
- DE s16le PCM signed 16-bit little-endian
- DE s24be PCM signed 24-bit big-endian
- DE s24le PCM signed 24-bit little-endian
- DE s32be PCM signed 32-bit big-endian
- DE s32le PCM signed 32-bit little-endian
- DE s8 PCM signed 8-bit
- DE u16be PCM unsigned 16-bit big-endian
- DE u16le PCM unsigned 16-bit little-endian
- DE u24be PCM unsigned 24-bit big-endian
- DE u24le PCM unsigned 24-bit little-endian
- DE u32be PCM unsigned 32-bit big-endian
- DE u32le PCM unsigned 32-bit little-endian
- DE u8 PCM unsigned 8-bit
- DE vidc PCM Archimedes VIDC
- 1 AV_SAMPLE_FMT_U8, ///< unsigned 8 bits
- 2 AV_SAMPLE_FMT_S16, ///< signed 16 bits
- 3 AV_SAMPLE_FMT_S32, ///< signed 32 bits
- 4 AV_SAMPLE_FMT_FLT, ///< float
- 5 AV_SAMPLE_FMT_DBL, ///< double
只能保存在AVFrame的uint8_t *data[0]
⾳频保持格式如下:
LRLRLR ...
- 1 AV_SAMPLE_FMT_U8P, ///< unsigned 8 bits, planar
- 2 AV_SAMPLE_FMT_S16P, ///< signed 16 bits, planar
- 3 AV_SAMPLE_FMT_S32P, ///< signed 32 bits, planar
- 4 AV_SAMPLE_FMT_FLTP, ///< float, planar
- 5 AV_SAMPLE_FMT_DBLP, ///< double, planar
- 6 AV_SAMPLE_FMT_S64, ///< signed 64 bits
- 7 AV_SAMPLE_FMT_S64P, ///< signed 64 bits, planar
-
- //编码 代码。
- //音频的格式,
- //从本地⽂件读取PCM数据进⾏AAC格式编码,然后将编码后的AAC数据存储到本地⽂件。
-
- #include <iostream>
-
- extern "C" {
- #include "libavcodec/avcodec.h"
- #include "libavformat/avformat.h"
- #include "libavutil/fifo.h"
- #include "libavutil/audio_fifo.h"
- #include "libswresample/swresample.h"
- #include "libavutil/opt.h"
-
- }
- using namespace std;
-
- static void get_adts_header(AVCodecContext *ctx, uint8_t *adts_header, int aac_length)
- {
- uint8_t freq_idx = 0; //0: 96000 Hz 3: 48000 Hz 4: 44100 Hz
- switch (ctx->sample_rate) {
- case 96000: freq_idx = 0; break;
- case 88200: freq_idx = 1; break;
- case 64000: freq_idx = 2; break;
- case 48000: freq_idx = 3; break;
- case 44100: freq_idx = 4; break;
- case 32000: freq_idx = 5; break;
- case 24000: freq_idx = 6; break;
- case 22050: freq_idx = 7; break;
- case 16000: freq_idx = 8; break;
- case 12000: freq_idx = 9; break;
- case 11025: freq_idx = 10; break;
- case 8000: freq_idx = 11; break;
- case 7350: freq_idx = 12; break;
- default: freq_idx = 4; break;
- }
- uint8_t chanCfg = ctx->channels;
- uint32_t frame_length = aac_length + 7;
- adts_header[0] = 0xFF;
- adts_header[1] = 0xF1;
- adts_header[2] = ((ctx->profile) << 6) + (freq_idx << 2) + (chanCfg >> 2);
- adts_header[3] = (((chanCfg & 3) << 6) + (frame_length >> 11));
- adts_header[4] = ((frame_length & 0x7FF) >> 3);
- adts_header[5] = (((frame_length & 7) << 5) + 0x1F);
- adts_header[6] = 0xFC;
- }
-
- static int encode(AVCodecContext *ctx, AVFrame *frame, AVPacket *pkt, FILE *output)
- {
- int ret;
-
- /* send the frame for encoding */
- ret = avcodec_send_frame(ctx, frame);
- if (ret < 0) {
- fprintf(stderr, "Error sending the frame to the encoder\n");
- return -1;
- }
-
- /* read all the available output packets (in general there may be any number of them */
- // 编码和解码都是一样的,都是send 1次,然后receive多次, 直到AVERROR(EAGAIN)或者AVERROR_EOF
- while (ret >= 0) {
- ret = avcodec_receive_packet(ctx, pkt);
- if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
- return 0;
- } else if (ret < 0) {
- fprintf(stderr, "Error encoding audio frame\n");
- return -1;
- }
-
- size_t len = 0;
- printf("ctx->flags:0x%x & AV_CODEC_FLAG_GLOBAL_HEADER:0x%x, name:%s\n",ctx->flags, ctx->flags & AV_CODEC_FLAG_GLOBAL_HEADER, ctx->codec->name);
- if((ctx->flags & AV_CODEC_FLAG_GLOBAL_HEADER)) {
- // 需要额外的adts header写入
- uint8_t aac_header[7];
- get_adts_header(ctx, aac_header, pkt->size);
- len = fwrite(aac_header, 1, 7, output);
- if(len != 7) {
- fprintf(stderr, "fwrite aac_header failed\n");
- return -1;
- }
- }
- len = fwrite(pkt->data, 1, pkt->size, output);
- if(len != pkt->size) {
- fprintf(stderr, "fwrite aac data failed\n");
- return -1;
- }
- /* 是否需要释放数据? avcodec_receive_packet第一个调用的就是 av_packet_unref
- * 所以我们不用手动去释放,这里有个问题,不能将pkt直接插入到队列,因为编码器会释放数据
- * 可以新分配一个pkt, 然后使用av_packet_move_ref转移pkt对应的buffer
- */
- av_packet_unref(pkt);
- }
- av_frame_unref(frame);
- return -1;
- }
-
-
-
- /*
- * 这里只支持2通道的转换
- */
- void f32le_convert_to_fltp(float *f32le, float *fltp, int nb_samples) {
- float *fltp_l = fltp; // 左通道
- float *fltp_r = fltp + nb_samples; // 右通道
- for(int i = 0; i < nb_samples; i++) {
- fltp_l[i] = f32le[i*2]; // 0 1 - 2 3
- fltp_r[i] = f32le[i*2+1]; // 可以尝试注释左声道或者右声道听听声音
- }
- }
-
- /* 检测该编码器是否支持该采样格式 */
- static int check_sample_fmt(const AVCodec *codec, enum AVSampleFormat sample_fmt)
- {
- const enum AVSampleFormat *p = codec->sample_fmts;
-
- while (*p != AV_SAMPLE_FMT_NONE) { // 通过AV_SAMPLE_FMT_NONE(-1)作为结束符
- printf("%s sample_fmt support %d\n",codec->name,*p);
- if (*p == sample_fmt)
- return 1;
- p++;
- }
- return 0;
- }
-
- /* 检测该编码器是否支持该采样率 */
- static int check_sample_rate(const AVCodec *codec, const int sample_rate)
- {
- const int *p = codec->supported_samplerates;
- while (*p != 0) {// 0作为退出条件,比如libfdk-aacenc.c的aac_sample_rates
- printf("%s support %dhz\n", codec->name, *p);
- if (*p == sample_rate)
- return 1;
- p++;
- }
- return 0;
- }
-
- /* 检测该编码器是否支持该采样率, 该函数只是作参考 */
- static int check_channel_layout(const AVCodec *codec, AVChannelLayout ch_layout)
- {
- // 不是每个codec都给出支持的 ch_layout,实验测试,ffmpeg自带的aac 编码器中的 ch_layout 就是nullptr
- const AVChannelLayout * avchannelLayout= codec->ch_layouts;
- if(!avchannelLayout) {
- printf("the codec %s no set channel_layouts\n", codec->name);
- return 1;
- }
- while ((*avchannelLayout).nb_channels != 0) { // 0作为退出条件,比如libfdk-aacenc.c的aac_channel_layout
- printf("%s support channel_layout %d\n", codec->name, (*avchannelLayout).nb_channels);
- if ((*avchannelLayout).nb_channels == ch_layout.nb_channels)
- return 1;
- avchannelLayout++;
- }
- return 0;
- }
-
- void printfAVCodec(const AVCodec *avcodec){
- if(avcodec==nullptr){
- cout<<"printfAVCodec error avcodec==nullptr"<<endl;
- return ;
- }
- cout << "avcodec->name = " << avcodec->name << endl;
- cout << "avcodec->long_name = " << avcodec->long_name << endl;
-
- //该编解码器的 类型,enum AVMediaType type; 类似 AVMEDIA_TYPE_VIDEO,AVMEDIA_TYPE_AUDIO
- cout << "avcodec->type = " << avcodec->type << endl;
-
- //enum AVCodecID id; 类似 AV_CODEC_ID_AAC,AV_CODEC_ID_H264 , AAC 对应的值是十进制是86018,十六进制是15002
- cout << "avcodec->id = " << avcodec->id << endl;
-
-
- /**
- * Codec capabilities.
- * see AV_CODEC_CAP_*
- * int capabilities;
- * AAC 作为 encoder 的值是98,对应的二进制是 0110 0010,对应的如下的三个值 或 起来
- * #define AV_CODEC_CAP_DR1 (1 << 1)
- #define AV_CODEC_CAP_DELAY (1 << 5)
- #define AV_CODEC_CAP_SMALL_LAST_FRAME (1 << 6)
- */
- cout << "avcodec->capabilities = " << avcodec->capabilities << endl;
-
- ///< maximum value for lowres supported by the decoder
- /// 视频专用 解码器支持的低分辨率的最大值
- // uint8_t max_lowres;
- cout << "avcodec->max_lowres = " << avcodec->max_lowres << endl;
-
-
-
- ///< array of supported framerates, or NULL if any, array is terminated by {0,0}
- ///const AVRational *supported_framerates;
- ///支持的帧率(仅视频), 类似 每秒25张图片
- if(avcodec->supported_framerates == nullptr){
- cout<<"avcodec->supported_framerates = nullptr"<<endl;
- } else {
- cout<<"avcodec->supported_framerates != nullptr"<<endl;
- const AVRational * avr = avcodec->supported_framerates;
- int i =0;
- while(1){
- if(avr[i].den ==0 && avr[i].num == 0){
- break;
- }
- cout<<"avr["<<i <<"].den = "<< avr[i].den << " avr[" << i << "].num = " << avr[i].num << endl;
- ++i;
- }
- }
-
-
- ///< array of supported pixel formats, or NULL if unknown, array is terminated by -1
- ///const enum AVPixelFormat *pix_fmts;
- /// 支持的像素格式(仅视频),类似 AV_PIX_FMT_YUV420P
- if(avcodec->pix_fmts == nullptr){
- cout<<"avcodec->pix_fmts = nullptr"<<endl;
- }else{
- cout<<"avcodec->pix_fmts != nullptr"<<endl;
- const enum AVPixelFormat * avpixelformat = avcodec->pix_fmts;
- int i =0;
- while(1){
- if(avpixelformat[i] == -1){
- break;
- }else{
- cout<<"avpixelformat["<<i<<"] = avpixelformat[i] " <<endl;
- ++i;
- }
- }
- }
-
-
-
- ///< array of supported audio samplerates, or NULL if unknown, array is terminated by 0
- /// const int *supported_samplerates;
- /// 支持的采样率(仅音频)41000,48000
- if(avcodec->supported_samplerates == nullptr){
- cout<<"avcodec->supported_samplerates = nullptr"<<endl;
- }else{
- cout<<"avcodec->supported_samplerates != nullptr"<<endl;
- const int * support_samplerates = avcodec->supported_samplerates;
- int i =0;
- while(1){
- if(support_samplerates[i] == 0){
- break;
- }else{
- cout<<"support_samplerates["<<i<<"] = " << support_samplerates[i]<<endl;
- ++i;
- }
- }
- }
-
-
- ///支持的采样格式(仅音频),类似 AV_SAMPLE_FMT_S16,FFMpeg 自带的AAC 只支持 AV_SAMPLE_FMT_FLTP,对应的十进制的值是8
- ///< array of supported sample formats, or NULL if unknown, array is terminated by -1
- /// const enum AVSampleFormat *sample_fmts;
-
- if(avcodec->sample_fmts == nullptr){
- cout<<"avcodec->sample_fmts = nullptr"<<endl;
- }else{
- cout<<"avcodec->sample_fmts != nullptr"<<endl;
- const enum AVSampleFormat *sample_fmts = avcodec->sample_fmts;
- int i =0;
- while(1){
- if(sample_fmts[i] == -1){
- break;
- }else{
- cout<<"sample_fmts["<<i<<"] = " << sample_fmts[i]<<endl;
- ++i;
- }
- }
- }
-
-
-
- //#if FF_API_OLD_CHANNEL_LAYOUT
- // /**
- // * @deprecated use ch_layouts instead
- // */
- // attribute_deprecated
- // const uint64_t *channel_layouts; ///< array of support channel layouts, or NULL if unknown. array is terminated by 0
- //#endif
-
- //该编码器支持的声道数量。已经不再使用。可以使用 const AVChannelLayout *ch_layouts; 替代
- const uint64_t *channel_layouts = avcodec->channel_layouts;
-
- if(channel_layouts == nullptr){
- cout<<"channel_layouts = nullptr"<<endl; //在ffmepg 6.0的时候,使用编码器ffmpeg自带的AAC,这块为nullptr。这不合理。这里baidu了一下,发现 如下的说法: // 不是每个codec都给出支持的channel_layout
- }else{
- cout<<"channel_layouts != nullptr"<<endl;
- int i = 0;
- while(1){
- if(channel_layouts[i] == 0){
- break;
- }
- cout<<"channel_layouts[<<" << i << "] = " << channel_layouts[i] << endl;
- ++i;
- }
- }
-
- ///这里为了方便测试 ,将已经废弃的const uint64_t *channel_layouts; 和 const AVChannelLayout *ch_layouts;对比
- /**
- * Array of supported channel layouts, terminated with a zeroed layout.
- */
- ///const AVChannelLayout *ch_layouts;
- const AVChannelLayout *ch_layouts = avcodec->ch_layouts;
- if(ch_layouts == nullptr){
- cout<<"ch_layouts = nullptr"<<endl; //在ffmepg 6.0的时候,使用编码器ffmpeg自带的AAC,这块为nullptr。这不合理。 baidu说明如下: // 不是每个codec都给出支持的channel_layout
-
- }else{
- cout<<"ch_layouts != nullptr"<<endl;
- int i = 0;
- while(1){
- if(ch_layouts[i].nb_channels == 0 ){
- break;
- }
- cout<<"ch_layouts[<<" << i << "].nb_channels = " << ch_layouts[i].nb_channels << endl;
- ++i;
- }
- }
-
-
-
-
- ///< AVClass for the private context
- /// const AVClass *priv_class;
- /// AVClass最主要的作用就是给结构体(例如AVFormatContext等)增加AVOption功能的支持。换句话说AVClass就是AVOption和目标结构体之间的“桥梁”。AVClass要求必须声明为目标结构体的第一个变量。
- const AVClass *priv_class = avcodec->priv_class;
- if(priv_class==nullptr){
- cout << "priv_class = nullptr" << endl;
- }else {
- cout << "priv_class != nullptr" << endl;
- cout << "priv_class->option->name = " << priv_class->option->name << endl;
-
- }
-
-
- ///< array of recognized profiles, or NULL if unknown, array is terminated by {AV_PROFILE_UNKNOWN}
- /// const AVProfile *profiles;
- /// 如果非NULL,则为此编解码器识别的配置文件数组。
- /// 该结构描述了由AVCodecID描述的单个编解码器的属性。
-
- const AVProfile *profiles = avcodec->profiles;
- if(profiles == nullptr){
- cout<<"profiles = nullptr"<<endl;
- }else{
- cout<<"profiles != nullptr"<<endl;
- int i = 0;
- while(1){
-
- if(profiles[i].profile == AV_PROFILE_UNKNOWN ){
- break;
- }
- cout<<"profiles[<<" << i << "].profiles = " << profiles[i].profile << " profiles.name = " << profiles[i].name << endl;
- ++i;
- }
- }
-
-
- /**
- * Group name of the codec implementation.
- * This is a short symbolic name of the wrapper backing this codec.
- A wrapper uses some kind of external implementation for the codec,
- such as an external library, or a codec implementation provided by the OS or the hardware.
- * If this field is NULL, this is a builtin, libavcodec native codec.
- * If non-NULL, this will be the suffix in AVCodec.name in most cases (usually AVCodec.name will be of the form "
_"). - const char *wrapper_name;
- */
- /**
- *编解码器实现的组名称。
- *这是支持此编解码器的包装器的简短符号名称。
- 包装器对编解码器使用某种外部实现,
- 诸如外部库或由OS或硬件提供的编解码器实现。
- *如果此字段为NULL,则这是一个内置的libavcodec本机编解码器。
- *如果非NULL,在大多数情况下,这将是AVCodec.name中的后缀(通常AVCodec.name的形式为“
_”)。 - */
- const char*wrapper_name = avcodec->wrapper_name;
-
- if(wrapper_name == nullptr){
- cout<<"wrapper_name = nullptr"<<endl;
- }else{
- cout<<"wrapper_name = " <<wrapper_name <<endl;
- }
-
- cout<<"debug point "<<endl;
-
- }
-
-
- void printfAVCodecContext(AVCodecContext *encoderAVCodecContext){
-
- }
-
-
-
- int main()
- {
- cout << "Hello World!" << endl;
- int ret = 0;
-
- //1.编码器
- const AVCodec * encoderAVCodec = nullptr;
- //2.编码器上下文
- AVCodecContext * encoderAVCodecContext = nullptr;
-
- //7.打开输入和输出文件
- const char *in_pcm_file = "D:/AllInformation/qtworkspacenew/08encoder_01_pcmToAAC/48000_2_f32le.pcm";
- FILE *infile = nullptr;
-
- const char *out_aac_file = "D:/AllInformation/qtworkspacenew/08encoder_01_pcmToAAC/48000_2_f32le.acc";;
- FILE *outfile = nullptr;
-
- //8.有了文件,就需要从文件读写,创建avframe,用于将pcm文件中的数据读取到avframe中。
- AVFrame *frame = nullptr;
-
- //9.有了文件,就需要从文件读写,创建 avpacket, 用于将编码后的数据存储到avpacket中。
- AVPacket *pkt = nullptr;
-
- //12.
- // 12.1 avframe 中的每一个frame 帧中的大小。
- int frame_bytes = 0;
- // 12.2 frame_buf 的目的是从 pcm file 中读取数据到 frame_buf,然后 通过 av_samples_fill_arrays方法读取到 真正的frame中
- uint8_t *frame_buf = nullptr;
- // 12.3 frame_buf_trans 的目的是,如果 frame_buf 的格式不符合条件,需要将 frame_buf中的数据转换,然后存储到 frame_buf_trans 中
- const uint8_t *frame_buf_trans = nullptr;
-
- //13. 设置pts,在编码阶段设置
- int64_t pts = 0;
-
- //1.找到编码器
- encoderAVCodec = avcodec_find_encoder(AV_CODEC_ID_AAC);
- if(encoderAVCodec == nullptr){
- ret = -1;
- cout << " avcodec_find_encoder(AV_CODEC_ID_AAC) error " << endl;
- goto pcmtoaacend;
- }
- //1.1 打印编码器信息
- printfAVCodec(encoderAVCodec);
-
- //2.编码器上下文
- encoderAVCodecContext = avcodec_alloc_context3(encoderAVCodec);
- if(encoderAVCodec == nullptr){
- cout << " avcodec_alloc_context3(AV_CODEC_ID_AAC) error " << endl;
- ret = -2;
- goto pcmtoaacend;
- }
-
- printfAVCodecContext(encoderAVCodecContext);
-
-
- //3.设置 编码器上下文 参数,这一步的原因:要告诉编码器,我要将什么格式的pcm进行编码,pcm数据是纯音频数据,没有头部,如果不告知编码器,编码器就不知道参数,就无从编码
- //3.1 设置哪些参数呢? 告知我们的pcm 是 -ar 48000 -ac 2 -f f32le 的,由于我们使用的是ffmpeg自带的aac编码器,因此 sample_rate必须是48000,sample_fmt必须是AV_SAMPLE_FMT_FLTP
- //那么如果不是,就应该使用音频重采样处理成ffmpeg 自带的可以识别的 采样率,采样格式。
- //那么如果我们使用的lib-fdk,由于lib-fdk可以识别 采样率是44100,采样格式是 AV_SAMPLE_FMT_S16,如果给定的pcm不是AV_SAMPLE_FMT_S16的,也不是44100的,那么就要进行 音频重采样。
-
- encoderAVCodecContext->sample_rate = 48000; //48000; 一定要设置,因为在 编码器 中 只是说了 当前编码器支持的 采样率数组,我们要在编码器上下文中设置真正的采样率到底是多少
- encoderAVCodecContext->ch_layout = AV_CHANNEL_LAYOUT_STEREO; //2 在编码器中只是说了当前编码器支持的 声道数 数组(有些编码器还不说),因此要手动的设定。也是因为有些编码器并不设定支持的 声道数 数组,因此声道数这个值不用判断 是否合理
- if(strcmp(encoderAVCodec->name, "aac") == 0) {
- encoderAVCodecContext->sample_fmt = AV_SAMPLE_FMT_FLTP; //f32le //我们使用的是ffmpeg自带的aac编码器,ffmpeg自带的aac编码器要求,pcm的格式必须是 AV_SAMPLE_FMT_FLTP
- } else if(strcmp(encoderAVCodec->name, "libfdk_aac") == 0) {
- encoderAVCodecContext->sample_fmt = AV_SAMPLE_FMT_S16; //s16le
- } else {
- encoderAVCodecContext->sample_fmt = AV_SAMPLE_FMT_FLTP; //f32le
- }
- encoderAVCodecContext->codec_id = encoderAVCodec->id; //设置编码器id,从FFMPEG自带的AAC编码器来看,在通过 第二步,分配编码器上下文的时候,就已经将编码器的 id传递过去了,这里查看avcodec_alloc_context3方法源码,就可以看到encoderAVCodecContext 中的 值是将 encoderAVCodec 的id 和type 拷贝过去的。因此可以不赋值。但是为了保险起见,还是赋值一下比较好
- encoderAVCodecContext->codec_type = encoderAVCodec->type; //设置编码器类型 同上
- encoderAVCodecContext->bit_rate = 128*1024; //设置平均比特率,FFMEPG AAC中有说明,不设置的话,默认也会是128kbps,ffmpeg网站 参考 https://ffmpeg.org/ffmpeg-all.html#Options-11
- encoderAVCodecContext->profile = FF_PROFILE_AAC_LOW; //设置aac 的规格,这个值也可以不设置,默认也会是 FF_PROFILE_AAC_LOW, ffmpeg网站 参考 https://ffmpeg.org/ffmpeg-all.html#Options-11
-
- //4. 检测我们给定的pcm的这些值是否支持,只要有一个不支持就需要 音频重采样。
- if (!check_sample_fmt(encoderAVCodec, encoderAVCodecContext->sample_fmt)) {
- fprintf(stderr, "Encoder does not support sample format %s",
- av_get_sample_fmt_name(encoderAVCodecContext->sample_fmt));
- exit(1);
- }
- if (!check_sample_rate(encoderAVCodec, encoderAVCodecContext->sample_rate)) {
- fprintf(stderr, "Encoder does not support sample rate %d", encoderAVCodecContext->sample_rate);
- exit(1);
- }
- if (!check_channel_layout(encoderAVCodec, encoderAVCodecContext->ch_layout)) {
- fprintf(stderr, "Encoder does not support channel layout %lu", encoderAVCodecContext->ch_layout.nb_channels);
- exit(1);
- }
-
- //5.如果走到这里,说明上述参数都可以使用,那么我们打印一下这些参数
- printf("\n\nAudio encode config\n");
- printf("bit_rate:%ldkbps\n", encoderAVCodecContext->bit_rate/1024);
- printf("sample_rate:%d\n", encoderAVCodecContext->sample_rate);
- printf("sample_fmt:%s\n", av_get_sample_fmt_name(encoderAVCodecContext->sample_fmt));
- printf("channels:%d\n", encoderAVCodecContext->ch_layout.nb_channels);
- // frame_size是在avcodec_open2后进行关联
- printf("1 frame_size:%d\n", encoderAVCodecContext->frame_size); //在没有 “将编码器上下文和编码器进行关联”前,这个值是0,当调用了avcodec_open2方法 -- “将编码器上下文和编码器进行关联”后,aac会将这个值变成 1024
- encoderAVCodecContext->flags = AV_CODEC_FLAG_GLOBAL_HEADER; //ffmpeg默认的aac是不带adts,而fdk_aac默认带adts,这里我们强制不带
-
- //6. /* 将编码器上下文和编码器进行关联 从avcodec_open2源码来看,会设置很多的参数 */
- if (avcodec_open2(encoderAVCodecContext, encoderAVCodec, NULL) < 0) {
- fprintf(stderr, "Could not open codec\n");
- exit(1);
- }
-
- printfAVCodecContext(encoderAVCodecContext);
-
- printf("2 frame_size:%d\n\n", encoderAVCodecContext->frame_size); // 决定每次到底送多少个采样点,当 avcodec_open2 调用后,这个值是1024
-
- //7.打开输入和输出文件
- infile = fopen(in_pcm_file, "rb");
- if (!infile) {
- fprintf(stderr, "Could not open %s\n", in_pcm_file);
- goto pcmtoaacend;
- }
- outfile = fopen(out_aac_file, "wb");
- if (!outfile) {
- fprintf(stderr, "Could not open %s\n", out_aac_file);
- goto pcmtoaacend;
- }
-
- //8.有了文件,就需要从文件读写,创建avframe,用于将pcm文件中的数据读取到avframe中。
- /* frame containing input raw audio */
- frame = av_frame_alloc(); //av_frame_alloc,会分配出来frame的内存,但是不会给frame的data分配空间。在后面的 av_frame_get_buffer方法中,会真正的给 frame的data分配空间。参考AVFrame 结构体中的data。
- if (!frame)
- {
- fprintf(stderr, "Could not allocate audio frame\n");
- goto pcmtoaacend;
- }
-
-
- //9.有了文件,就需要从文件读写,创建 avpacket, 用于将编码后的数据存储到avpacket中。
- /* packet for holding encoded output */
- pkt = av_packet_alloc();
- if (!pkt)
- {
- fprintf(stderr, "could not allocate the packet\n");
- goto pcmtoaacend;
- }
-
- //10.设置 avframe的参数,因为读取的pcm数据 要都是要放在avframe中,因此要告知给 avframe 中读取多少数据。
- /* 每次送多少数据给编码器由:
- * (1)frame_size(每帧单个通道的采样点数);
- * (2)sample_fmt(采样点格式);
- * (3)channel_layout(通道布局情况);
- * 3要素决定
- */
- frame->nb_samples = encoderAVCodecContext->frame_size;
- frame->format = encoderAVCodecContext->sample_fmt;
- frame->ch_layout = encoderAVCodecContext->ch_layout;
-
- printf("frame nb_samples:%d\n", frame->nb_samples);
- printf("frame sample_fmt:%d\n", frame->format);
- printf("frame channel_layout:%lu\n\n", frame->ch_layout);
-
- //11. 为frame分配 buffer,实际上就是给frame的data分配真正的空间,参考 av_frame_get_buffer 的源码。
- //参考av_frame_get_buffer方法的说明如下
- /*** The following fields must be set on frame before calling this function:
- * - format (pixel format for video, sample format for audio)
- * - width and height for video
- * - nb_samples and ch_layout for audio
- * 第一个参数 frame,会给这个frame分配 data和 extradata的空间
- * 如果这个frame已经分配过了data,那么可能造成 内存泄漏。
- * 第二个参数为0,表示和当前的CPU对齐。而且强烈要求是0,除非您知道你要干啥?
- ***/
- ret = av_frame_get_buffer(frame, 0);
- if (ret < 0)
- {
- fprintf(stderr, "Could not allocate audio data buffers\n");
- goto pcmtoaacend;
- }
-
- //12. 下来就要将 pcm 的数据 读取到frame中,最终使用的方法是 av_samples_fill_arrays,
- //看一下av_samples_fill_arrays方法的参数,第一个参数表示给 frame->data中写,第二个参数给frame->linesize 中写,
- // 第二个参数表示读取 buf中数据,那么这个buf的大小应该是多少呢?很显然,应该是一个帧 的大小,那么一个帧的大小是多少呢?对于aac,是1024
- //第四个参数,表示 给 frame 读取的数据有多少个 声道
- //第五个参数,表示 给frame中的 样本数多大
- //第六个参数,表示,给frame中的 pcm的格式是啥
- //第七个参数,表示是否字节对齐,0表示字节对齐,0也是默认值。
- // int av_samples_fill_arrays(uint8_t **audio_data, int *linesize,
- // const uint8_t *buf,
- // int nb_channels, int nb_samples,
- // enum AVSampleFormat sample_fmt, int align);
-
- //12 从av_samples_fill_arrays方法中,我们需要 自己弄一个第三个参数出来,并且告知第三个参数是多大。弄一个const uint8_t * 很容易,那么怎么知道这个buffer的大小呢?
- //我们是给av_samples_fill_arrays方法的第一个参数中写,那么就要知道frame一帧的数据是多大,由于aac的一帧是需要1024个样本,那么大小就是 = 每一个样本的大小 * 1024个样本 * 声道数量
- // 计算出每一帧的数据 单个采样点的字节 * 通道数目 * 每帧采样点数量
- frame_bytes = av_get_bytes_per_sample((AVSampleFormat)frame->format) * frame->ch_layout.nb_channels * frame->nb_samples;
-
- //这里我们可以想一下 av_samples_fill_arrays 方法为什么需要这些参数。
- //第一个参数,第二个参数都是给 哪里写,第三个参数要写的buf是啥, 那么4,5,6,7参数是干啥的呢?是告诉ffmpeg我们的内存要怎么分配。
-
- frame_buf = (uint8_t *)malloc(frame_bytes);
- if(!frame_buf) {
- printf("frame_buf malloc failed\n");
- goto pcmtoaacend;
- }
-
- frame_buf_trans = (uint8_t *)malloc(frame_bytes);
- if(!frame_buf_trans) {
- printf("frame_buf_trans malloc failed\n");
- goto pcmtoaacend;
- }
-
- //13.真正的开始读取数据
- printf("start enode\n");
- while(1){
- //13.1 每次开始从pcm file 读取的时候,先要将 frame_buf的数据清空
- memset((void *)frame_buf, 0, frame_bytes);
- //13.2 当我们将数据清除了后,就应该给frame_buf中写入数据了。返回值read_bytes表示真正读取的数据是多少
- size_t read_bytes = fread(frame_buf, 1, frame_bytes, infile);
- if(read_bytes <= 0){ //注意的是:fread 的返回值正常情况下为真正的读取的数据是多少;如果返回0,表示第二个参数或者第三个参数为0;如果读取到文件尾或者出现错误,则返回负值;fread 不区别文件尾和错误,而调用者必须用 feof 和 ferror 鉴别出现者为何。
- if (feof(infile)) //feof 检查是否已抵达给定文件流的结尾。说明是正常读取文件结束。
- printf("Error reading infile: unexpected end of file\n");
- else if (ferror(infile)) {
- perror("Error reading infile");
- }
- printf("read file finish\n");
- break;
-
- }
- //如果走到这一步,说明读取的数据是有的,那么开始对这部分数据进行处理
- //那么现在这个数据都早frame_buf中了,这个frame_buf是我们自定义的一块内存,这块内存的中的数据是从pcm中获取的,
- //注意的是:能存储在文件中的pcm数据都是交错模式的,但是ffmpeg 的AAC编码器是不能处理 交错模式的pcm的,因此我们还需要将交错模式的pcm转换成 planar 模式的pcm
-
- //av_frame_make_writable:确保AVFrame是可写的,尽可能避免数据的复制。 如果AVFrame不是是可写的,将分配新的buffer和复制数据。
- //确保该frame可写, 如果编码器内部保持了内存参考计数,则需要重新拷贝一个备份.目的是新写入的数据和编码器保存的数据不能产生冲突
- ret = av_frame_make_writable(frame);
- if(ret != 0)
- printf("av_frame_make_writable failed, ret = %d\n", ret);
-
- //注意我们这时候读取到的frame_buf中的数据是 infile文件中的数据。也就是pcm文件,那么这个pcm一般情况下都是 交错模式的(因为只有交错模式的才能播放,一般存放在本地文件的pcm都是交错模式的)
-
- //ffmpeg自带的aac 只能处理 AV_SAMPLE_FMT_FLTP ; fdk-aac只能处理的是 AV_SAMPLE_FMT_S16模式
-
- //这里判断,如果使用的fdk-libaac,且我们给编码器设置的就是 AV_SAMPLE_FMT_S16 ,就可以直接处理,因为fdk-libaac是可以直接处理 AV_SAMPLE_FMT_S16
- if((strcmp(encoderAVCodec->name, "libfdk_aac")==0) && AV_SAMPLE_FMT_S16 == frame->format) {
- // 将读取到的PCM数据填充到frame去,但要注意格式的匹配, 是planar还是packed都要区分清楚
- ret = av_samples_fill_arrays(frame->data, frame->linesize,
- frame_buf, frame->ch_layout.nb_channels,
- frame->nb_samples, (AVSampleFormat)frame->format, 0);
- if(ret <0 ){
- printf("av_samples_fill_arrays failed AV_SAMPLE_FMT_S16 \n");
- goto pcmtoaacend;
- }
- } else {
- // 走到这里,就是我们读取的数据 是从pcm 的infile中获得的,大部分也 是交错模式的 ,那么这里就有需要将这个交错模式的pcm,转成ffmpeg 内部AAC支持的 AV_SAMPLE_FMT_FLTP planar 模式的,才能处理
- // 这里为了简单,本地测试是直接用的 48000 的 f32le的pcm,因此要将本地的f32le packed模式的数据转为float palanar(通过 f32le_convert_to_fltp),
- memset((void *)frame_buf_trans, 0, frame_bytes);
- f32le_convert_to_fltp((float *)frame_buf, (float *)frame_buf_trans, frame->nb_samples);
- ret = av_samples_fill_arrays(frame->data, frame->linesize,
- frame_buf_trans, frame->ch_layout.nb_channels,
- frame->nb_samples, (AVSampleFormat)frame->format, 0);
- if(ret <0 ){
- printf("av_samples_fill_arrays failed AV_SAMPLE_FMT_S16 \n");
- goto pcmtoaacend;
- }
- }
-
- //走到这里数据就已经在 frame中了,那么下来就要对这个frame进行编码了
- // 设置pts
- pts += frame->nb_samples;
- frame->pts = pts; // 使用采样率作为pts的单位,具体换算成秒 pts*1/采样率
- ret = encode(encoderAVCodecContext, frame, pkt, outfile);
- if(ret < 0) {
- printf("encode failed\n");
- break;
- }
-
- }
-
- //14/* 冲刷编码器 */
- encode(encoderAVCodecContext, NULL, pkt, outfile);
-
-
-
- pcmtoaacend:
- // 关闭文件
- fclose(infile);
- fclose(outfile);
-
- if(frame_buf){
- free((void *)frame_buf);
- }
- if(frame_buf_trans){
- free((void *)frame_buf_trans);
- }
- av_packet_free(&pkt);//av_packet_free是安全的,即使pkt是null,参考源码可知
- av_frame_free(&frame);//av_frame_free是安全的,即使frame是null,参考源码可知
- if(!infile){
- fclose(infile);
- }
-
- if(!outfile){
- fclose(outfile);
- }
-
- if(encoderAVCodecContext){
- avcodec_free_context(&encoderAVCodecContext);
- }
-
- return ret;
- }
0代表成功。
如果返回值<0,代表错误,一般错误有三种,前两种不应该叫做错误
* @retval AVERROR(EAGAIN) output is not available in the current state - user must
* try to send input 输出 在当前状态下不可用,user需要重新发送一遍
* @retval AVERROR_EOF the encoder has been fully flushed, and there will be no
* more output packets 编码器已完全刷新,将不再有输出数据包
* @retval AVERROR(EINVAL) codec not opened, or it is a decoder 不是编码器,可能是解码器
- /**
- * Read encoded data from the encoder.
- *
- * @param avctx codec context
- * @param avpkt This will be set to a reference-counted packet allocated by the
- * encoder. Note that the function will always call
- * av_packet_unref(avpkt) before doing anything else.
- * @retval 0 success
- * @retval AVERROR(EAGAIN) output is not available in the current state - user must
- * try to send input
- * @retval AVERROR_EOF the encoder has been fully flushed, and there will be no
- * more output packets
- * @retval AVERROR(EINVAL) codec not opened, or it is a decoder
- * @retval "another negative error code" legitimate encoding errors
- */
- int avcodec_receive_packet(AVCodecContext *avctx, AVPacket *avpkt);