若该文为原创文章,转载请注明原文出处
本文章博客地址:https://blog.csdn.net/qq21497936/article/details/108799279
各位读者,知识无穷而人力有穷,要么改需求,要么找专业人士,要么自己研究

红胖子(红模仿)的博文大全:开发技术集合(包含Qt实用技术、树莓派、三维、OpenCV、OpenGL、ffmpeg、OSG、单片机、软硬结合等等)持续更新中…(点击传送门)

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

上一篇:《FFmpeg开发笔记(六):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 0pAVCodecContext = 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.pcmfor (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 0pAVCodecContext = 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 1outChannel = 2;outSampleRate = 44100;outFormat = AV_SAMPLE_FMT_S16P;
#endif
#if 0outChannel = 2;outSampleRate = 48000;outFormat = AV_SAMPLE_FMT_FLTP;
#endifLOG << "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同步时间显示播放》
下一篇:《FFmpeg开发笔记(八):ffmpeg解码音频并使用SDL同步音频播放》

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

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

  1. ffmpeg api解码音频,得到pcm数据

    ffmpeg api解码音频,得到pcm数据,程序如下: extern "C" { #include "libavutil/avutil.h" #include ...

  2. qml开发笔记(七):输入元素鼠标输入MouseArea和键盘输入Keys

    若该文为原创文章,未经允许不得转载 原博主博客地址:https://blog.csdn.net/qq21497936 原博主博客导航:https://blog.csdn.net/qq21497936/ ...

  3. windows内核开发笔记七:内核开发OVERLAPPED结构体详解

    windows内核开发笔记七:内核开发OVERLAPPED结构体详解 typedef struct _OVERLAPPED {   DWORD Internal;   DWORD InternalHi ...

  4. FFmpeg开发(三):音频播放器的实现

    OpenSL基础 OpenSLES接口可以直接载Native层处理音频数据,减少了Java层到Native层在采集.播放和编解码过程中的数据拷贝. OpenSLES文档:OpenSL_ES_Speci ...

  5. FFmpeg 开发(07):FFmpeg + OpenGLES 实现 3D 全景播放器

    前文中,我们已经利用 FFmpeg + OpenGLES + OpenSLES 实现了一个多媒体播放器,本文将基于此播放器实现一个酷炫的 3D 全景播放器. 全景播放器原理 全景视频是由多台摄像机在一 ...

  6. JNI开发笔记(七)--aar库的生成和调用

    aar库的生成和调用 引 前言 1. 新建一个空工程 2. 新建一个Module 3. 移植JNI工程到Module中 4. 生成aar库与so库 5. 在另一个工程中调用aar库 引 JNI开发笔记 ...

  7. FFmpeg 开发(04):FFmpeg + OpenGLES 实现音频可视化播放

    本文基于上一篇文章 FFmpeg + OpenSLES 实现音频解码播放 ,利用 FFmpeg 对一个 Mp4 文件的音频流进行解码,然后将解码后的 PCM 音频数据进行重采样,最后利用 OpenSL ...

  8. FFmpeg 开发(09):FFmpeg、x264以及fdk-aac 编译整合

    前面系列文章已经讲过使用 FFmpeg 对音频和视频进行解码以及视频添加滤镜渲染,接下来文章将会介绍音频和视频编码相关,包括为视频添加好玩的滤镜.音频添加搞笑的变声效果. 关于 x264.fdk-aa ...

  9. FFmpeg 开发(10):FFmpeg 视频录制 - 视频添加滤镜和编码

    音视频开发中,视频编码是另一个重要的部分,基于 FFmpeg 软件解码前面系列文章已经介绍过了,接下来主要介绍软件编码这一块,包括视频编码.音频编码.为视频添加滤镜等.后期文章安排将介绍 Androi ...

最新文章

  1. 除了芯片 我们还应关注哪些核心技术
  2. 配置cisco路由器特定时间重启
  3. 运动目标检测_混合高斯背景建模
  4. Python爬虫-利用代理IP访问网页(requests)
  5. [css] 使用纯CSS代码实现动画的暂停与播放
  6. 点名册_骑士新书《万界点名册》十万收藏火爆气势不减当年修真聊天群
  7. oracle生成顺序编号,Oracle排序以及序号的输出 | 学步园
  8. Go 存储基础 — 内存结构体怎么写入文件?
  9. WordPress 5.1.1 发布,修复 CSRF 漏洞
  10. jsp(web作业)
  11. 服务中没有listen_Odoo 中的 IM(即时通讯)实现分析
  12. 软件工程综合实践第二次作业——结对编程
  13. win10装鸿蒙双系统,win10下能装双系统教程
  14. 复制虚拟机出现”适配器 的mac地址在保留地址范围内‘’
  15. 光滑曲线_光滑流形初步(2)——切向量与微分
  16. 开题报告中的研究现状怎么写?
  17. cad计算路网密度加调整路线
  18. 父亲将房子过户给后代需要交税吗
  19. MATLAB的画家之旅
  20. 请问想考软考,零基础的话,哪个证书最好考呢

热门文章

  1. Android查看手机位置,安卓能不能查找iphone?安卓手机查iphone位置的方法
  2. android 刷新布局,Android之刷新布局
  3. 圆柱属于能滚动的物体吗_由感知到建构 ——从问题“圆柱体容易滚动还是容易推动”说起...
  4. 能量采集 BZOJ - 2005
  5. Attention机制详解
  6. 贵州荔波:十四年办好一个节带富一方人
  7. 蜘蛛可以查php内容吗,如何查看spider蜘蛛,那么怎么才能知道蜘蛛是否来过你的站?...
  8. java 多线程性能_java多线程性能浅析
  9. python获取股票财务报表数据生成excel_(python导出股票数据到excel表格)如何将python输出的内容输入到表格Excel中...
  10. 【软文营销】企业网站优化软文怎么写