当前位置:   article > 正文

FFmpeg音频解码流程详解及简单demo参考_av_bsf_send_packet

av_bsf_send_packet

        本文主要讲解FFmpeg的音频解码具体流程,API使用。最后再以一个非常简单的demo演示将一个mp3格式的音频文件解码为原始数据pcm文件。 本文主要基于FFmpeg音频解码新接口。

一、FFmpeg音频解码API调用流程图      

   API接口简单大体讲解如下:

  1. av_register_all():注册FFmpeg所有编解码器。
  2. avformat_open_input():打开音频地址并获取里面的内容(解封装)
  3. avformat_find_stream_info():获取内容
  4. avcodec_find_decoder():寻找解码器
  5. avcodec_alloc_context3():申请解码器相关上下文
  6. avcodec_open2():打开解码器
  7. av_read_frame():从原始有格式文件中一帧一帧读取出来
  8. avcodec_send_packet():解码核心接口新接口,发送一帧音频给解码器。即是AVPacket(存储AAC等音频格式码流数据)。
  9. avcodec_receive_frame():解码核心接口新接口,接收解码器解码后的一帧视频,AVFrame(PCM原始数据)。

二、音频解码过程API调用流程

1、注册各大组件

        这一步是ffmpeg的任何程序的第一步都是需要先注册ffmpeg相关的各大组件的:

  1. //注册各大组件
  2. av_register_all();
  3. LOGE("注册成功")

2、打开音频文件并获取相关上下文

        在解码之前我们得获取里面的内容,这一步就是打开地址并且获取里面的内容。其中avFormatContext是内容的一个上下文。

        并使用avformat_open_input打开播放源,inputPath为输入的地址,也就是音频文件,然后使用avformat_find_stream_info从获取的内容中寻找相关流。

  1. //打开音频地址并获取里面的内容(解封装)
  2. error = avformat_open_input(&avFormatContext, inputPath, NULL, NULL);
  3. if (error < 0){
  4. LOGE("打开音频文件失败\n");
  5. return false;
  6. }
  7. if (avformat_find_stream_info(avFormatContext, NULL) < 0){
  8. LOGE("获取内容失败")
  9. return false;
  10. }

3、寻找音频流

        我们在上面已经获取了内容,我们再从中找出相对应的音频流。

  1. //获取音频的编码信息
  2. AVCodecParameters *origin_par = NULL;
  3. int mAudioStreamIdx = -1;
  4. mAudioStreamIdx = av_find_best_stream(avFormatContext, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
  5. if (mAudioStreamIdx < 0) {
  6. av_log(NULL, AV_LOG_ERROR, "Can't find audio stream in input file\n");
  7. return false;
  8. }
  9. LOGE("成功找到音频流")

4、获取并打开解码器

        如果要进行解码,那么得有解码器并打开解码器。在这一步中我加了个过滤器的相关配置,这个东西不是非必要的,所以我加了个宏ABSFILTER_ENABLE可以选择是否打开。

  1. // 寻找解码器 {start
  2. AVCodec *mAcodec = NULL;
  3. AVCodecContext *mAvContext = NULL;
  4. mAcodec = avcodec_find_decoder(origin_par->codec_id);
  5. mAvContext = avcodec_alloc_context3(mAcodec);
  6. if (!mAcodec || !mAvContext){
  7. return false;
  8. }
  9. #if ABSFILTER_ENABLE
  10. //过滤器相关配置,这个与音频码流格式相关
  11. const AVBitStreamFilter * absFilter = NULL;
  12. AVBSFContext *absCtx = NULL;
  13. AVCodecParameters *codecpar = NULL;
  14. absFilter = av_bsf_get_by_name("mp3decomp");
  15. //过滤器分配内存
  16. av_bsf_alloc(absFilter, &absCtx);
  17. //添加解码器属性
  18. codecpar = avFormatContext->streams[mAudioStreamIdx]->codecpar;
  19. avcodec_parameters_copy(absCtx->par_in, codecpar);
  20. absCtx->time_base_in = avFormatContext->streams[mAudioStreamIdx]->time_base;
  21. //初始化过滤器上下文
  22. av_bsf_init(absCtx);
  23. #endif
  24. // 打开解码器
  25. if (avcodec_open2(mAvContext, mAcodec, NULL) != 0){
  26. LOGE("打开失败")
  27. return false;
  28. }
  29. LOGE("解码器打开成功")
  30. // 寻找解码器 end}

5、申请AVPacket和AVFrame以及相关设置

        申请AVPacket和AVFrame,其中AVPacket的作用是:保存解码之前的数据和一些附加信息等;AVFrame的作用是:存放解码过后的数据。

  1. //申请AVPacket
  2. AVPacket *packet = (AVPacket *) av_malloc(sizeof(AVPacket));
  3. av_init_packet(packet);
  4. //申请AVFrame
  5. AVFrame *frame = av_frame_alloc();//分配一个AVFrame结构体,AVFrame结构体一般用于存储原始数据,指向解码后的原始帧

6、开始解码

        接下来就可以开始解码,如下是解码的核心段代码,这里同样有上面所说的过滤器的,这个可以先不关心:

  1. {
  2. // 发送待解码包
  3. int result = avcodec_send_packet(mAvContext, packet);
  4. av_packet_unref(packet);
  5. if (result < 0){
  6. av_log(NULL, AV_LOG_ERROR, "Error submitting a packet for decoding\n");
  7. continue;
  8. }
  9. // 接收解码数据
  10. while (result >= 0){
  11. result = avcodec_receive_frame(mAvContext, frame);
  12. if (result == AVERROR_EOF)
  13. break;
  14. else if (result == AVERROR(EAGAIN)){
  15. result = 0;
  16. break;
  17. }
  18. else if (result < 0){
  19. av_log(NULL, AV_LOG_ERROR, "Error decoding frame\n");
  20. av_frame_unref(frame);
  21. break;
  22. }
  23. av_frame_unref(frame);
  24. }
  25. }

7、解码并保存为PCM文件

        PCM是音频解码后的原始数据,我们将解码后的数据保存为PCM文件,用于分析。在上一步中解码后的地方加上保存为PCM文件的操作。

        另外这里有用了上面所说的过滤器的东西,我们暂时先不关心。

        完整过程如下:

  1. while(1)
  2. {
  3. int ret = av_read_frame(avFormatContext, packet);
  4. if (ret != 0){
  5. av_strerror(ret,buf,sizeof(buf));
  6. LOGE("--%s--\n",buf);
  7. av_packet_unref(packet);
  8. break;
  9. }
  10. if (ret >= 0 && packet->stream_index != mAudioStreamIdx){
  11. av_packet_unref(packet);
  12. continue;
  13. }
  14. #if ABSFILTER_ENABLE
  15. if (av_bsf_send_packet(absCtx, packet) < 0){
  16. LOGE("av_bsf_send_packet faile \n");
  17. av_packet_unref(packet);
  18. continue;
  19. }
  20. if (av_bsf_receive_packet(absCtx, packet) < 0) {
  21. LOGE("av_bsf_receive_packet faile \n");
  22. av_packet_unref(packet);
  23. continue;
  24. }
  25. #endif
  26. {
  27. // 发送待解码包
  28. int result = avcodec_send_packet(mAvContext, packet);
  29. av_packet_unref(packet);
  30. if (result < 0){
  31. av_log(NULL, AV_LOG_ERROR, "Error submitting a packet for decoding\n");
  32. continue;
  33. }
  34. // 接收解码数据
  35. while (result >= 0){
  36. result = avcodec_receive_frame(mAvContext, frame);
  37. if (result == AVERROR_EOF)
  38. break;
  39. else if (result == AVERROR(EAGAIN)){
  40. result = 0;
  41. break;
  42. }
  43. else if (result < 0){
  44. av_log(NULL, AV_LOG_ERROR, "Error decoding frame\n");
  45. av_frame_unref(frame);
  46. break;
  47. }
  48. // 写文件保存音频数据
  49. data_size = av_get_bytes_per_sample(mAvContext->sample_fmt);
  50. if (data_size < 0) {
  51. /* This should not occur, checking just for paranoia */
  52. LOGE("Failed to calculate data size\n");
  53. return false;
  54. }
  55. for (i = 0; i < frame->nb_samples; i++)
  56. for (ch = 0; ch < mAvContext->channels; ch++)
  57. fwrite(frame->data[ch] + data_size*i, 1, data_size, fp_PCM);
  58. av_frame_unref(frame);
  59. }
  60. }
  61. }

8、收尾释放

        最后释放相关资源

  1. fclose(fp_PCM);
  2. av_frame_free(&frame);
  3. avcodec_close(mAvContext);
  4. avformat_free_context(avFormatContext);
  5. #if ABSFILTER_ENABLE
  6. av_bsf_free(&absCtx);
  7. absCtx = NULL;
  8. #endif

        至此,mp3格式的文件就被解码出原始数据并保存为PCM文件。

三、demo运行

         demo中指定了视频源文件是/sdcard/input.mp3,编码后的pcm文件是/sdcard/audioDecodeOut.pcm,如下代码,若要改文件,可以在此处修改:

  1. @Override
  2. public void run() {
  3. String PATH = Environment.getExternalStorageDirectory().getPath();
  4. //视频解码
  5. String input = PATH + File.separator + "input.mp3";
  6. String output = PATH + File.separator + "audioDecodeOut.pcm";
  7. decode_audio(input,output);
  8. Toast.makeText(MainActivity.this, "音频解码完成,请自行从手机中拉取pcm文件", Toast.LENGTH_SHORT).show();
  9. }

        运行后截图如下:

        点击"START AUDIO DECODE"按钮,开始对音频文件进行解码,界面不会有什么提示,等解码完成后会弹出提示词 "音频解码完成,请自行从手机中拉取pcm文件"

        我们从运行log中也能看到一些信息,如下:

        当有--- audio decode finished ---打印出来说明已经解码完成。

        然后我们adb shell进去手机看看解码前后的文件:

         可以看到解码后的pcm原始数据文件比解码前是大很多的。

四、分析PCM文件

        接下来我们分析下解码后的PCM文件是否正确。

        首先我们需要先知道一下解码前的mp3文件的一些信息,比如声道数,采样率等等。这些我们可以通过FFmpeg的ffprobe来获取,这里我在我的Ubuntu电脑上通过ffmpeg命令行获取一下:

        红圈圈中的就是几个比较重要的信息,看到是采样率是44100,双声道,fltp格式。

        接下来使用PCM分析工具,我使用的是Audacity:

         打开工具,并点击导入原始数据:

        然后选择pcm文件,弹出格式选择的弹窗,采样率就是刚才的44100,声道是双声道也就是2声道,字节序一般是小尾端,编码是fltp,也就是选32-bit float。

         点击导入后,显示如下,可以看到音频数据的波形图,然后点击右上方的播放按钮播放pcm文件,若音乐听起来正常,与之前的mp3文件一致,则说明解码后的pcm数据正常。

         完整例子已经放到github上,如下:

https://github.com/weekend-y/FFmpeg_Android_Demo/tree/master/demo6_audioDecode

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

闽ICP备14008679号