此章节的一些参数,需要先掌握aac的一些基本知识:aac音视频开发13 FFmpeg 音频 --- 常用音频格式AAC,AAC编码器, AAC ADTS格式 。_ffmpeg aac data数据格式-CSDN博客
目的:
从本地⽂件读取PCM数据进⾏AAC格式编码,然后将编码后的AAC数据存储到本地⽂件。
流程:
关键函数说明:
avcodec_find_encoder:根据指定的AVCodecID查找注册的编码器。
avcodec_alloc_context3:为AVCodecContext分配内存。
avcodec_open2:打开编码器。
avcodec_send_frame:将AVFrame⾮压缩数据给编码器。
avcodec_receive_packet:获取到编码后的AVPacket数据,收到的packet需要⾃⼰释放内存。
av_frame_get_buffer: 为⾳频或视频帧分配新的buffer。在调⽤这个函数之前,必须在AVFame上设
置好以下属性:format(视频为像素格式,⾳频为样本格式)、nb_samples(样本个数,针对⾳频)、
channel_layout(通道类型,针对⾳频)、width/height(宽⾼,针对视频)。
av_frame_make_writable :确保AVFrame是可写的,使⽤av_frame_make_writable()的问题是,在最坏的情况下,它会在您使⽤encode再次更改整个输⼊frame之前复制它. 如果frame不可写,
av_frame_make_writable()将分配新的缓冲区,并复制这个输⼊input frame数据,避免和编码器需
要缓存该帧时造成冲突。
av_samples_fill_arrays 填充⾳频帧
对于 flush encoder的操作:
编码器通常的冲洗⽅法:调⽤⼀次 avcodec_send_frame(NULL)(返回成功),然后不停调⽤
avcodec_receive_packet() 直到其返回 AVERROR_EOF,取出所有缓存帧, avcodec_receive_packet() 返回 AVERROR_EOF 这⼀次是没有有效数据的,仅仅获取到⼀
个结束标志
PCM样本格式
PCM(Pulse Code Modulation,脉冲编码调制)⾳频数据是未经压缩的⾳频采样数据裸流,它是由模拟信 号经过采样、量化、编码转换成的标准数字⾳频数据。
描述PCM数据的6个参数:
-
Sample Rate : 采样频率。8kHz(电话)、44.1kHz(CD)、48kHz(DVD)。
-
Sample Size : 量化位数。通常该值为16-bit。
-
Number of Channels : 通道个数。常⻅的⾳频有⽴体声(stereo)和单声道(mono)两种类型,⽴体声包 含左声道和右声道。另外还有环绕⽴体声等其它不太常⽤的类型。
-
Sign : 表示样本数据是否是有符号位,⽐如⽤⼀字节表示的样本数据,有符号的话表示范围为-128 ~ 127,⽆符号是0 ~ 255。有符号位16bits数据取值范围为-32768~32767。
-
Byte Ordering : 字节序。字节序是little-endian还是big-endian。通常均为little-endian。字节序说
明⻅第4节。 -
Integer Or Floating Point : 整形或浮点型。⼤多数格式的PCM样本数据使⽤整形表示,⽽在⼀些对 精度要求⾼的应⽤⽅⾯,使⽤浮点类型表示PCM样本数据(浮点数 float值域为 [-1.0, 1.0])。
//播放格式为f32le,双声道,采样频率48000Hz的PCM数据
ffplay -f f32le -ac 2 -ar 48000 pcm_audio
如何知道FFmpeg⽀持的PCM数据格式
使⽤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
s是有符号,u是⽆符号,f是浮点数。
be是⼤端,le是⼩端。
FFmpeg中Packed和Planar的PCM数据区别
FFmpeg中⾳视频数据基本上都有Packed和Planar两种存储⽅式,对于双声道⾳频来说,
Packed⽅式为两个声道的数据交错存储;Planar⽅式为两个声道分开存储。 假设⼀个L/R为⼀
个采样点,数据存储的⽅式如下所示:
Packed: L R L R L R L R
Planar: L L L L ... R R R R...
packed格式
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 ...
planar格式
planar为FFmpeg内部存储⾳频使⽤的采样格式,所有的Planar格式后⾯都有字⺟P标识。
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
plane 0: LLLLLLLLLLLLLLLLLLLLLLLLLL...
plane 1: RRRRRRRRRRRRRRRRRRRR....
plane 0对于uint8_t *data[0];
plane 1对于uint8_t *data[1];
FFMPEG 默认的AAC编码器行为:
FFmpeg默认的AAC编码器不⽀持AV_SAMPLE_FMT_S16格式的编码,
只⽀持 AV_SAMPLE_FMT_FLTP,
AV_SAMPLE_FMT_FLTP 这种格式是按平⾯存储,样点是float类型,所谓平⾯也就是 每个声道单独存储,⽐如左声道存储到data[0]中,右声道存储到data[1]中。
FFmpeg⾳频 解码后 和 编码前 的数据是存放在AVFrame结构中的。
Packed格式,frame.data[0]或frame.extended_data[0]包含所有的⾳频数据中。
Planar格式,frame.data[i]或者frame.extended_data[i]表示第i个声道的数据(假设声道0是第⼀
个),
AVFrame.data数组⼤⼩固定为8,如果声道数超过8,需要从frame.extended_data获取声道数据。
补充说明
Planar模式 是 ffmpeg内部存储模式 ,我们 实际使⽤的⾳频⽂件 都是 Packed模式 的。
FFmpeg解码不同格式的⾳频输出的⾳频采样格式不是⼀样。
测试发现,
AAC解码输出的数据为浮点型的 AV_SAMPLE_FMT_FLTP 格式,
MP3解码输出的数据为 AV_SAMPLE_FMT_S16P 格式(使 ⽤的mp3⽂件为16位深)。
具体采样格式可以查看解码后的AVFrame中的 format成员 或编解码器的 AVCodecContext中的 sample_fmt 成员。
Planar或者Packed模式直接影响到保存⽂件时写⽂件的操作,操作数据的时候⼀定要先检测⾳频采样 格式。
示例代码:
//编码 代码。
//音频的格式,
//从本地⽂件读取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 "<codec_name>_<wrapper_name>").
const char *wrapper_name;
*/
/**
*编解码器实现的组名称。
*这是支持此编解码器的包装器的简短符号名称。
包装器对编解码器使用某种外部实现,
诸如外部库或由OS或硬件提供的编解码器实现。
*如果此字段为NULL,则这是一个内置的libavcodec本机编解码器。
*如果非NULL,在大多数情况下,这将是AVCodec.name中的后缀(通常AVCodec.name的形式为"<codec_name>_<wrapper_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;
}
问题一:
avcodec_receive_packet 不同的返回值代表什么含义;读取的packet如果要放到队列⾥⾯那应该怎么放 到队列?
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);