FFmpeg库的使用与深度解析:解码音频流流程

简介: FFmpeg库的使用与深度解析:解码音频流流程

解码音频流:FFmpeg库的使用与深度解析

1. 引言

多媒体处理领域,FFmpeg是一个非常强大的库,它提供了多种工具和接口用于处理音频和视频数据。本文将深入探讨如何使用FFmpeg库进行音频流的解码和重采样。

“Simplicity is the ultimate sophistication.” — Leonardo da Vinci

这句话也适用于编程和数据处理。简单的代码和算法往往更容易维护和扩展。

2. 解封装流程

2.1 注册所有封装器和解封装器

使用av_register_all()函数进行注册。

av_register_all();

2.2 打开文件

使用avformat_open_input()函数打开一个文件或URL。

AVFormatContext* pFormatCtx = nullptr;
avformat_open_input(&pFormatCtx, "input.mp3", nullptr, nullptr);

2.3 查找流信息

使用avformat_find_stream_info()函数查找流信息。

avformat_find_stream_info(pFormatCtx, nullptr);

2.4 获取音频流索引和解码器ID

int audioStream = -1;
for (int i = 0; i < pFormatCtx->nb_streams; i++) {
    if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO) {
        audioStream = i;
        break;
    }
}
AVCodecID codecID = pFormatCtx->streams[audioStream]->codec->codec_id;

3. 解码流程

3.1 获取解码器

使用avcodec_find_decoder()函数获取解码器。

AVCodec* pCodec = avcodec_find_decoder(codecID);

3.2 打开解码器

使用avcodec_open2()函数打开解码器。

AVCodecContext* pCodecCtx = pFormatCtx->streams[audioStream]->codec;
avcodec_open2(pCodecCtx, pCodec, nullptr);

3.3 解码数据

AVPacket packet;
AVFrame* pFrame = av_frame_alloc();
while (av_read_frame(pFormatCtx, &packet) >= 0) {
    if (packet.stream_index == audioStream) {
        avcodec_send_packet(pCodecCtx, &packet);
        avcodec_receive_frame(pCodecCtx, pFrame);
    }
    av_packet_unref(&packet);
}

4. 重采样

4.1 创建SwrContext

SwrContext* swrCtx = swr_alloc();

4.2 设置参数并初始化

swr_alloc_set_opts(swrCtx, ...);
swr_init(swrCtx);

4.3 数据转换和内存释放

swr_convert(swrCtx, ...);
swr_free(&swrCtx);

5. 代码示例

#include <iostream>
#include <cstdio>
#include <vdef.h>
using namespace std;
#define MAX_AUDIO_FRAME_SIZE 192000
//Buffer:存储格式
//|-----------|-------------|
//chunk-------pos---len-----|
static Uint8* audio_chunk;
static int audio_len;  //音频剩余长度
static Uint8* audio_pos;  //静态控制音频播放位置
//注册回调函数  SDL2.0
// udata就是我们给到SDL的指针,stream是我们要把声音数据写入的缓冲区指针,len是缓冲区的大小。
void Fill_audio(void* udata,Uint8* stream,int len)
{
    cout << "Fill_audio len:"<<len<<endl;
    SDL_memset(stream,0,len);
    if(audio_len == 0)
    return ;
    len = (len>audio_len?len:audio_len);   //尽可能为最大音频量
    SDL_MixAudio(stream,audio_pos,len,SDL_MIX_MAXVOLUME); //这里的音量设置为函数要求,不影响硬件音量
    audio_pos +=len;//音频播放位置
    audio_len -=len;//剩余音频长度
}
int main()    //这里main 在SDL_main中被宏定义了用的时候不可以使用int main(省参)
{
 int  l_s32AStreamSubscript = -1;//音频流标志
  avformat_network_init();
  char fillename[] = "E:\\tt.mp3";//播放文件
// 1.Open the input file in the unpacked format
  l_pstFormatCtx = avformat_alloc_context();
  if(avformat_open_input(&l_pstFormatCtx,fillename,NULL,NULL)!=0)
  {
    cout << "[music_error]Could not open source file,exit work_" << fillename <<endl;
    return -1;
  }
  if(avformat_find_stream_info(l_pstFormatCtx,NULL)<0)
  {
    cout << "[music_error]couldn't find stream information" <<endl;
    return -1;
  }
//2. get the index position of the audio stream,
  if(l_pstFormatCtx!=nullptr)
  {
    for (unsigned int i = 0; i < l_pstFormatCtx->nb_streams; ++i)
    {
      if (l_pstFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO)
        l_s32AStreamSubscript = i;//Audio stream sequence number
    }
  }
  if(l_s32AStreamSubscript == -1)
  {
    cout << "[music_error]Can't find audiostream" <<endl;
    return -1;
  }
//3.Find and open the audio decoder  Codec type or id mismatches
  l_pstAStream= l_pstFormatCtx->streams[l_s32AStreamSubscript];
  l_pstACodec =  avcodec_find_decoder(l_pstAStream->codecpar->codec_id);
  l_pstACodecCtx = avcodec_alloc_context3(l_pstACodec); //Allocation of AVCodecContext memory
  if(l_pstACodecCtx == nullptr || avcodec_parameters_to_context(l_pstACodecCtx, (const AVCodecParameters *)l_pstAStream->codecpar)<0)
  {
      cout << "[music_error]Codec ont find" <<endl;
      return -1;
  }
  if (avcodec_open2(l_pstACodecCtx, l_pstACodec, nullptr) < 0 || l_pstACodec == nullptr)
  {
      cout << "[music_error]Cannot find the corresponding decoder or the file is encrypted" <<SDL_GetError()<<endl;
      return -1;
  }
  if(SDL_Init(SDL_INIT_VIDEO|SDL_INIT_AUDIO|SDL_INIT_TIMER))
  {
    cout << "[music_error]Could not initialize SDL" <<SDL_GetError()<<endl;
    return -1;
  }
  uint64_t out_channel_layout  = AV_CH_LAYOUT_STEREO;  //声道格式
  AVSampleFormat out_sample_fmt = AV_SAMPLE_FMT_S16;   //采样格式
  int out_nb_samples=l_pstACodecCtx->frame_size;   //nb_samples: AAC-1024 MP3-1152  格式大小 /*有的是视频格式数据头为非标准格式,从frame_size中得不到正确的数据大小,只能解码一帧数据后才可以获得*/
  int out_sample_rate = 44100;//采样率 pCodecCtx->sample_rate
  int out_channels = av_get_channel_layout_nb_channels(out_channel_layout);  //根据声道格式返回声道个数
  int out_buffer_size = av_samples_get_buffer_size(NULL,out_channels,out_nb_samples,out_sample_fmt,1);//获取输出缓冲大小
  out_buffer = (uint8_t*)av_malloc(MAX_AUDIO_FRAME_SIZE);
  memset(out_buffer,0,MAX_AUDIO_FRAME_SIZE);
  wanted_spec.freq = out_sample_rate; //采样率
  wanted_spec.format = AUDIO_S16SYS;  //告诉SDL我们将要给的格式
  wanted_spec.channels = out_channels;   //声音的通道数
  wanted_spec.silence = 0;         //用来表示静音的值
  wanted_spec.samples = out_nb_samples;   //格式大小
  wanted_spec.callback = Fill_audio;    //回调函数
  //打开音频设备
  wanted_spec.userdata = l_pstACodecCtx;    //SDL供给回调函数运行的参数
  if (SDL_OpenAudio(&wanted_spec, NULL)<0)
  {
        printf("can't open audio.\n");
        return -1;
  }
   //根据声道数返回默认输入声道格式
  int64_t in_channel_layout = av_get_default_channel_layout(l_pstACodecCtx->channels);
   //音频格式转换准备
  au_convert_ctx = swr_alloc();//等同于au_convert_ctx  = NULL;
  //参数设置:输出格式PCM -- 输入格式  MP3
  au_convert_ctx = swr_alloc_set_opts(au_convert_ctx,out_channel_layout, out_sample_fmt, out_sample_rate,
        in_channel_layout,l_pstACodecCtx->sample_fmt , l_pstACodecCtx->sample_rate,0, NULL);
  swr_init(au_convert_ctx);//初始化
  int index =  0;
  packet = (AVPacket*)av_malloc(sizeof(AVPacket));
  av_init_packet(packet);
  pFrame = av_frame_alloc();
  //解析数据包
  while(av_read_frame(l_pstFormatCtx, packet)>=0)
  {
      if(packet->stream_index == l_s32AStreamSubscript)  //如果为音频标志
      {                 
          //解码一帧音频压缩数据,得到音频像素数据
          if ( avcodec_send_packet(l_pstACodecCtx, packet) != 0)
          {
             cout<<"[audio_decode_frame] avcodec_send_packet failed"<<endl;
          }
          else
          {
      //       cout<<"[audio_decode_frame] avcodec_send_packet successfully"<<endl;
          }
           一个avPacket可能包含多帧数据,所以需要使用while循环一直读取
          while( (avcodec_receive_frame(l_pstACodecCtx, pFrame) )>= 0)
          {
              //数据格式转换
              swr_convert(au_convert_ctx,&out_buffer,MAX_AUDIO_FRAME_SIZE,(const uint8_t**)pFrame->data,pFrame->nb_samples);
              //输出一帧包大小
              printf("index:%5d\t pts:%lld\t packet size:%d\n",index,packet->pts,packet->size);
              index++;
           }
          while(audio_len>0)
            SDL_Delay(1);//延时1ms
         //指向音频数据 (PCM data)
         audio_chunk = (Uint8 *) out_buffer;
         //音频长度
         audio_len =out_buffer_size;
         //当前播放位置
         audio_pos = audio_chunk;
         //开始播放
         SDL_PauseAudio(0);
      }
//      cout<<"[audio_decode_frame]Remove the reference to the previous frame"<<endl;
      av_packet_unref(packet);
      av_frame_unref(pFrame);
  }
     av_packet_free(&packet);
     //释放转换结构体
         swr_free(&au_convert_ctx);
#if USE_SDL
    SDL_CloseAudio();//Close SDL
    SDL_Quit();
#endif
#if WRITEPCM
    fclose(file);
#endif
    av_free(out_buffer);
//free_AVCodecCtx:
    avcodec_close(l_pstACodecCtx);
    // 关闭打开音频文件
    avformat_close_input(&l_pstFormatCtx);
    system("pause");
    return 0;
}

6. 总结

本文详细介绍了如何使用FFmpeg库进行音频流的解封装、解码和重采样。这些步骤虽然看似简单,但每一个函数和接口背后都有深刻的设计哲学。

“The most important property of a program is whether it accomplishes the intention of its user.” — C.A.R. Hoare

在我们的编程学习之旅中,理解是我们迈向更高层次的重要一步。然而,掌握新技能、新理念,始终需要时间和坚持。从心理学的角度看,学习往往伴随着不断的试错和调整,这就像是我们的大脑在逐渐优化其解决问题的“算法”。

这就是为什么当我们遇到错误,我们应该将其视为学习和进步的机会,而不仅仅是困扰。通过理解和解决这些问题,我们不仅可以修复当前的代码,更可以提升我们的编程能力,防止在未来的项目中犯相同的错误。

我鼓励大家积极参与进来,不断提升自己的编程技术。无论你是初学者还是有经验的开发者,我希望我的博客能对你的学习之路有所帮助。如果你觉得这篇文章有用,不妨点击收藏,或者留下你的评论分享你的见解和经验,也欢迎你对我博客的内容提出建议和问题。每一次的点赞、评论、分享和关注都是对我的最大支持,也是对我持续分享和创作的动力。

目录
相关文章
|
13天前
|
Linux 编译器 Android开发
FFmpeg开发笔记(九)Linux交叉编译Android的x265库
在Linux环境下,本文指导如何交叉编译x265的so库以适应Android。首先,需安装cmake和下载android-ndk-r21e。接着,下载x265源码,修改crosscompile.cmake的编译器设置。配置x265源码,使用指定的NDK路径,并在配置界面修改相关选项。随后,修改编译规则,编译并安装x265,调整pc描述文件并更新PKG_CONFIG_PATH。最后,修改FFmpeg配置脚本启用x265支持,编译安装FFmpeg,将生成的so文件导入Android工程,调整gradle配置以确保顺利运行。
37 1
FFmpeg开发笔记(九)Linux交叉编译Android的x265库
|
14天前
|
Unix Linux Shell
FFmpeg开发笔记(八)Linux交叉编译Android的FFmpeg库
在Linux环境下交叉编译Android所需的FFmpeg so库,首先下载`android-ndk-r21e`,然后解压。接着,上传FFmpeg及相关库(如x264、freetype、lame)源码,修改相关sh文件,将`SYSTEM=windows-x86_64`改为`SYSTEM=linux-x86_64`并删除回车符。对x264的configure文件进行修改,然后编译x264。同样编译其他第三方库。设置环境变量`PKG_CONFIG_PATH`,最后在FFmpeg源码目录执行配置、编译和安装命令,生成的so文件复制到App工程指定目录。
43 9
FFmpeg开发笔记(八)Linux交叉编译Android的FFmpeg库
|
22天前
|
数据采集 机器学习/深度学习 数据可视化
数据科学项目实战:完整的Python数据分析流程案例解析
【4月更文挑战第12天】本文以Python为例,展示了数据分析的完整流程:从CSV文件加载数据,执行预处理(处理缺失值和异常值),进行数据探索(可视化和统计分析),选择并训练线性回归模型,评估模型性能,以及结果解释与可视化。每个步骤都包含相关代码示例,强调了数据科学项目中理论与实践的结合。
|
5天前
|
缓存 Java 开发者
10个点介绍SpringBoot3工作流程与核心组件源码解析
Spring Boot 是Java开发中100%会使用到的框架,开发者不仅要熟练使用,对其中的核心源码也要了解,正所谓知其然知其所以然,V 哥建议小伙伴们在学习的过程中,一定要去研读一下源码,这有助于你在开发中游刃有余。欢迎一起交流学习心得,一起成长。
|
17天前
|
存储 编解码 编译器
FFmpeg 7.0 正式登场:全新 VVC 解码器
【4月更文挑战第9天】最新版本的流行视频处理软件FFmpeg 7.0,代号为“Dijkstra”,已正式发布。
29 0
|
17天前
|
数据采集 数据可视化 数据挖掘
Seaborn实战:从数据清洗到可视化全流程解析
【4月更文挑战第17天】在数据分析中,Seaborn是用于数据可视化的重要工具,同时也辅助数据清洗。本文通过实例展示了如何利用Seaborn从数据清洗(包括导入数据、处理缺失和异常值)到数据探索(描述性统计、分组统计和可视化探索)。接着,文章详细讲解了数据可视化,包括分类和数值数据的图表以及高级图表如小提琴图、箱形图和热力图。最后,介绍了Seaborn与其他工具(如Pandas和Matplotlib)的结合使用,强调了数据可视化的迭代优化过程。学习并掌握Seaborn能提升数据分析和展示的效率。
|
28天前
|
编解码 缓存 算法
FFmpeg开发笔记(四)FFmpeg的动态链接库介绍
FFmpeg是一个强大的多媒体处理框架,提供ffmpeg、ffplay和ffprobe工具及八个库:avcodec(编解码)、avdevice(设备输入输出)、avfilter(音视频滤镜)、avformat(格式处理)、avutil(通用工具和算法)、postproc(后期效果)、swresample(音频重采样)和swscale(视频图像转换)。这些库支持定制化开发,涵盖了从采集、编码、过滤到输出的全过程。了解详细FFmpeg开发信息,可参考《FFmpeg开发实战:从零基础到短视频上线》。
35 0
FFmpeg开发笔记(四)FFmpeg的动态链接库介绍
|
1月前
|
C++
C++ While 和 For 循环:流程控制全解析
本文介绍了C++中的`switch`语句和循环结构。`switch`语句根据表达式的值执行匹配的代码块,可以使用`break`终止执行并跳出`switch`。`default`关键字用于处理没有匹配`case`的情况。接着,文章讲述了三种类型的循环:`while`循环在条件满足时执行代码,`do/while`至少执行一次代码再检查条件,`for`循环适用于已知循环次数的情况。`for`循环包含初始化、条件和递增三个部分。此外,还提到了嵌套循环和C++11引入的`foreach`循环,用于遍历数组元素。最后,鼓励读者关注微信公众号`Let us Coding`获取更多内容。
21 0
|
1月前
|
搜索推荐 数据管理 数据挖掘
解码2024年项目管理系统:排行榜背后的功能与特色解析
2024年十大项目管理工具:Zoho Projects以其专业成熟度领先,适合跨部门协作和进度跟踪;Nifty适合初创公司,界面直观,响应快速;Quickbase面向处理大量信息的团队,提供定制化解决方案;WorkOtter专为中大型企业资源管理和汇报设计;Asana适合大型协作团队,任务管理和沟通高效;Monday.com高度可定制,适合复杂项目管理;Smartsheet结合电子表格功能,适合流程多变的团队;Adobe Workfront针对复杂项目和自动化需求;ClickUp是一站式工作平台,功能多样;Trello则以简洁看板适合小团队和个人。考虑团队规模、项目复杂度和个性化需求来选工具
30 1
|
2月前
|
自然语言处理 编译器 程序员
【Qt底层之 元对象的编译】Qt 元对象系统及其编译流程解析
【Qt底层之 元对象的编译】Qt 元对象系统及其编译流程解析
105 4

推荐镜像

更多