关于ffmpeg:FFmpeg开发笔记七ffmpeg解码音频保存为PCM并使用软件播放

4次阅读

共计 15105 个字符,预计需要花费 38 分钟才能阅读完成。

若该文为原创文章,未经容许不得转载
原博主博客地址:https://blog.csdn.net/qq21497936
原博主博客导航:https://blog.csdn.net/qq21497936/article/details/102478062
本文章博客地址:https://blog.csdn.net/qq21497936/article/details/108799279
各位读者,常识无穷而人力有穷,要么改需要,要么找专业人士,要么本人钻研

红瘦子 (红模拟) 的博文大全:开发技术汇合(蕴含 Qt 实用技术、树莓派、三维、OpenCV、OpenGL、ffmpeg、OSG、单片机、软硬联合等等)继续更新中 …(点击传送门)

FFmpeg 和 SDL 开发专栏(点击传送门)

上一篇:《FFmpeg 开发笔记(六):ffmpeg 解码视频并应用 SDL 同步工夫显示播放》
下一篇:敬请期待

前言

  本篇解码音频,包含从 mp3 等文件中抽取音频流的 pcm,从视频文件中抽取音频流的 pcm。
  本文章篇幅绝对较长,码字作图不易,请各位读者且行且珍惜。

音频基础知识

  音频的几个关键因素请查看:《SDL 开发笔记(二):音频根底介绍、应用 SDL 播放音频

Demo

  导入原始文件,设置好数据类型、升到、采样率
  

  
  

软件下载地址

  CSDN:https://download.csdn.net/download/qq21497936/12888731
  QQ 群:1047134658(点击“文件”搜寻“audacity”,群内与博文同步更新)

FFmpeg 解码音频

ffmpeg 解码音频流程

  ffmpeg 解码音频转码根本流程如下:
  

步骤一:注册:

  应用 ffmpeg 对应的库,都须要进行注册,能够注册子项也能够注册全副。

步骤二:关上文件:

  关上文件,依据文件名信息获取对应的 ffmpeg 全局上下文。

步骤三:探测流信息:

  肯定要探测流信息,拿到流编码的编码格局,不探测流信息则其流编码器拿到的编码类型可能为空,后续进行数据转换的时候就无奈通晓原始格局,导致谬误。

步骤四:查找对应的解码器

  根据流的格局查找解码器,软解码还是硬解码是在此处决定的,然而特地留神是否反对硬件,须要本人查找本地的硬件解码器对应的标识,并查问其是否反对。广泛操作是,枚举反对文件后缀解码的所有解码器进行查找,查找到了就是能够硬解了(此处,不做过多的探讨,对应硬解码后续会有文章进行进一步钻研)。
(留神:解码时查找解码器,编码时查找编码器,两者函数不同,不要弄错了,否则后续能关上然而数据是错的)

步骤五:关上解码器

  关上获取到的解码器。

步骤六:申请重采样构造体

  此处特地留神,基本上解码的数据都是 pcm 格局,pcm 格局也分很多种,若 8 位整形,无符号 8 为整形,32 位浮点,带 P 和不带 P 的,带 P 的数据真存储为 LRLRLRLR 不带 P 的为 LLLLRRRR,还有单通道、双通道和多通道,通道又波及到了声道的定位枚举,所以 pcm 原始数据也多种多样,对齐进行重弄采样使其输入的 pcm 格局参数特点统一。

步骤七:重采样初始化

  重采样构造体设置好后,须要设置失效。

步骤八:解封装获取其中一个数据包。

  数据包是封装在容器中的一个数据包。

步骤九:分组数据包送往解码器

  拿取封装的一个 packet 后,判断 packet 数据的类型进行送往解码器解码。

步骤十:从解码器缓存中获取解码后的数据

  一个包可能存在多组数据,老的 api 获取的是第一个,新的 api 离开后,能够循环获取,直至获取不到跳转“步骤十二”

步骤十一:样本点重采样

  应用冲残阳函数联合转换构造体对编码的数据进行转换,拿到重采样后的音频原始数据。

步骤十二:自行处理

  拿到了原始数据自行处理。
  继续执行“步骤八”,若步骤八获取不到数据则执行“步骤十二”

步骤十三:开释 QAVPacket

  此处要独自列出是因为,其实很多网上和开发者的代码:
  在进入循环解码前进行了 av_new_packet,循环中未 av_free_packet,造成内存溢出;
  在进入循环解码前进行了 av_new_packet,循环中进行 av_free_pakcet,那么一次 new 对应无数次 free,在编码器上是不合乎前后一一对应标准的。
  查看源代码,其实能够发现 av_read_frame 时,主动进行了 av_new_packet(),那么其实对于 packet,只须要进行一次 av_packet_alloc()即可,解码完后 av_free_packet。
  执行完后,返回执行“步骤八:获取一帧 packet”,一次循环完结。

步骤十四:开释冲重采样构造体

  全副解码实现后,依照申请程序,反向顺次进行对应资源的开释。

步骤十五:敞开解码 / 编码器

  敞开之前关上的解码 / 编码器。

步骤十六:敞开上下文

  敞开文件上下文后,要对之前申请的变量依照申请的程序,顺次开释。

ffmpeg 解码音频相干变量

  与视频解码通用变量请参照博文《FFmpeg 开发笔记(四):ffmpeg 解码的根本流程详解》中的“ffmpeg 解码相干变量”。

SwrContext

  重采样的构造体,最要害的是几个参数,输出的采样频率、通道布局、数据格式,输入的采样频率、通道布局、数据格式。

ffmpeg 解码音频流程相干函数原型

  与视频解码通用函数原型请参照博文《FFmpeg 开发笔记(四):ffmpeg 解码的根本流程详解》中的 ”ffmpeg 解码相干函数原型。

swr_alloc_set_opts

struct SwrContext *swr_alloc_set_opts(struct SwrContext *s,
                                  int64_t out_ch_layout,
                                  enum AVSampleFormat out_sample_fmt,
                                  int out_sample_rate,
                                  int64_t  in_ch_layout,
                                  enum AVSampleFormat  in_sample_fmt,
                                  int  in_sample_rate,
                                  int log_offset,
                                  void *log_ctx);

  调配并设置重采样的构造体上下文。

  • 参数一:输出须要设置的重采样构造体,如果为空,则会由此函数外部进行调配。
  • 参数二:输入的通道布局(转换后的)
  • 参数三:输入的样本格局(转换后的)

  
  带 P 和不带 P,关系到了 AVFrame 中的 data 的数据排列,不带 P,则是 LRLRLRLRLR 排列,带 P 则是 LLLLLRRRRR 排列,若是双通道则带 P 则意味着 data[0]全是 L,data[1]全是 R(留神:这是采样点不是字节),PCM 播放器播放的文件须要的是 LRLRLRLR 的。

    • 参数四:输入的采样率(转换后的)
    • 参数五:输出的通道布局(转换前的)
    • 参数六:输出的样本格局(转换前的)
    • 参数七:输出的采样率(转换前的)
    • 参数八:日志等级,疏忽间接 0
    • 参数九:日志,疏忽间接 0

    swr_init

    int swr_init(struct SwrContext *s);

      初始化采样器,使采样器失效。

    swr_free

    void swr_free(struct SwrContext **s);

      开释给定的 SwrContext 并将指针设置为 NULL。

    ffmpeg3 之后的新解码 api 解码函数原型

    avcodec_send_packet:ffmpeg3 新增解码发送数据包给解码器

    int avcodec_send_packet(AVCodecContext *avctx, const AVPacket *avpkt);

      将原始分组数据发送给解码器。
      在外部,此调用将复制相干的 AVCodeContext 字段,这些字段能够影响每个数据包的解码,并在理论解码数据包时利用这些字段。(例如 AVCodeContext.skip_frame,这可能会批示解码器抛弃应用此函数发送的数据包所蕴含的帧。)
      这个函数能够了解为 ffmpeg 为多线程筹备的,将解码数据帧包送入编码器了解为一个线程,将从编码器获取解码后的数据了解为一个线程。

    • 参数一:编解码器上下文
    • 参数二 :avpkt 输出的 AVPacket。通常,这将是一个繁多的视频帧,或几个残缺的音频帧。数据包的所有权归调用者所有,解码器不会写入数据包。 解码器能够创立对分组数据的援用(如果分组没有被援用计数,则复制它)。与旧的 API 不同,数据包总是被齐全消耗掉,如果它蕴含多个帧(例如某些音频编解码器),则须要在发送新数据包之前屡次调用 avcodec_receive_frame()。它能够是 NULL(或者数据设置为 NULL 且大小设置为 0 的 AVPacket);在这种状况下,它被认为是一个刷新包,它收回流完结的信号。发送第一个刷新包将返回胜利。后续的是不必要的,将返回 AVERROR ou EOF。如果解码器仍有帧缓冲,它将在发送刷新包后返回它们。

    avcodec_receive_frame:ffmpeg3 新增解码从解码器获取解码后的帧

    int avcodec_receive_frame(AVCodecContext *avctx, AVFrame *frame);

      从解码器返回解码输入数据。这个函数能够了解为 ffmpeg 为多线程筹备的,将解码数据帧包送入编码器了解为一个线程,将从编码器获取解码后的数据了解为一个线程。

    • 参数一:编解码器上下文
    • 参数二:这将被设置为参考计数的视频或音频解码器调配的帧(取决于解码器类型)。请留神,函数在执行任何其余操作之前总是调用 av_frame_unref(frame),本人开释 frame,只有最初一帧不开释。

    Demo 源码

    解码音频不带重采样版本 v1.3.0

    void FFmpegManager::testDecodeAudio()
    {
        QString fileName = "test/1.avi";
    //    QString fileName = "test/1.mp4";
    //    QString fileName = "E:/testFile2/1.mp3";
        QString outFileName = "E:/1.pcm";
        // ffmpeg 相干变量事后定义与调配
        AVFormatContext *pAVFormatContext = 0;          // ffmpeg 的全局上下文,所有 ffmpeg 操作都须要
        AVCodecContext *pAVCodecContext = 0;            // ffmpeg 编码上下文
        AVCodec *pAVCodec = 0;                          // ffmpeg 编码器
        AVPacket *pAVPacket = 0;                        // ffmpag 单帧数据包
        AVFrame *pAVFrame = 0;                          // ffmpeg 单帧缓存
        QFile file(outFileName);                        // Qt 文件操作
    
        int ret = 0;                                    // 函数执行后果
        int audioIndex = -1;                            // 音频流所在的序号
        int numBytes = 0;
    
        pAVFormatContext = avformat_alloc_context();    // 调配
        pAVPacket = av_packet_alloc();                  // 调配
        pAVFrame = av_frame_alloc();                    // 调配
    
        if(!pAVFormatContext || !pAVPacket || !pAVFrame)
        {
            LOG << "Failed to alloc";
            goto END;
        }
        // 步骤一:注册所有容器和编解码器(也能够只注册一类,如注册容器、注册编码器等)av_register_all();
    
        // 步骤二:关上文件(ffmpeg 胜利则返回 0)
        LOG << "文件:" << fileName << ",是否存在:" << QFile::exists(fileName);
    //    ret = avformat_open_input(&pAVFormatContext, fileName.toUtf8().data(), pAVInputFormat, 0);
        ret = avformat_open_input(&pAVFormatContext, fileName.toUtf8().data(), 0, 0);
        if(ret)
        {
            LOG << "Failed";
            goto END;
        }
    
        // 步骤三:探测流媒体信息
        ret = avformat_find_stream_info(pAVFormatContext, 0);
        if(ret < 0)
        {LOG << "Failed to avformat_find_stream_info(pAVCodecContext, 0)";
            goto END;
        }
        LOG << "视频文件蕴含流信息的数量:" << pAVFormatContext->nb_streams;
    
        // 步骤四:提取流信息, 提取视频信息
        for(int index = 0; index < pAVFormatContext->nb_streams; index++)
        {pAVCodecContext = pAVFormatContext->streams[index]->codec;
            switch (pAVCodecContext->codec_type)
            {
            case AVMEDIA_TYPE_UNKNOWN:
                LOG << "流序号:" << index << "类型为:" << "AVMEDIA_TYPE_UNKNOWN";
                break;
            case AVMEDIA_TYPE_VIDEO:
                LOG << "流序号:" << index << "类型为:" << "AVMEDIA_TYPE_VIDEO";
                break;
            case AVMEDIA_TYPE_AUDIO:
                LOG << "流序号:" << index << "类型为:" << "AVMEDIA_TYPE_AUDIO";
                audioIndex = index;
                break;
            case AVMEDIA_TYPE_DATA:
                LOG << "流序号:" << index << "类型为:" << "AVMEDIA_TYPE_DATA";
                break;
            case AVMEDIA_TYPE_SUBTITLE:
                LOG << "流序号:" << index << "类型为:" << "AVMEDIA_TYPE_SUBTITLE";
                break;
            case AVMEDIA_TYPE_ATTACHMENT:
                LOG << "流序号:" << index << "类型为:" << "AVMEDIA_TYPE_ATTACHMENT";
                break;
            case AVMEDIA_TYPE_NB:
                LOG << "流序号:" << index << "类型为:" << "AVMEDIA_TYPE_NB";
                break;
            default:
                break;
            }
            // 曾经找打视频品流
            if(audioIndex != -1)
            {break;}
        }
    
        if(audioIndex == -1 || !pAVCodecContext)
        {
            LOG << "Failed to find video stream";
            goto END;
        }
        // 步骤五:对找到的音频流寻解码器
        pAVCodec = avcodec_find_decoder(pAVCodecContext->codec_id);
        if(!pAVCodec)
        {LOG << "Fialed to avcodec_find_decoder(pAVCodecContext->codec_id):"
                << pAVCodecContext->codec_id;
            goto END;
        }
    #if 0
        pAVCodecContext = avcodec_alloc_context3(pAVCodec);
        // 填充 CodecContext 信息
        if (avcodec_parameters_to_context(pAVCodecContext,
                                          pAVFormatContext->streams[audioIndex]->codecpar) < 0)
        {printf("Failed to copy codec parameters to decoder context!\n");
            goto END;
        }
    #endif
    
        // 步骤六:关上解码器
        ret = avcodec_open2(pAVCodecContext, pAVCodec, NULL);
        if(ret)
        {LOG << "Failed to avcodec_open2(pAVCodecContext, pAVCodec, pAVDictionary)";
            goto END;
        }
    
        // 打印
        LOG << "解码器名称:" <<pAVCodec->name
            << "通道数:" << pAVCodecContext->channels
            << "采样率:" << pAVCodecContext->sample_rate
            << "采样格局:" << pAVCodecContext->sample_fmt;
    
        file.open(QIODevice::WriteOnly | QIODevice::Truncate);
    
        // 步骤七:读取一帧数据的数据包
        while(av_read_frame(pAVFormatContext, pAVPacket) >= 0)
        {if(pAVPacket->stream_index == audioIndex)
            {
                // 步骤八:将封装包发往解码器
                ret = avcodec_send_packet(pAVCodecContext, pAVPacket);
                if(ret)
                {LOG << "Failed to avcodec_send_packet(pAVCodecContext, pAVPacket) ,ret =" << ret;
                    break;
                }
                // 步骤九:从解码器循环拿取数据帧
                while(!avcodec_receive_frame(pAVCodecContext, pAVFrame))
                {//                for(int index = 0; index < pAVFrame->linesize[0]; index++)
    //                {// 入坑一;字节交织谬误,单条音轨是好的,双轨存入文件,应用 pcm 的软件播放,则默认是 LRLRLRLR 的形式(采样点交织)
    //                    file.write((const char *)(pAVFrame->data[0] + index), 1);
    //                    file.write((const char *)(pAVFrame->data[1] + index), 1);
    //                }
                    // 入坑一;字节交织谬误,单条音轨是好的,双轨存入文件,应用 pcm 的软件播放,则默认是 LRLRLRLR 的形式(采样点交织)
    //                file.write((const char *)(pAVFrame->data[0], pAVFrame->linesize[0]);
    //                file.write((const char *)(pAVFrame->data[1], pAVFrame->linesize[0]);
                    // 输入为 2, S16P 格局是 2 字节
                    numBytes = av_get_bytes_per_sample(pAVCodecContext->sample_fmt);
    //                LOG << "numBytes =" << numBytes;
                    /*
                        P 示意 Planar(立体),其数据格式排列形式为 (特地记住,该处是以点 nb_samples 采样点来交织,不是以字节交织):
                        LLLLLLRRRRRRLLLLLLRRRRRRLLLLLLRRRRRRL...(每个 LLLLLLRRRRRR 为一个音频帧)而不带 P 的数据格式(即交织排列)排列形式为:LRLRLRLRLRLRLRLRLRLRLRLRLRLRLRLRLRLRL...(每个 LR 为一个音频样本)*/
                    // 应用命令行提取 pcm ffmpeg.exe -i 1.mp3 -f s16le -ar 44100 -ac 2 -acodec pcm_s16le D:/2.pcm
                    for (int index = 0; index < pAVFrame->nb_samples; index++)
                    {for (int channel = 0; channel < pAVCodecContext->channels; channel++)  // 交织的形式写入, 大部分 float 的格局输入
                        {file.write((char *)pAVFrame->data[channel] + numBytes * index, numBytes);
                        }
                    }
                    av_free_packet(pAVPacket);
                }
            }
        }
        file.close();
    END:
        LOG << "开释回收资源";
        if(pAVFrame)
        {av_frame_free(&pAVFrame);
            pAVFrame = 0;
            LOG << "av_frame_free(pAVFrame)";
        }
        if(pAVPacket)
        {av_free_packet(pAVPacket);
            pAVPacket = 0;
            LOG << "av_free_packet(pAVPacket)";
        }
        if(pAVCodecContext)
        {avcodec_close(pAVCodecContext);
            pAVCodecContext = 0;
            LOG << "avcodec_close(pAVCodecContext);";
        }
        if(pAVFormatContext)
        {avformat_close_input(&pAVFormatContext);
            avformat_free_context(pAVFormatContext);
            pAVFormatContext = 0;
            LOG << "avformat_free_context(pAVFormatContext)";
        }
    }

    解码音频重采样版本 v1.3.1

    void FFmpegManager::testDecodeAudioForPcm()
    {
    //    QString fileName = "test/1.avi";
        QString fileName = "E:/testFile/3.mp4";
    //    QString fileName = "E:/testFile2/1.mp3";
    
        QString outFileName = "D:/1.pcm";
    
        AVFormatContext *pAVFormatContext = 0;          // ffmpeg 的全局上下文,所有 ffmpeg 操作都须要
        AVCodecContext *pAVCodecContext = 0;            // ffmpeg 编码上下文
        AVCodec *pAVCodec = 0;                          // ffmpeg 编码器
        AVPacket *pAVPacket = 0;                        // ffmpag 单帧数据包
        AVFrame *pAVFrame = 0;                          // ffmpeg 单帧缓存
        SwrContext *pSwrContext = 0;                    // ffmpeg 音频转码
        QFile file(outFileName);                        // Qt 文件操作
    
        int ret = 0;                                    // 函数执行后果
        int audioIndex = -1;                            // 音频流所在的序号
        int numBytes = 0;
        uint8_t * outData[2] = {0};
        int dstNbSamples = 0;                           // 解码指标的采样率
    
        int outChannel = 0;                             // 重采样后输入的通道
        AVSampleFormat outFormat = AV_SAMPLE_FMT_NONE;  // 重采样后输入的格局
        int outSampleRate = 0;                          // 重采样后输入的采样率
    
        pAVFormatContext = avformat_alloc_context();    // 调配
        pAVPacket = av_packet_alloc();                  // 调配
        pAVFrame = av_frame_alloc();                    // 调配
    
        if(!pAVFormatContext || !pAVPacket || !pAVFrame)
        {
            LOG << "Failed to alloc";
            goto END;
        }
        // 步骤一:注册所有容器和编解码器(也能够只注册一类,如注册容器、注册编码器等)av_register_all();
    
        // 步骤二:关上文件(ffmpeg 胜利则返回 0)
        LOG << "文件:" << fileName << ",是否存在:" << QFile::exists(fileName);
    //    ret = avformat_open_input(&pAVFormatContext, fileName.toUtf8().data(), pAVInputFormat, 0);
        ret = avformat_open_input(&pAVFormatContext, fileName.toUtf8().data(), 0, 0);
        if(ret)
        {
            LOG << "Failed";
            goto END;
        }
    
        // 步骤三:探测流媒体信息
        ret = avformat_find_stream_info(pAVFormatContext, 0);
        if(ret < 0)
        {LOG << "Failed to avformat_find_stream_info(pAVCodecContext, 0)";
            goto END;
        }
        LOG << "视频文件蕴含流信息的数量:" << pAVFormatContext->nb_streams;
    
        // 步骤四:提取流信息, 提取视频信息
        for(int index = 0; index < pAVFormatContext->nb_streams; index++)
        {pAVCodecContext = pAVFormatContext->streams[index]->codec;
            switch (pAVCodecContext->codec_type)
            {
            case AVMEDIA_TYPE_UNKNOWN:
                LOG << "流序号:" << index << "类型为:" << "AVMEDIA_TYPE_UNKNOWN";
                break;
            case AVMEDIA_TYPE_VIDEO:
                LOG << "流序号:" << index << "类型为:" << "AVMEDIA_TYPE_VIDEO";
                break;
            case AVMEDIA_TYPE_AUDIO:
                LOG << "流序号:" << index << "类型为:" << "AVMEDIA_TYPE_AUDIO";
                audioIndex = index;
                break;
            case AVMEDIA_TYPE_DATA:
                LOG << "流序号:" << index << "类型为:" << "AVMEDIA_TYPE_DATA";
                break;
            case AVMEDIA_TYPE_SUBTITLE:
                LOG << "流序号:" << index << "类型为:" << "AVMEDIA_TYPE_SUBTITLE";
                break;
            case AVMEDIA_TYPE_ATTACHMENT:
                LOG << "流序号:" << index << "类型为:" << "AVMEDIA_TYPE_ATTACHMENT";
                break;
            case AVMEDIA_TYPE_NB:
                LOG << "流序号:" << index << "类型为:" << "AVMEDIA_TYPE_NB";
                break;
            default:
                break;
            }
            // 曾经找打视频品流
            if(audioIndex != -1)
            {break;}
        }
    
        if(audioIndex == -1 || !pAVCodecContext)
        {
            LOG << "Failed to find video stream";
            goto END;
        }
        // 步骤五:对找到的音频流寻解码器
        pAVCodec = avcodec_find_decoder(pAVCodecContext->codec_id);
        if(!pAVCodec)
        {LOG << "Fialed to avcodec_find_decoder(pAVCodecContext->codec_id):"
                << pAVCodecContext->codec_id;
            goto END;
        }
    #if 0
        pAVCodecContext = avcodec_alloc_context3(pAVCodec);
        // 填充 CodecContext 信息
        if (avcodec_parameters_to_context(pAVCodecContext,
                                          pAVFormatContext->streams[audioIndex]->codecpar) < 0)
        {printf("Failed to copy codec parameters to decoder context!\n");
            goto END;
        }
    #endif
    
        // 步骤六:关上解码器
        ret = avcodec_open2(pAVCodecContext, pAVCodec, NULL);
        if(ret)
        {LOG << "Failed to avcodec_open2(pAVCodecContext, pAVCodec, pAVDictionary)";
            goto END;
        }
    
        // 打印
        LOG << "解码器名称:" <<pAVCodec->name << endl
            << "通道数:" << pAVCodecContext->channels << endl
            << "通道布局:" << av_get_default_channel_layout(pAVCodecContext->channels) << endl
            << "采样率:" << pAVCodecContext->sample_rate << endl
            << "采样格局:" << pAVCodecContext->sample_fmt;
    #if 1
        outChannel = 2;
        outSampleRate = 44100;
        outFormat = AV_SAMPLE_FMT_S16P;
    #endif
    #if 0
        outChannel = 2;
        outSampleRate = 48000;
        outFormat = AV_SAMPLE_FMT_FLTP;
    #endif
        LOG << "to" << endl
            << "通道数:" << outChannel << endl
            << "通道布局:" << av_get_default_channel_layout(outChannel) << endl
            << "采样率:" << outSampleRate << endl
            << "采样格局:" << outFormat;
        // 步骤七:获取音频转码器并设置采样参数初始化
        // 入坑二:通道布局与通道数据的枚举值是不同的,须要转换
        pSwrContext = swr_alloc_set_opts(0,                                 // 输出为空,则会调配
                                         av_get_default_channel_layout(outChannel),
                                         outFormat,                         // 输入的采样频率
                                         outSampleRate,                     // 输入的格局
                                         av_get_default_channel_layout(pAVCodecContext->channels),
                                         pAVCodecContext->sample_fmt,       // 输出的格局
                                         pAVCodecContext->sample_rate,      // 输出的采样率
                                         0,
                                         0);
        ret = swr_init(pSwrContext);
        if(ret < 0)
        {LOG << "Failed to swr_init(pSwrContext);";
            goto END;
        }
    
        file.open(QIODevice::WriteOnly | QIODevice::Truncate);
    
        outData[0] = (uint8_t *)av_malloc(1152 * 8);
        outData[1] = (uint8_t *)av_malloc(1152 * 8);
    
        // 步骤七:读取一帧数据的数据包
        while(av_read_frame(pAVFormatContext, pAVPacket) >= 0)
        {if(pAVPacket->stream_index == audioIndex)
            {
                // 步骤八:将封装包发往解码器
                ret = avcodec_send_packet(pAVCodecContext, pAVPacket);
                if(ret)
                {LOG << "Failed to avcodec_send_packet(pAVCodecContext, pAVPacket) ,ret =" << ret;
                    break;
                }
                // 步骤九:从解码器循环拿取数据帧
                while(!avcodec_receive_frame(pAVCodecContext, pAVFrame))
                {
                    // nb_samples 并不是每个包都雷同,遇见过第一个包为 47,第二个包开始为 1152 的
    //                LOG << pAVFrame->nb_samples;
                    // 步骤十:获取每个采样点的字节大小
                    numBytes = av_get_bytes_per_sample(outFormat);
                    // 步骤十一:批改采样率参数后,须要从新获取采样点的样本个数
                    dstNbSamples = av_rescale_rnd(pAVFrame->nb_samples,
                                                  outSampleRate,
                                                  pAVCodecContext->sample_rate,
                                                  AV_ROUND_ZERO);
                    // 步骤十二:重采样
                    swr_convert(pSwrContext,
                                outData,
                                dstNbSamples,
                                (const uint8_t **)pAVFrame->data,
                                pAVFrame->nb_samples);
                    // 第一次显示
                    static bool show = true;
                    if(show)
                    {
                        LOG << numBytes << pAVFrame->nb_samples << "to" << dstNbSamples;
                        show = false;
                    }
                    // 步骤十四:应用 LRLRLRLRLRL(采样点为单位,采样点有几个字节,交替存储到文件,可应用 pcm 播放器播放)for (int index = 0; index < dstNbSamples; index++)
                    {for (int channel = 0; channel < pAVCodecContext->channels; channel++)  // 交织的形式写入, 大部分 float 的格局输入
                        {
                            //  用于原始文件 jinxin 跟比照
    //                        file.write((char *)pAVFrame->data[channel] + numBytes * index, numBytes);
                            file.write((char *)outData[channel] + numBytes * index, numBytes);
                        }
                    }
                    av_free_packet(pAVPacket);
                }
            }
        }
        file.close();
    END:
        LOG << "开释回收资源";
        if(outData[0] && outData[1])
        {av_free(outData[0]);
            av_free(outData[1]);
            outData[0] = 0;
            outData[1] = 0;
            LOG << "av_free(outData[0])";
            LOG << "av_free(outData[1])";
        }
        if(pSwrContext)
        {swr_free(&pSwrContext);
            pSwrContext = 0;
        }
        if(pAVFrame)
        {av_frame_free(&pAVFrame);
            pAVFrame = 0;
            LOG << "av_frame_free(pAVFrame)";
        }
        if(pAVPacket)
        {av_free_packet(pAVPacket);
            pAVPacket = 0;
            LOG << "av_free_packet(pAVPacket)";
        }
        if(pAVCodecContext)
        {avcodec_close(pAVCodecContext);
            pAVCodecContext = 0;
            LOG << "avcodec_close(pAVCodecContext);";
        }
        if(pAVFormatContext)
        {avformat_close_input(&pAVFormatContext);
            avformat_free_context(pAVFormatContext);
            pAVFormatContext = 0;
            LOG << "avformat_free_context(pAVFormatContext)";
        }
    }

    工程模板 v1.3.0、v1.3.1

      对应工程模板 v1.3.0:减少解码音频裸存 pcmDemo
      对应工程模板 v1.3.1:减少解码音频重采样存 pcmDemo

    入坑

    入坑一:v1.3.0 输入的 pcm 文件音频播放声音变了

    起因

      存文件存错了,入坑一;字节交织谬误,单条音轨是好的,双轨存入文件,应用 pcm 的软件播放,则默认是 LRLRLRLR 的形式(采样点交织)。
    剖析音频文件如下:
      
      

    解决

      

    入坑二:v1.3.1 输入的 pcm 文件音频播放声音过快

    起因

      通道布局与通道数据的枚举值是不同的,须要转换

    解决

      

    入坑三:v1.3.1 输入的 pcm 文件音频升高采样率呈现滴答的声音

    起因

      重采样之后,采样率不同了,那么对应的工夫分片的数据包是雷同的,那么很显著,采样率低了,则数据应该缩小,工夫是一样长的,问题就处在转换函数须要计算一次采样率变了之后的理论采样点,关系到其输入的音频采样点数据,否则长了还好说,短了的话,存入更多就是谬误数据,天然就呈现声音不对。

    解决

      

    入坑四:v1.3.1 输入的 pcm 文件较短

    起因

      解码 mp4 封装时,获取到的第一个 AVFrame 的 nb_samples 不同,第一帧尾 32,本想做动静散布,后果踩坑.

    解决

      在最后面开拓认为的最大缓存空间,如下:
      

    上一篇:《FFmpeg 开发笔记(六):ffmpeg 解码视频并应用 SDL 同步工夫显示播放》
    下一篇:敬请期待

    原博主博客地址:https://blog.csdn.net/qq21497936
    原博主博客导航:https://blog.csdn.net/qq21497936/article/details/102478062
    本文章博客地址:https://blog.csdn.net/qq21497936/article/details/108799279

    正文完
     0