• FFmpeg音频解码


    目录

    1. 参考

    2. 音频解码过程

    3. FFmpeg流程

    4. avcodec编解码API介绍

    5. 示例代码

    1. 参考

    2. 音频解码过程 音频解码过程如下图所示:

    ​音频解码.png

    3. FFmpeg流程

    FFmpeg_decode_audio.png

    关键函数说明:

    • avcodec_find_decoder:根据指定的AVCodecID查找注册的解码器。

    • av_parser_init:初始化AVCodecParserContext。

    • avcodec_alloc_context3:为AVCodecContext分配内存。

    • avcodec_open2:打开解码器。

    • av_parser_parse2:解析获得一个Packet。

    • avcodec_send_packet:将AVPacket压缩数据给解码器。

    • avcodec_receive_frame:获取到解码后的AVFrame数据。

    • av_get_bytes_per_sample: 获取每个sample中的字节数。

    关键数据结构说明:

    • AVCodecParser:用于解析输入的数据流并把它分成一帧一帧的压缩编码数据。比较形象的说法就是把长长的一段连续的数据“切割”成一段段的数据。

    4. avcodec编解码API介绍 avcodec_send_packet、avcodec_receive_frame的API是FFmpeg3版本加入的。为了正确的使用它们,有必要阅读FFmpeg的文档说明[2]。 以下内容摘译自文档说明[2] FFmpeg提供了两组函数,分别用于编码和解码:

    • 解码:avcodec_send_packet()、avcodec_receive_frame()。

    • 解码:avcodec_send_frame()、avcodec_receive_packet()。

    API的设计与编解码的流程非常贴切。 建议的使用流程如下:

    1. 像以前一样设置并打开AVCodecContext。

    2. 输入有效的数据:

    • 解码:调用avcodec_send_packet()给解码器传入包含原始的压缩数据的AVPacket对象。

    • 编码:调用 avcodec_send_frame()给编码器传入包含解压数据的AVFrame对象。

    • 两种情况下推荐AVPacket和AVFrame都使用refcounted(引用计数)的模式,否则libavcodec可能需要对输入的数据进行拷贝。

    1. 在一个循环体内去接收codec的输出,即周期性地调用avcodec_receive_*()来接收codec输出的数据:

    • 解码:调用avcodec_receive_frame(),如果成功会返回一个包含未压缩数据的AVFrame。

    • 编码:调用avcodec_receive_packet(),如果成功会返回一个包含压缩数据的AVPacket。

    • 反复地调用avcodec_receive_packet()直到返回 AVERROR(EAGAIN)或其他错误。返回AVERROR(EAGAIN)错误表示codec需要新的输入来输出更多的数据。对于每个输入的packet或frame,codec一般会输出一个frame或packet,但是也有可能输出0个或者多于1个。

    1. 流处理结束的时候需要flush(洗刷) codec。因为codec可能在内部缓冲多个frame或packet,出于性能或其他必要的情况(如考虑B帧的情况)。 处理流程如下:

    • 调用avcodec_send_*()传入的AVFrame或AVPacket指针设置为NULL。 这将开启draining mode(排水模式)。

    • 反复地调用avcodec_receive_*()直到返回AVERROR_EOF的错误,这个方法这个时候不会返回AVERROR(EAGAIN)的错误,除非你忘记了开启draining mode。

    • codec可以重新开启,但是需要先调用 avcodec_flush_buffers()来重置codec。

    【关注+后台扣1,免费分享】资料包括《Andoird音视频开发必备手册+音视频学习视频+学习文档资料包+大厂面试真题+2022最新学习路线图》等等 

    说明:

    1. 编码或者解码刚开始的时候,codec可能接收了多个输入的frame或packet后还没有输出数据,直到内部的buffer被填充满。上面的使用流程可以处理这种情况。

    2. 理论上调用avcodec_send_*()的时候可能会发生AVERROR(EAGAIN)的错误,这只应该在有输出数据没有被接收的情况,你可以依赖这个机制来实现区别于上面建议流程的处理方式,比如反复地调用avcodec_send_*(),出现AVERROR(EAGAIN)错误的时候再去调用avcodec_receive_*()。

    3. 并不是所有的codec都遵循一个严格、可预测的数据处理流程,唯一可以保证的是 “调用avcodec_send_*()/avcodec_receive_*()返回AVERROR(EAGAIN)的时候去avcodec_receive_*()/avcodec_send_*()会成功,否则不应该返回AVERROR(EAGAIN)的错误。”一般来说,任何codec都不允许无限制地缓冲输入或者输出。

    4. 在同一个AVCodecContext上混合使用新旧API是不允许的,这将导致未定义的行为。

    5. 示例代码

    1. /**
    2. * audio decoding with libavcodec API example
    3. * FFmpeg/doc/examples/decode_audio.c
    4. */
    5. #include
    6. #include
    7. #include
    8. #include
    9. #include
    10. #include
    11. #define AUDIO_INBUF_SIZE 20480
    12. #define AUDIO_REFILL_THRESH 4096
    13. static void decode(AVCodecContext *dec_ctx, AVPacket *pkt, AVFrame *frame,
    14. FILE *outfile)
    15. {
    16. int i, ch;
    17. int ret, data_size;
    18. /* send the packet with the compressed data to the decoder */
    19. ret = avcodec_send_packet(dec_ctx, pkt);
    20. if (ret < 0) {
    21. fprintf(stderr, "Error submitting the packet to the decoder\n");
    22. exit(1);
    23. }
    24. /* read all the output frames (in general there may be any number of them */
    25. while (ret >= 0) {
    26. ret = avcodec_receive_frame(dec_ctx, frame);
    27. if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
    28. return;
    29. else if (ret < 0) {
    30. fprintf(stderr, "Error during decoding\n");
    31. exit(1);
    32. }
    33. data_size = av_get_bytes_per_sample(dec_ctx->sample_fmt);
    34. if (data_size < 0) {
    35. /* This should not occur, checking just for paranoia */
    36. fprintf(stderr, "Failed to calculate data size\n");
    37. exit(1);
    38. }
    39. for (i = 0; i < frame->nb_samples; i++)
    40. for (ch = 0; ch < dec_ctx->channels; ch++)
    41. fwrite(frame->data[ch] + data_size*i, 1, data_size, outfile);
    42. }
    43. }
    44. int main(int argc, char **argv)
    45. {
    46. const char *outfilename, *filename;
    47. const AVCodec *codec;
    48. AVCodecContext *c= NULL;
    49. AVCodecParserContext *parser = NULL;
    50. int len, ret;
    51. FILE *f, *outfile;
    52. uint8_t inbuf[AUDIO_INBUF_SIZE + AV_INPUT_BUFFER_PADDING_SIZE];
    53. uint8_t *data;
    54. size_t data_size;
    55. AVPacket *pkt;
    56. AVFrame *decoded_frame = NULL;
    57. if (argc <= 2) {
    58. fprintf(stderr, "Usage: %s \n", argv[0]);
    59. exit(0);
    60. }
    61. filename = argv[1];
    62. outfilename = argv[2];
    63. pkt = av_packet_alloc();
    64. /* find the MPEG audio decoder */
    65. codec = avcodec_find_decoder(AV_CODEC_ID_AAC);
    66. if (!codec) {
    67. fprintf(stderr, "Codec not found\n");
    68. exit(1);
    69. }
    70. parser = av_parser_init(codec->id);
    71. if (!parser) {
    72. fprintf(stderr, "Parser not found\n");
    73. exit(1);
    74. }
    75. c = avcodec_alloc_context3(codec);
    76. if (!c) {
    77. fprintf(stderr, "Could not allocate audio codec context\n");
    78. exit(1);
    79. }
    80. /* open it */
    81. if (avcodec_open2(c, codec, NULL) < 0) {
    82. fprintf(stderr, "Could not open codec\n");
    83. exit(1);
    84. }
    85. f = fopen(filename, "rb");
    86. if (!f) {
    87. fprintf(stderr, "Could not open %s\n", filename);
    88. exit(1);
    89. }
    90. outfile = fopen(outfilename, "wb");
    91. if (!outfile) {
    92. av_free(c);
    93. exit(1);
    94. }
    95. /* decode until eof */
    96. data = inbuf;
    97. data_size = fread(inbuf, 1, AUDIO_INBUF_SIZE, f);
    98. while (data_size > 0) {
    99. if (!decoded_frame) {
    100. if (!(decoded_frame = av_frame_alloc())) {
    101. fprintf(stderr, "Could not allocate audio frame\n");
    102. exit(1);
    103. }
    104. }
    105. ret = av_parser_parse2(parser, c, &pkt->data, &pkt->size,
    106. data, data_size,
    107. AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);
    108. if (ret < 0) {
    109. fprintf(stderr, "Error while parsing\n");
    110. exit(1);
    111. }
    112. data += ret;
    113. data_size -= ret;
    114. if (pkt->size)
    115. decode(c, pkt, decoded_frame, outfile);
    116. if (data_size < AUDIO_REFILL_THRESH) {
    117. memmove(inbuf, data, data_size);
    118. data = inbuf;
    119. len = fread(data + data_size, 1,
    120. AUDIO_INBUF_SIZE - data_size, f);
    121. if (len > 0)
    122. data_size += len;
    123. }
    124. }
    125. /* flush the decoder */
    126. pkt->data = NULL;
    127. pkt->size = 0;
    128. decode(c, pkt, decoded_frame, outfile);
    129. fclose(outfile);
    130. fclose(f);
    131. avcodec_free_context(&c);
    132. av_parser_close(parser);
    133. av_frame_free(&decoded_frame);
    134. av_packet_free(&pkt);
    135. return 0;
    136. }

  • 相关阅读:
    TikTok营销攻略:如何精准测试与优化品牌营销策略
    小趴菜实战Mac上安装Anaconda
    笔试强训第25天--编程题--(树根+星际密码)
    DDD架构
    旅游景区一体化污水处理设备产品特点
    【网页设计】期末大作业:化妆品主题——绿色大气的html5响应式化妆品护肤品肌肤网页设计(11页)
    HTML基础--Form表单--内联元素
    FreeRTOS中的内存分配策略
    【 OpenGauss源码学习 —— 列存储(CU)(二)】
    日志门面slf4j与常用的日志框架Log4j,Logback和Log4j2
  • 原文地址:https://blog.csdn.net/yinshipin007/article/details/126022561