当前位置:   article > 正文

音视频开发29 FFmpeg 音频编码- 流程以及重要API,该章节使用AAC编码说明_新版ffmpeg编码aac注意事项

新版ffmpeg编码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 "<codec_name>_<wrapper_name>").
  312. const char *wrapper_name;
  313. */
  314. /**
  315. *编解码器实现的组名称。
  316. *这是支持此编解码器的包装器的简短符号名称。
  317. 包装器对编解码器使用某种外部实现,
  318. 诸如外部库或由OS或硬件提供的编解码器实现。
  319. *如果此字段为NULL,则这是一个内置的libavcodec本机编解码器。
  320. *如果非NULL,在大多数情况下,这将是AVCodec.name中的后缀(通常AVCodec.name的形式为“<codec_name>_<wrapper_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);

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/知新_RL/article/detail/970074
推荐阅读
相关标签
  

闽ICP备14008679号