• 音视频开发29 FFmpeg 音频编码- 流程以及重要API,该章节使用AAC编码说明


    此章节的一些参数,需要先掌握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个参数:
    1. Sample Rate : 采样频率。8kHz(电话)、44.1kHz(CD)、48kHz(DVD)。
    2. Sample Size : 量化位数。通常该值为16-bit。
    3. Number of Channels : 通道个数。常⻅的⾳频有⽴体声(stereo)和单声道(mono)两种类型,⽴体声包 含左声道和右声道。另外还有环绕⽴体声等其它不太常⽤的类型。
    4. Sign : 表示样本数据是否是有符号位,⽐如⽤⼀字节表示的样本数据,有符号的话表示范围为-128 ~ 127,⽆符号是0 ~ 255。有符号位16bits数据取值范围为-32768~32767。
    5. Byte Ordering : 字节序。字节序是little-endian还是big-endian。通常均为little-endian。字节序说
    明⻅第4节。
    6. Integer Or Floating Point : 整形或浮点型。⼤多数格式的PCM样本数据使⽤整形表示,⽽在⼀些对 精度要求⾼的应⽤⽅⾯,使⽤浮点类型表示PCM样本数据(浮点数 float值域为 [-1.0, 1.0])。

    1. //播放格式为f32le,双声道,采样频率48000Hz的PCM数据
    2. ffplay -f f32le -ac 2 -ar 48000 pcm_audio

    如何知道FFmpeg⽀持的PCM数据格式

    使⽤ffmpeg -formats命令,获取ffmpeg⽀持的⾳视频格式,其中我们可以找到⽀持的PCM格式。

    ffmpeg -formats | findstr PCM

    1. DE alaw PCM A-law
    2. DE f32be PCM 32-bit floating-point big-endian
    3. DE f32le PCM 32-bit floating-point little-endian
    4. DE f64be PCM 64-bit floating-point big-endian
    5. DE f64le PCM 64-bit floating-point little-endian
    6. DE mulaw PCM mu-law
    7. DE s16be PCM signed 16-bit big-endian
    8. DE s16le PCM signed 16-bit little-endian
    9. DE s24be PCM signed 24-bit big-endian
    10. DE s24le PCM signed 24-bit little-endian
    11. DE s32be PCM signed 32-bit big-endian
    12. DE s32le PCM signed 32-bit little-endian
    13. DE s8 PCM signed 8-bit
    14. DE u16be PCM unsigned 16-bit big-endian
    15. DE u16le PCM unsigned 16-bit little-endian
    16. DE u24be PCM unsigned 24-bit big-endian
    17. DE u24le PCM unsigned 24-bit little-endian
    18. DE u32be PCM unsigned 32-bit big-endian
    19. DE u32le PCM unsigned 32-bit little-endian
    20. DE u8 PCM unsigned 8-bit
    21. 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. 1 AV_SAMPLE_FMT_U8, ///< unsigned 8 bits
    2. 2 AV_SAMPLE_FMT_S16, ///< signed 16 bits
    3. 3 AV_SAMPLE_FMT_S32, ///< signed 32 bits
    4. 4 AV_SAMPLE_FMT_FLT, ///< float
    5. 5 AV_SAMPLE_FMT_DBL, ///< double

    只能保存在AVFrame的uint8_t *data[0]

    ⾳频保持格式如下:

    LRLRLR ...

    planar格式

    planar为FFmpeg内部存储⾳频使⽤的采样格式,所有的Planar格式后⾯都有字⺟P标识。
    1. 1 AV_SAMPLE_FMT_U8P, ///< unsigned 8 bits, planar
    2. 2 AV_SAMPLE_FMT_S16P, ///< signed 16 bits, planar
    3. 3 AV_SAMPLE_FMT_S32P, ///< signed 32 bits, planar
    4. 4 AV_SAMPLE_FMT_FLTP, ///< float, planar
    5. 5 AV_SAMPLE_FMT_DBLP, ///< double, planar
    6. 6 AV_SAMPLE_FMT_S64, ///< signed 64 bits
    7. 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模式直接影响到保存⽂件时写⽂件的操作,操作数据的时候⼀定要先检测⾳频采样 格式。

    示例代码:

    1. //编码 代码。
    2. //音频的格式,
    3. //从本地⽂件读取PCM数据进⾏AAC格式编码,然后将编码后的AAC数据存储到本地⽂件。
    4. #include <iostream>
    5. extern "C" {
    6. #include "libavcodec/avcodec.h"
    7. #include "libavformat/avformat.h"
    8. #include "libavutil/fifo.h"
    9. #include "libavutil/audio_fifo.h"
    10. #include "libswresample/swresample.h"
    11. #include "libavutil/opt.h"
    12. }
    13. using namespace std;
    14. static void get_adts_header(AVCodecContext *ctx, uint8_t *adts_header, int aac_length)
    15. {
    16. uint8_t freq_idx = 0; //0: 96000 Hz 3: 48000 Hz 4: 44100 Hz
    17. switch (ctx->sample_rate) {
    18. case 96000: freq_idx = 0; break;
    19. case 88200: freq_idx = 1; break;
    20. case 64000: freq_idx = 2; break;
    21. case 48000: freq_idx = 3; break;
    22. case 44100: freq_idx = 4; break;
    23. case 32000: freq_idx = 5; break;
    24. case 24000: freq_idx = 6; break;
    25. case 22050: freq_idx = 7; break;
    26. case 16000: freq_idx = 8; break;
    27. case 12000: freq_idx = 9; break;
    28. case 11025: freq_idx = 10; break;
    29. case 8000: freq_idx = 11; break;
    30. case 7350: freq_idx = 12; break;
    31. default: freq_idx = 4; break;
    32. }
    33. uint8_t chanCfg = ctx->channels;
    34. uint32_t frame_length = aac_length + 7;
    35. adts_header[0] = 0xFF;
    36. adts_header[1] = 0xF1;
    37. adts_header[2] = ((ctx->profile) << 6) + (freq_idx << 2) + (chanCfg >> 2);
    38. adts_header[3] = (((chanCfg & 3) << 6) + (frame_length >> 11));
    39. adts_header[4] = ((frame_length & 0x7FF) >> 3);
    40. adts_header[5] = (((frame_length & 7) << 5) + 0x1F);
    41. adts_header[6] = 0xFC;
    42. }
    43. static int encode(AVCodecContext *ctx, AVFrame *frame, AVPacket *pkt, FILE *output)
    44. {
    45. int ret;
    46. /* send the frame for encoding */
    47. ret = avcodec_send_frame(ctx, frame);
    48. if (ret < 0) {
    49. fprintf(stderr, "Error sending the frame to the encoder\n");
    50. return -1;
    51. }
    52. /* read all the available output packets (in general there may be any number of them */
    53. // 编码和解码都是一样的,都是send 1次,然后receive多次, 直到AVERROR(EAGAIN)或者AVERROR_EOF
    54. while (ret >= 0) {
    55. ret = avcodec_receive_packet(ctx, pkt);
    56. if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
    57. return 0;
    58. } else if (ret < 0) {
    59. fprintf(stderr, "Error encoding audio frame\n");
    60. return -1;
    61. }
    62. size_t len = 0;
    63. 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);
    64. if((ctx->flags & AV_CODEC_FLAG_GLOBAL_HEADER)) {
    65. // 需要额外的adts header写入
    66. uint8_t aac_header[7];
    67. get_adts_header(ctx, aac_header, pkt->size);
    68. len = fwrite(aac_header, 1, 7, output);
    69. if(len != 7) {
    70. fprintf(stderr, "fwrite aac_header failed\n");
    71. return -1;
    72. }
    73. }
    74. len = fwrite(pkt->data, 1, pkt->size, output);
    75. if(len != pkt->size) {
    76. fprintf(stderr, "fwrite aac data failed\n");
    77. return -1;
    78. }
    79. /* 是否需要释放数据? avcodec_receive_packet第一个调用的就是 av_packet_unref
    80. * 所以我们不用手动去释放,这里有个问题,不能将pkt直接插入到队列,因为编码器会释放数据
    81. * 可以新分配一个pkt, 然后使用av_packet_move_ref转移pkt对应的buffer
    82. */
    83. av_packet_unref(pkt);
    84. }
    85. av_frame_unref(frame);
    86. return -1;
    87. }
    88. /*
    89. * 这里只支持2通道的转换
    90. */
    91. void f32le_convert_to_fltp(float *f32le, float *fltp, int nb_samples) {
    92. float *fltp_l = fltp; // 左通道
    93. float *fltp_r = fltp + nb_samples; // 右通道
    94. for(int i = 0; i < nb_samples; i++) {
    95. fltp_l[i] = f32le[i*2]; // 0 1 - 2 3
    96. fltp_r[i] = f32le[i*2+1]; // 可以尝试注释左声道或者右声道听听声音
    97. }
    98. }
    99. /* 检测该编码器是否支持该采样格式 */
    100. static int check_sample_fmt(const AVCodec *codec, enum AVSampleFormat sample_fmt)
    101. {
    102. const enum AVSampleFormat *p = codec->sample_fmts;
    103. while (*p != AV_SAMPLE_FMT_NONE) { // 通过AV_SAMPLE_FMT_NONE(-1)作为结束符
    104. printf("%s sample_fmt support %d\n",codec->name,*p);
    105. if (*p == sample_fmt)
    106. return 1;
    107. p++;
    108. }
    109. return 0;
    110. }
    111. /* 检测该编码器是否支持该采样率 */
    112. static int check_sample_rate(const AVCodec *codec, const int sample_rate)
    113. {
    114. const int *p = codec->supported_samplerates;
    115. while (*p != 0) {// 0作为退出条件,比如libfdk-aacenc.c的aac_sample_rates
    116. printf("%s support %dhz\n", codec->name, *p);
    117. if (*p == sample_rate)
    118. return 1;
    119. p++;
    120. }
    121. return 0;
    122. }
    123. /* 检测该编码器是否支持该采样率, 该函数只是作参考 */
    124. static int check_channel_layout(const AVCodec *codec, AVChannelLayout ch_layout)
    125. {
    126. // 不是每个codec都给出支持的 ch_layout,实验测试,ffmpeg自带的aac 编码器中的 ch_layout 就是nullptr
    127. const AVChannelLayout * avchannelLayout= codec->ch_layouts;
    128. if(!avchannelLayout) {
    129. printf("the codec %s no set channel_layouts\n", codec->name);
    130. return 1;
    131. }
    132. while ((*avchannelLayout).nb_channels != 0) { // 0作为退出条件,比如libfdk-aacenc.c的aac_channel_layout
    133. printf("%s support channel_layout %d\n", codec->name, (*avchannelLayout).nb_channels);
    134. if ((*avchannelLayout).nb_channels == ch_layout.nb_channels)
    135. return 1;
    136. avchannelLayout++;
    137. }
    138. return 0;
    139. }
    140. void printfAVCodec(const AVCodec *avcodec){
    141. if(avcodec==nullptr){
    142. cout<<"printfAVCodec error avcodec==nullptr"<<endl;
    143. return ;
    144. }
    145. cout << "avcodec->name = " << avcodec->name << endl;
    146. cout << "avcodec->long_name = " << avcodec->long_name << endl;
    147. //该编解码器的 类型,enum AVMediaType type; 类似 AVMEDIA_TYPE_VIDEO,AVMEDIA_TYPE_AUDIO
    148. cout << "avcodec->type = " << avcodec->type << endl;
    149. //enum AVCodecID id; 类似 AV_CODEC_ID_AAC,AV_CODEC_ID_H264 , AAC 对应的值是十进制是86018,十六进制是15002
    150. cout << "avcodec->id = " << avcodec->id << endl;
    151. /**
    152. * Codec capabilities.
    153. * see AV_CODEC_CAP_*
    154. * int capabilities;
    155. * AAC 作为 encoder 的值是98,对应的二进制是 0110 0010,对应的如下的三个值 或 起来
    156. * #define AV_CODEC_CAP_DR1 (1 << 1)
    157. #define AV_CODEC_CAP_DELAY (1 << 5)
    158. #define AV_CODEC_CAP_SMALL_LAST_FRAME (1 << 6)
    159. */
    160. cout << "avcodec->capabilities = " << avcodec->capabilities << endl;
    161. ///< maximum value for lowres supported by the decoder
    162. /// 视频专用 解码器支持的低分辨率的最大值
    163. // uint8_t max_lowres;
    164. cout << "avcodec->max_lowres = " << avcodec->max_lowres << endl;
    165. ///< array of supported framerates, or NULL if any, array is terminated by {0,0}
    166. ///const AVRational *supported_framerates;
    167. ///支持的帧率(仅视频), 类似 每秒25张图片
    168. if(avcodec->supported_framerates == nullptr){
    169. cout<<"avcodec->supported_framerates = nullptr"<<endl;
    170. } else {
    171. cout<<"avcodec->supported_framerates != nullptr"<<endl;
    172. const AVRational * avr = avcodec->supported_framerates;
    173. int i =0;
    174. while(1){
    175. if(avr[i].den ==0 && avr[i].num == 0){
    176. break;
    177. }
    178. cout<<"avr["<<i <<"].den = "<< avr[i].den << " avr[" << i << "].num = " << avr[i].num << endl;
    179. ++i;
    180. }
    181. }
    182. ///< array of supported pixel formats, or NULL if unknown, array is terminated by -1
    183. ///const enum AVPixelFormat *pix_fmts;
    184. /// 支持的像素格式(仅视频),类似 AV_PIX_FMT_YUV420P
    185. if(avcodec->pix_fmts == nullptr){
    186. cout<<"avcodec->pix_fmts = nullptr"<<endl;
    187. }else{
    188. cout<<"avcodec->pix_fmts != nullptr"<<endl;
    189. const enum AVPixelFormat * avpixelformat = avcodec->pix_fmts;
    190. int i =0;
    191. while(1){
    192. if(avpixelformat[i] == -1){
    193. break;
    194. }else{
    195. cout<<"avpixelformat["<<i<<"] = avpixelformat[i] " <<endl;
    196. ++i;
    197. }
    198. }
    199. }
    200. ///< array of supported audio samplerates, or NULL if unknown, array is terminated by 0
    201. /// const int *supported_samplerates;
    202. /// 支持的采样率(仅音频)41000,48000
    203. if(avcodec->supported_samplerates == nullptr){
    204. cout<<"avcodec->supported_samplerates = nullptr"<<endl;
    205. }else{
    206. cout<<"avcodec->supported_samplerates != nullptr"<<endl;
    207. const int * support_samplerates = avcodec->supported_samplerates;
    208. int i =0;
    209. while(1){
    210. if(support_samplerates[i] == 0){
    211. break;
    212. }else{
    213. cout<<"support_samplerates["<<i<<"] = " << support_samplerates[i]<<endl;
    214. ++i;
    215. }
    216. }
    217. }
    218. ///支持的采样格式(仅音频),类似 AV_SAMPLE_FMT_S16,FFMpeg 自带的AAC 只支持 AV_SAMPLE_FMT_FLTP,对应的十进制的值是8
    219. ///< array of supported sample formats, or NULL if unknown, array is terminated by -1
    220. /// const enum AVSampleFormat *sample_fmts;
    221. if(avcodec->sample_fmts == nullptr){
    222. cout<<"avcodec->sample_fmts = nullptr"<<endl;
    223. }else{
    224. cout<<"avcodec->sample_fmts != nullptr"<<endl;
    225. const enum AVSampleFormat *sample_fmts = avcodec->sample_fmts;
    226. int i =0;
    227. while(1){
    228. if(sample_fmts[i] == -1){
    229. break;
    230. }else{
    231. cout<<"sample_fmts["<<i<<"] = " << sample_fmts[i]<<endl;
    232. ++i;
    233. }
    234. }
    235. }
    236. //#if FF_API_OLD_CHANNEL_LAYOUT
    237. // /**
    238. // * @deprecated use ch_layouts instead
    239. // */
    240. // attribute_deprecated
    241. // const uint64_t *channel_layouts; ///< array of support channel layouts, or NULL if unknown. array is terminated by 0
    242. //#endif
    243. //该编码器支持的声道数量。已经不再使用。可以使用 const AVChannelLayout *ch_layouts; 替代
    244. const uint64_t *channel_layouts = avcodec->channel_layouts;
    245. if(channel_layouts == nullptr){
    246. cout<<"channel_layouts = nullptr"<<endl; //在ffmepg 6.0的时候,使用编码器ffmpeg自带的AAC,这块为nullptr。这不合理。这里baidu了一下,发现 如下的说法: // 不是每个codec都给出支持的channel_layout
    247. }else{
    248. cout<<"channel_layouts != nullptr"<<endl;
    249. int i = 0;
    250. while(1){
    251. if(channel_layouts[i] == 0){
    252. break;
    253. }
    254. cout<<"channel_layouts[<<" << i << "] = " << channel_layouts[i] << endl;
    255. ++i;
    256. }
    257. }
    258. ///这里为了方便测试 ,将已经废弃的const uint64_t *channel_layouts; 和 const AVChannelLayout *ch_layouts;对比
    259. /**
    260. * Array of supported channel layouts, terminated with a zeroed layout.
    261. */
    262. ///const AVChannelLayout *ch_layouts;
    263. const AVChannelLayout *ch_layouts = avcodec->ch_layouts;
    264. if(ch_layouts == nullptr){
    265. cout<<"ch_layouts = nullptr"<<endl; //在ffmepg 6.0的时候,使用编码器ffmpeg自带的AAC,这块为nullptr。这不合理。 baidu说明如下: // 不是每个codec都给出支持的channel_layout
    266. }else{
    267. cout<<"ch_layouts != nullptr"<<endl;
    268. int i = 0;
    269. while(1){
    270. if(ch_layouts[i].nb_channels == 0 ){
    271. break;
    272. }
    273. cout<<"ch_layouts[<<" << i << "].nb_channels = " << ch_layouts[i].nb_channels << endl;
    274. ++i;
    275. }
    276. }
    277. ///< AVClass for the private context
    278. /// const AVClass *priv_class;
    279. /// AVClass最主要的作用就是给结构体(例如AVFormatContext等)增加AVOption功能的支持。换句话说AVClass就是AVOption和目标结构体之间的“桥梁”。AVClass要求必须声明为目标结构体的第一个变量。
    280. const AVClass *priv_class = avcodec->priv_class;
    281. if(priv_class==nullptr){
    282. cout << "priv_class = nullptr" << endl;
    283. }else {
    284. cout << "priv_class != nullptr" << endl;
    285. cout << "priv_class->option->name = " << priv_class->option->name << endl;
    286. }
    287. ///< array of recognized profiles, or NULL if unknown, array is terminated by {AV_PROFILE_UNKNOWN}
    288. /// const AVProfile *profiles;
    289. /// 如果非NULL,则为此编解码器识别的配置文件数组。
    290. /// 该结构描述了由AVCodecID描述的单个编解码器的属性。
    291. const AVProfile *profiles = avcodec->profiles;
    292. if(profiles == nullptr){
    293. cout<<"profiles = nullptr"<<endl;
    294. }else{
    295. cout<<"profiles != nullptr"<<endl;
    296. int i = 0;
    297. while(1){
    298. if(profiles[i].profile == AV_PROFILE_UNKNOWN ){
    299. break;
    300. }
    301. cout<<"profiles[<<" << i << "].profiles = " << profiles[i].profile << " profiles.name = " << profiles[i].name << endl;
    302. ++i;
    303. }
    304. }
    305. /**
    306. * Group name of the codec implementation.
    307. * This is a short symbolic name of the wrapper backing this codec.
    308. A wrapper uses some kind of external implementation for the codec,
    309. such as an external library, or a codec implementation provided by the OS or the hardware.
    310. * If this field is NULL, this is a builtin, libavcodec native codec.
    311. * If non-NULL, this will be the suffix in AVCodec.name in most cases (usually AVCodec.name will be of the form "_").
    312. const char *wrapper_name;
    313. */
    314. /**
    315. *编解码器实现的组名称。
    316. *这是支持此编解码器的包装器的简短符号名称。
    317. 包装器对编解码器使用某种外部实现,
    318. 诸如外部库或由OS或硬件提供的编解码器实现。
    319. *如果此字段为NULL,则这是一个内置的libavcodec本机编解码器。
    320. *如果非NULL,在大多数情况下,这将是AVCodec.name中的后缀(通常AVCodec.name的形式为“_”)。
    321. */
    322. const char*wrapper_name = avcodec->wrapper_name;
    323. if(wrapper_name == nullptr){
    324. cout<<"wrapper_name = nullptr"<<endl;
    325. }else{
    326. cout<<"wrapper_name = " <<wrapper_name <<endl;
    327. }
    328. cout<<"debug point "<<endl;
    329. }
    330. void printfAVCodecContext(AVCodecContext *encoderAVCodecContext){
    331. }
    332. int main()
    333. {
    334. cout << "Hello World!" << endl;
    335. int ret = 0;
    336. //1.编码器
    337. const AVCodec * encoderAVCodec = nullptr;
    338. //2.编码器上下文
    339. AVCodecContext * encoderAVCodecContext = nullptr;
    340. //7.打开输入和输出文件
    341. const char *in_pcm_file = "D:/AllInformation/qtworkspacenew/08encoder_01_pcmToAAC/48000_2_f32le.pcm";
    342. FILE *infile = nullptr;
    343. const char *out_aac_file = "D:/AllInformation/qtworkspacenew/08encoder_01_pcmToAAC/48000_2_f32le.acc";;
    344. FILE *outfile = nullptr;
    345. //8.有了文件,就需要从文件读写,创建avframe,用于将pcm文件中的数据读取到avframe中。
    346. AVFrame *frame = nullptr;
    347. //9.有了文件,就需要从文件读写,创建 avpacket, 用于将编码后的数据存储到avpacket中。
    348. AVPacket *pkt = nullptr;
    349. //12.
    350. // 12.1 avframe 中的每一个frame 帧中的大小。
    351. int frame_bytes = 0;
    352. // 12.2 frame_buf 的目的是从 pcm file 中读取数据到 frame_buf,然后 通过 av_samples_fill_arrays方法读取到 真正的frame中
    353. uint8_t *frame_buf = nullptr;
    354. // 12.3 frame_buf_trans 的目的是,如果 frame_buf 的格式不符合条件,需要将 frame_buf中的数据转换,然后存储到 frame_buf_trans 中
    355. const uint8_t *frame_buf_trans = nullptr;
    356. //13. 设置pts,在编码阶段设置
    357. int64_t pts = 0;
    358. //1.找到编码器
    359. encoderAVCodec = avcodec_find_encoder(AV_CODEC_ID_AAC);
    360. if(encoderAVCodec == nullptr){
    361. ret = -1;
    362. cout << " avcodec_find_encoder(AV_CODEC_ID_AAC) error " << endl;
    363. goto pcmtoaacend;
    364. }
    365. //1.1 打印编码器信息
    366. printfAVCodec(encoderAVCodec);
    367. //2.编码器上下文
    368. encoderAVCodecContext = avcodec_alloc_context3(encoderAVCodec);
    369. if(encoderAVCodec == nullptr){
    370. cout << " avcodec_alloc_context3(AV_CODEC_ID_AAC) error " << endl;
    371. ret = -2;
    372. goto pcmtoaacend;
    373. }
    374. printfAVCodecContext(encoderAVCodecContext);
    375. //3.设置 编码器上下文 参数,这一步的原因:要告诉编码器,我要将什么格式的pcm进行编码,pcm数据是纯音频数据,没有头部,如果不告知编码器,编码器就不知道参数,就无从编码
    376. //3.1 设置哪些参数呢? 告知我们的pcm 是 -ar 48000 -ac 2 -f f32le 的,由于我们使用的是ffmpeg自带的aac编码器,因此 sample_rate必须是48000,sample_fmt必须是AV_SAMPLE_FMT_FLTP
    377. //那么如果不是,就应该使用音频重采样处理成ffmpeg 自带的可以识别的 采样率,采样格式。
    378. //那么如果我们使用的lib-fdk,由于lib-fdk可以识别 采样率是44100,采样格式是 AV_SAMPLE_FMT_S16,如果给定的pcm不是AV_SAMPLE_FMT_S16的,也不是44100的,那么就要进行 音频重采样。
    379. encoderAVCodecContext->sample_rate = 48000; //48000; 一定要设置,因为在 编码器 中 只是说了 当前编码器支持的 采样率数组,我们要在编码器上下文中设置真正的采样率到底是多少
    380. encoderAVCodecContext->ch_layout = AV_CHANNEL_LAYOUT_STEREO; //2 在编码器中只是说了当前编码器支持的 声道数 数组(有些编码器还不说),因此要手动的设定。也是因为有些编码器并不设定支持的 声道数 数组,因此声道数这个值不用判断 是否合理
    381. if(strcmp(encoderAVCodec->name, "aac") == 0) {
    382. encoderAVCodecContext->sample_fmt = AV_SAMPLE_FMT_FLTP; //f32le //我们使用的是ffmpeg自带的aac编码器,ffmpeg自带的aac编码器要求,pcm的格式必须是 AV_SAMPLE_FMT_FLTP
    383. } else if(strcmp(encoderAVCodec->name, "libfdk_aac") == 0) {
    384. encoderAVCodecContext->sample_fmt = AV_SAMPLE_FMT_S16; //s16le
    385. } else {
    386. encoderAVCodecContext->sample_fmt = AV_SAMPLE_FMT_FLTP; //f32le
    387. }
    388. encoderAVCodecContext->codec_id = encoderAVCodec->id; //设置编码器id,从FFMPEG自带的AAC编码器来看,在通过 第二步,分配编码器上下文的时候,就已经将编码器的 id传递过去了,这里查看avcodec_alloc_context3方法源码,就可以看到encoderAVCodecContext 中的 值是将 encoderAVCodec 的id 和type 拷贝过去的。因此可以不赋值。但是为了保险起见,还是赋值一下比较好
    389. encoderAVCodecContext->codec_type = encoderAVCodec->type; //设置编码器类型 同上
    390. encoderAVCodecContext->bit_rate = 128*1024; //设置平均比特率,FFMEPG AAC中有说明,不设置的话,默认也会是128kbps,ffmpeg网站 参考 https://ffmpeg.org/ffmpeg-all.html#Options-11
    391. encoderAVCodecContext->profile = FF_PROFILE_AAC_LOW; //设置aac 的规格,这个值也可以不设置,默认也会是 FF_PROFILE_AAC_LOW, ffmpeg网站 参考 https://ffmpeg.org/ffmpeg-all.html#Options-11
    392. //4. 检测我们给定的pcm的这些值是否支持,只要有一个不支持就需要 音频重采样。
    393. if (!check_sample_fmt(encoderAVCodec, encoderAVCodecContext->sample_fmt)) {
    394. fprintf(stderr, "Encoder does not support sample format %s",
    395. av_get_sample_fmt_name(encoderAVCodecContext->sample_fmt));
    396. exit(1);
    397. }
    398. if (!check_sample_rate(encoderAVCodec, encoderAVCodecContext->sample_rate)) {
    399. fprintf(stderr, "Encoder does not support sample rate %d", encoderAVCodecContext->sample_rate);
    400. exit(1);
    401. }
    402. if (!check_channel_layout(encoderAVCodec, encoderAVCodecContext->ch_layout)) {
    403. fprintf(stderr, "Encoder does not support channel layout %lu", encoderAVCodecContext->ch_layout.nb_channels);
    404. exit(1);
    405. }
    406. //5.如果走到这里,说明上述参数都可以使用,那么我们打印一下这些参数
    407. printf("\n\nAudio encode config\n");
    408. printf("bit_rate:%ldkbps\n", encoderAVCodecContext->bit_rate/1024);
    409. printf("sample_rate:%d\n", encoderAVCodecContext->sample_rate);
    410. printf("sample_fmt:%s\n", av_get_sample_fmt_name(encoderAVCodecContext->sample_fmt));
    411. printf("channels:%d\n", encoderAVCodecContext->ch_layout.nb_channels);
    412. // frame_size是在avcodec_open2后进行关联
    413. printf("1 frame_size:%d\n", encoderAVCodecContext->frame_size); //在没有 “将编码器上下文和编码器进行关联”前,这个值是0,当调用了avcodec_open2方法 -- “将编码器上下文和编码器进行关联”后,aac会将这个值变成 1024
    414. encoderAVCodecContext->flags = AV_CODEC_FLAG_GLOBAL_HEADER; //ffmpeg默认的aac是不带adts,而fdk_aac默认带adts,这里我们强制不带
    415. //6. /* 将编码器上下文和编码器进行关联 从avcodec_open2源码来看,会设置很多的参数 */
    416. if (avcodec_open2(encoderAVCodecContext, encoderAVCodec, NULL) < 0) {
    417. fprintf(stderr, "Could not open codec\n");
    418. exit(1);
    419. }
    420. printfAVCodecContext(encoderAVCodecContext);
    421. printf("2 frame_size:%d\n\n", encoderAVCodecContext->frame_size); // 决定每次到底送多少个采样点,当 avcodec_open2 调用后,这个值是1024
    422. //7.打开输入和输出文件
    423. infile = fopen(in_pcm_file, "rb");
    424. if (!infile) {
    425. fprintf(stderr, "Could not open %s\n", in_pcm_file);
    426. goto pcmtoaacend;
    427. }
    428. outfile = fopen(out_aac_file, "wb");
    429. if (!outfile) {
    430. fprintf(stderr, "Could not open %s\n", out_aac_file);
    431. goto pcmtoaacend;
    432. }
    433. //8.有了文件,就需要从文件读写,创建avframe,用于将pcm文件中的数据读取到avframe中。
    434. /* frame containing input raw audio */
    435. frame = av_frame_alloc(); //av_frame_alloc,会分配出来frame的内存,但是不会给frame的data分配空间。在后面的 av_frame_get_buffer方法中,会真正的给 frame的data分配空间。参考AVFrame 结构体中的data
    436. if (!frame)
    437. {
    438. fprintf(stderr, "Could not allocate audio frame\n");
    439. goto pcmtoaacend;
    440. }
    441. //9.有了文件,就需要从文件读写,创建 avpacket, 用于将编码后的数据存储到avpacket中。
    442. /* packet for holding encoded output */
    443. pkt = av_packet_alloc();
    444. if (!pkt)
    445. {
    446. fprintf(stderr, "could not allocate the packet\n");
    447. goto pcmtoaacend;
    448. }
    449. //10.设置 avframe的参数,因为读取的pcm数据 要都是要放在avframe中,因此要告知给 avframe 中读取多少数据。
    450. /* 每次送多少数据给编码器由:
    451. * (1)frame_size(每帧单个通道的采样点数);
    452. * (2)sample_fmt(采样点格式);
    453. * (3)channel_layout(通道布局情况);
    454. * 3要素决定
    455. */
    456. frame->nb_samples = encoderAVCodecContext->frame_size;
    457. frame->format = encoderAVCodecContext->sample_fmt;
    458. frame->ch_layout = encoderAVCodecContext->ch_layout;
    459. printf("frame nb_samples:%d\n", frame->nb_samples);
    460. printf("frame sample_fmt:%d\n", frame->format);
    461. printf("frame channel_layout:%lu\n\n", frame->ch_layout);
    462. //11. 为frame分配 buffer,实际上就是给frame的data分配真正的空间,参考 av_frame_get_buffer 的源码。
    463. //参考av_frame_get_buffer方法的说明如下
    464. /*** The following fields must be set on frame before calling this function:
    465. * - format (pixel format for video, sample format for audio)
    466. * - width and height for video
    467. * - nb_samples and ch_layout for audio
    468. * 第一个参数 frame,会给这个frame分配 data和 extradata的空间
    469. * 如果这个frame已经分配过了data,那么可能造成 内存泄漏。
    470. * 第二个参数为0,表示和当前的CPU对齐。而且强烈要求是0,除非您知道你要干啥?
    471. ***/
    472. ret = av_frame_get_buffer(frame, 0);
    473. if (ret < 0)
    474. {
    475. fprintf(stderr, "Could not allocate audio data buffers\n");
    476. goto pcmtoaacend;
    477. }
    478. //12. 下来就要将 pcm 的数据 读取到frame中,最终使用的方法是 av_samples_fill_arrays,
    479. //看一下av_samples_fill_arrays方法的参数,第一个参数表示给 frame->data中写,第二个参数给frame->linesize 中写,
    480. // 第二个参数表示读取 buf中数据,那么这个buf的大小应该是多少呢?很显然,应该是一个帧 的大小,那么一个帧的大小是多少呢?对于aac,是1024
    481. //第四个参数,表示 给 frame 读取的数据有多少个 声道
    482. //第五个参数,表示 给frame中的 样本数多大
    483. //第六个参数,表示,给frame中的 pcm的格式是啥
    484. //第七个参数,表示是否字节对齐,0表示字节对齐,0也是默认值。
    485. // int av_samples_fill_arrays(uint8_t **audio_data, int *linesize,
    486. // const uint8_t *buf,
    487. // int nb_channels, int nb_samples,
    488. // enum AVSampleFormat sample_fmt, int align);
    489. //12 从av_samples_fill_arrays方法中,我们需要 自己弄一个第三个参数出来,并且告知第三个参数是多大。弄一个const uint8_t * 很容易,那么怎么知道这个buffer的大小呢?
    490. //我们是给av_samples_fill_arrays方法的第一个参数中写,那么就要知道frame一帧的数据是多大,由于aac的一帧是需要1024个样本,那么大小就是 = 每一个样本的大小 * 1024个样本 * 声道数量
    491. // 计算出每一帧的数据 单个采样点的字节 * 通道数目 * 每帧采样点数量
    492. frame_bytes = av_get_bytes_per_sample((AVSampleFormat)frame->format) * frame->ch_layout.nb_channels * frame->nb_samples;
    493. //这里我们可以想一下 av_samples_fill_arrays 方法为什么需要这些参数。
    494. //第一个参数,第二个参数都是给 哪里写,第三个参数要写的buf是啥, 那么4,5,6,7参数是干啥的呢?是告诉ffmpeg我们的内存要怎么分配。
    495. frame_buf = (uint8_t *)malloc(frame_bytes);
    496. if(!frame_buf) {
    497. printf("frame_buf malloc failed\n");
    498. goto pcmtoaacend;
    499. }
    500. frame_buf_trans = (uint8_t *)malloc(frame_bytes);
    501. if(!frame_buf_trans) {
    502. printf("frame_buf_trans malloc failed\n");
    503. goto pcmtoaacend;
    504. }
    505. //13.真正的开始读取数据
    506. printf("start enode\n");
    507. while(1){
    508. //13.1 每次开始从pcm file 读取的时候,先要将 frame_buf的数据清空
    509. memset((void *)frame_buf, 0, frame_bytes);
    510. //13.2 当我们将数据清除了后,就应该给frame_buf中写入数据了。返回值read_bytes表示真正读取的数据是多少
    511. size_t read_bytes = fread(frame_buf, 1, frame_bytes, infile);
    512. if(read_bytes <= 0){ //注意的是:fread 的返回值正常情况下为真正的读取的数据是多少;如果返回0,表示第二个参数或者第三个参数为0;如果读取到文件尾或者出现错误,则返回负值;fread 不区别文件尾和错误,而调用者必须用 feof 和 ferror 鉴别出现者为何。
    513. if (feof(infile)) //feof 检查是否已抵达给定文件流的结尾。说明是正常读取文件结束。
    514. printf("Error reading infile: unexpected end of file\n");
    515. else if (ferror(infile)) {
    516. perror("Error reading infile");
    517. }
    518. printf("read file finish\n");
    519. break;
    520. }
    521. //如果走到这一步,说明读取的数据是有的,那么开始对这部分数据进行处理
    522. //那么现在这个数据都早frame_buf中了,这个frame_buf是我们自定义的一块内存,这块内存的中的数据是从pcm中获取的,
    523. //注意的是:能存储在文件中的pcm数据都是交错模式的,但是ffmpeg 的AAC编码器是不能处理 交错模式的pcm的,因此我们还需要将交错模式的pcm转换成 planar 模式的pcm
    524. //av_frame_make_writable:确保AVFrame是可写的,尽可能避免数据的复制。 如果AVFrame不是是可写的,将分配新的buffer和复制数据。
    525. //确保该frame可写, 如果编码器内部保持了内存参考计数,则需要重新拷贝一个备份.目的是新写入的数据和编码器保存的数据不能产生冲突
    526. ret = av_frame_make_writable(frame);
    527. if(ret != 0)
    528. printf("av_frame_make_writable failed, ret = %d\n", ret);
    529. //注意我们这时候读取到的frame_buf中的数据是 infile文件中的数据。也就是pcm文件,那么这个pcm一般情况下都是 交错模式的(因为只有交错模式的才能播放,一般存放在本地文件的pcm都是交错模式的)
    530. //ffmpeg自带的aac 只能处理 AV_SAMPLE_FMT_FLTP ; fdk-aac只能处理的是 AV_SAMPLE_FMT_S16模式
    531. //这里判断,如果使用的fdk-libaac,且我们给编码器设置的就是 AV_SAMPLE_FMT_S16 ,就可以直接处理,因为fdk-libaac是可以直接处理 AV_SAMPLE_FMT_S16
    532. if((strcmp(encoderAVCodec->name, "libfdk_aac")==0) && AV_SAMPLE_FMT_S16 == frame->format) {
    533. // 将读取到的PCM数据填充到frame去,但要注意格式的匹配, 是planar还是packed都要区分清楚
    534. ret = av_samples_fill_arrays(frame->data, frame->linesize,
    535. frame_buf, frame->ch_layout.nb_channels,
    536. frame->nb_samples, (AVSampleFormat)frame->format, 0);
    537. if(ret <0 ){
    538. printf("av_samples_fill_arrays failed AV_SAMPLE_FMT_S16 \n");
    539. goto pcmtoaacend;
    540. }
    541. } else {
    542. // 走到这里,就是我们读取的数据 是从pcm 的infile中获得的,大部分也 是交错模式的 ,那么这里就有需要将这个交错模式的pcm,转成ffmpeg 内部AAC支持的 AV_SAMPLE_FMT_FLTP planar 模式的,才能处理
    543. // 这里为了简单,本地测试是直接用的 48000 的 f32le的pcm,因此要将本地的f32le packed模式的数据转为float palanar(通过 f32le_convert_to_fltp),
    544. memset((void *)frame_buf_trans, 0, frame_bytes);
    545. f32le_convert_to_fltp((float *)frame_buf, (float *)frame_buf_trans, frame->nb_samples);
    546. ret = av_samples_fill_arrays(frame->data, frame->linesize,
    547. frame_buf_trans, frame->ch_layout.nb_channels,
    548. frame->nb_samples, (AVSampleFormat)frame->format, 0);
    549. if(ret <0 ){
    550. printf("av_samples_fill_arrays failed AV_SAMPLE_FMT_S16 \n");
    551. goto pcmtoaacend;
    552. }
    553. }
    554. //走到这里数据就已经在 frame中了,那么下来就要对这个frame进行编码了
    555. // 设置pts
    556. pts += frame->nb_samples;
    557. frame->pts = pts; // 使用采样率作为pts的单位,具体换算成秒 pts*1/采样率
    558. ret = encode(encoderAVCodecContext, frame, pkt, outfile);
    559. if(ret < 0) {
    560. printf("encode failed\n");
    561. break;
    562. }
    563. }
    564. //14/* 冲刷编码器 */
    565. encode(encoderAVCodecContext, NULL, pkt, outfile);
    566. pcmtoaacend:
    567. // 关闭文件
    568. fclose(infile);
    569. fclose(outfile);
    570. if(frame_buf){
    571. free((void *)frame_buf);
    572. }
    573. if(frame_buf_trans){
    574. free((void *)frame_buf_trans);
    575. }
    576. av_packet_free(&pkt);//av_packet_free是安全的,即使pkt是null,参考源码可知
    577. av_frame_free(&frame);//av_frame_free是安全的,即使frame是null,参考源码可知
    578. if(!infile){
    579. fclose(infile);
    580. }
    581. if(!outfile){
    582. fclose(outfile);
    583. }
    584. if(encoderAVCodecContext){
    585. avcodec_free_context(&encoderAVCodecContext);
    586. }
    587. return ret;
    588. }

    问题一:

    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  不是编码器,可能是解码器

    1. /**
    2. * Read encoded data from the encoder.
    3. *
    4. * @param avctx codec context
    5. * @param avpkt This will be set to a reference-counted packet allocated by the
    6. * encoder. Note that the function will always call
    7. * av_packet_unref(avpkt) before doing anything else.
    8. * @retval 0 success
    9. * @retval AVERROR(EAGAIN) output is not available in the current state - user must
    10. * try to send input
    11. * @retval AVERROR_EOF the encoder has been fully flushed, and there will be no
    12. * more output packets
    13. * @retval AVERROR(EINVAL) codec not opened, or it is a decoder
    14. * @retval "another negative error code" legitimate encoding errors
    15. */
    16. int avcodec_receive_packet(AVCodecContext *avctx, AVPacket *avpkt);

  • 相关阅读:
    旅游行业电商平台:数字化转型的引擎与未来发展趋势
    文心一言Plugin实战来了,测试开发旅游攻略助手
    ffmpeg 支持用h265编码的rtmp
    【C++之数组与指针】随机输入整数存入数组并用指针遍历
    git 创建分支并提交
    Vue3从入门到精通(一)
    Gin框架源码解析
    zipfile教程
    Servlet
    7月30号PMP考试延期后我们应该做什么?
  • 原文地址:https://blog.csdn.net/hunandede/article/details/139806722