我的编程空间,编程开发者的网络收藏夹
学习永远不晚

【Android FFMPEG 开发】FFMPEG 音频重采样 ( 初始化音频重采样上下文 SwrContext | 计算音频延迟 | 计算输出样本个数 | 音频重采样 swr_convert )

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

【Android FFMPEG 开发】FFMPEG 音频重采样 ( 初始化音频重采样上下文 SwrContext | 计算音频延迟 | 计算输出样本个数 | 音频重采样 swr_convert )

文章目录I . FFMPEG 播放视频流程II . FFMPEG 音频重采样流程III . FFMPEG 音频重采样IV . FFMPEG 初始化音频重采样上下文 SwrContextV . FFMPEG 计算音频延迟样本数VI . FFMPEG 计算音频重采样输出样本个数VII . FFMPEG 输出样本缓冲区初始化VIII . FFMPEG 音频重采样IX . FFMPEG 音频重采样输出的重采样数据字节数计算X . FFMPEG 音频重采样部分代码总结

I . FFMPEG 播放视频流程

FFMPEG 播放视频流程 : 视频中包含图像和音频 ;


① FFMPEG 初始化 : 参考博客 【Android FFMPEG 开发】FFMPEG 初始化 ( 网络初始化 | 打开音视频 | 查找音视频流 )

② FFMPEG 获取 AVStream 音视频流 : 参考博客 【Android FFMPEG 开发】FFMPEG 获取 AVStream 音视频流 ( AVFormatContext 结构体 | 获取音视频流信息 | 获取音视频流个数 | 获取音视频流 )

③ FFMPEG 获取 AVCodec 编解码器 : 参考博客 【Android FFMPEG 开发】FFMPEG 获取编解码器 ( 获取编解码参数 | 查找编解码器 | 获取编解码器上下文 | 设置上下文参数 | 打开编解码器 )

④ FFMPEG 读取音视频流中的数据到 AVPacket : 参考博客 【Android FFMPEG 开发】FFMPEG 读取音视频流中的数据到 AVPacket ( 初始化 AVPacket 数据 | 读取 AVPacket )

⑤ FFMPEG 解码 AVPacket 数据到 AVFrame ( 音频 / 视频数据解码 ) : 参考博客 【Android FFMPEG 开发】FFMPEG 解码 AVPacket 数据到 AVFrame ( AVPacket->解码器 | 初始化 AVFrame | 解码为 AVFrame 数据 )

⑥ FFMPEG AVFrame 图像格式转换 YUV -> RGBA : 参考博客 【Android FFMPEG 开发】FFMPEG AVFrame 图像格式转换 YUV -> RGBA ( 获取 SwsContext | 初始化图像数据存储内存 | 图像格式转换 )

⑦ FFMPEG ANativeWindow 原生绘制 准备 : 参考博客 【Android FFMPEG 开发】FFMPEG ANativeWindow 原生绘制 ( Java 层获取 Surface | 传递画布到本地 | 创建 ANativeWindow )



II . FFMPEG 音频重采样流程

FFMPEG 音频重采样流程 :


〇 视频播放操作 : FFMPEG 环境初始化 , 获取 AVStream 音视频流 , 获取 AVCodec 编解码器 , 读取音视频流中的数据到 AVPacket , 解码 AVPacket 数据到 AVFrame , AVFrame 图像格式转换 YUV -> RGBA , ANativeWindow 原生绘制 ;


〇 音频播放操作 : FFMPEG 环境初始化 , 获取 AVStream 音视频流 , 获取 AVCodec 编解码器 , 读取音视频流中的数据到 AVPacket , 解码 AVPacket 数据到 AVFrame , 然后进行下面的操作 , 音频重采样 ;


① 初始化音频重采样上下文 : struct SwrContext *swr_alloc_set_opts( … ) , int swr_init(struct SwrContext *s)

SwrContext *swrContext = swr_alloc_set_opts(
        0 ,                     //现在还没有 SwrContext 上下文 , 先传入 0
        //输出的音频参数
        AV_CH_LAYOUT_STEREO ,   //双声道立体声
        AV_SAMPLE_FMT_S16 ,     //采样位数 16 位
        44100 ,                 //输出的采样率
        //从编码器中获取输入音频格式
        avCodecContext->channel_layout, //输入的声道数
        avCodecContext->sample_fmt,     //输入的采样位数
        avCodecContext->sample_rate,    //输入的采样率
        0, 0    //日志参数 设置 0 即可
        );
swr_init(swrContext);

② 计算积压的延迟数据 : int64_t swr_get_delay(struct SwrContext *s, int64_t base)

int64_t delay = swr_get_delay(swrContext , avFrame->sample_rate);

③ 计算本次重采样后的样本个数 : int64_t av_rescale_rnd(int64_t a, int64_t b, int64_t c, enum AVRounding rnd) av_const

int64_t out_count = av_rescale_rnd(
        avFrame->nb_samples + delay, //本次要处理的数据个数
        44100,
        avFrame->sample_rate ,
        AV_ROUND_UP );

④ 音频重采样 : int swr_convert(struct SwrContext *s, uint8_t **out, int out_count, const uint8_t **in , int in_count)

int samples_per_channel_count = swr_convert(
        swrContext ,
        &data,
        out_count ,
        (const uint8_t **)avFrame->data, //普通指针转为 const 指针需要使用 const_cast 转换
        avFrame->nb_samples
        );

⑤ 计算音频重采样字节数 : 音频重采样 swr_convert ( ) 返回值 samples_per_channel_count 是 每个通道的样本数 ;

pcm_data_bit_size = samples_per_channel_count * 2 * 2;


III . FFMPEG 音频重采样

1 . 音频解码 : FFMPEG 从 AVStream 音频流中读取 AVPacket 压缩的编码数据包 , 然后进行解码 , 获得解码后的数据 , 封装在 AVFrame 中 ;


2 . 音频重采样 : 解码后的 AVFrame 的音频 采样率 , 采样位数 , 声道数 ( 左声道 / 右声道 / 立体声 ) 都是不确定的 , 但是在 Android 中的播放器 , 需要播放指定的 采样率 , 采样位数 , 声道数 参数的音频 , 因此需要将 AVFrame 中的音频数据 , 进行重采样 , 将其转换为我们创建的 Android 播放器可以播放的音频数据 ;


3 . 参考视频解码 : 视频播放的时候也是从 AVStream 中读取 AVPacket 数据 , 然后解码为 AVFrame 数据 , 但是其图像大部分是 YUV 像素格式的 , 需要转成 ARGB 像素格式才能再 Android 的 SurfaceView 中进行绘制 ;


4 . 重采样 与 像素格式转换 : 这个 音频重采样 与 图像的像素格式转换作用相同 , 都是将读取的不确定的音频图像格式 , 转成可以在 Android 中播放或显示的固定的音频图像格式 ;


5 . OpenSL ES 播放参数举例 : 我们设置的 OpenSLES 播放器设定播放的音频格式是 立体声 , 44100 Hz 采样 , 16位采样位数 , 要将 AVFrame 中的解码后的音频转为上面的格式要求 , 才能再 OpenSLES 播放器中播放 ;



IV . FFMPEG 初始化音频重采样上下文 SwrContext

1 . 初始化音频重采样上下文 : 音频重采样需要先初始化 音频重采样上下文 SwrContext , 首先要调用 swr_alloc_set_opts ( ) 初始化内存 并 设置 SwrContext 参数 , 再调用 swr_init(swrContext) 方法初始化 ;


2 . swr_alloc_set_opts ( ) 函数原型 : 为 SwrContext 音频重采样上下文 结构体分配内存 , 并设置相关参数 ;


① struct SwrContext *s 参数 : 音频重采样上下文 结构体指针 , 这里还没有 , 传入 0 即可 ;


输出相关参数 :

② int64_t out_ch_layout 参数 : 输出通道参数 , 左声道 / 右声道 / 立体声 ;

③ enum AVSampleFormat out_sample_fmt 参数 : 输出采样位数 , 每个样本的大小 , 8 位 或 16 位 ;

④ int out_sample_rate 参数 : 输出的采样率 , 单位 Hz , 如 44100 Hz , 代表一秒钟有 44100 个采样 ;


输入相关参数 :

⑤ int64_t in_ch_layout 参数 : 输入通道参数 , 左声道 / 右声道 / 立体声 ;

⑥ enum AVSampleFormat in_sample_fmt 参数 : 输入采样位数 , 每个样本的大小 , 8 位 或 16 位 ;

⑦ int in_sample_rate 参数 : 输入的采样率 , 单位 Hz , 如 44100 Hz , 代表一秒钟有 44100 个采样 ;


⑧ int log_offset 参数 : 日志相关参数 ; 0 即可 ;

⑨ void *log_ctx 参数 : 日志相关参数 ; 0 即可 ;


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);

3 . swr_init ( ) 函数原型 : 在用户设置完音频重采样上下文参数后 , 调用该方法可以初始化该上下文 ;


① int 返回值 : 如果初始化失败 , 会返回 AVERROR 错误码 ;


int swr_init(struct SwrContext *s);

4 . FFMPEG 初始化音频重采样上下文 SwrContext 代码示例 :


swrContext = swr_alloc_set_opts(
        0 ,                     //现在还没有 SwrContext 上下文 , 先传入 0
        //输出的音频参数
        AV_CH_LAYOUT_STEREO ,   //双声道立体声
        AV_SAMPLE_FMT_S16 ,     //采样位数 16 位
        44100 ,                 //输出的采样率
        //从编码器中获取输入音频格式
        avCodecContext->channel_layout, //输入的声道数
        avCodecContext->sample_fmt,     //输入的采样位数
        avCodecContext->sample_rate,    //输入的采样率
        0, 0    //日志参数 设置 0 即可
        );
//注意创建完之后初始化
swr_init(swrContext);


V . FFMPEG 计算音频延迟样本数

1 . 音频延迟情况 : FFMPEG 转码的过程中 , 可能没有一次性将一帧数据处理完毕 , 如输入了 20 个数据 , 一般情况下 20 个数据都能处理完毕 , 有时还会出现只处理了 19 个 , 剩余的 1 个数据就积压在了缓冲区中的情况 , 如果这种积压在缓冲区中的数据过大 , 会造成很大的音频延迟 , 甚至内存崩溃 ;


2 . 延迟数据处理方案 : 每次音频处理时 , 都尝试将上一次积压的音频采样数据加入到本次处理的数据中 , 防止出现音频延迟的情况 ;


3 . 获取音频数据积压个数 : 调用 swr_get_delay ( ) 方法 , 可以获取当前积压的音频采样数 , 或播放延迟时间 ;


4 . 对延迟的理解 : swr_get_delay ( ) 获取的是下一次的样本数据 A 输入 经过多长时间延迟后 , 才能将样本 A 播放出来 , 这个延迟就是积压的数据的播放时间 , 因此每次处理时将少部分积压数据进行处理 , 可以有效降低音频延迟 ;


5 . swr_get_delay ( ) 函数原型 : 获取下一次输入的样本 , 到对应的样本输出时 , 需要经历的延迟 , 即获取延迟的数据播放时长或样本个数 ( 二选一 ) ;


① struct SwrContext *s 参数 : 音频重采样上下文 结构体指针 ;

② int64_t base 参数 : 设置成 1 / 1000 获取延迟的时间 秒 / 毫秒 , 设置采样率 获取延迟的样本个数 ;


int64_t swr_get_delay(struct SwrContext *s, int64_t base);

6 . FFMPEG 计算音频延迟样本数 swr_get_delay ( ) 函数使用示例 : 这里传入样本采样率 , 获取的是样本个数 ;

//OpenSLES 播放器设定播放的音频格式是 立体声 , 44100 Hz 采样 , 16位采样位数
//  解码出来的 AVFrame 中的数据格式不确定 , 需要进行重采样

int64_t delay = swr_get_delay(swrContext , avFrame->sample_rate);


VI . FFMPEG 计算音频重采样输出样本个数

1 . FFMPEG 音频重采样 : 音频重采样操作 , 需要指定一个输出样本个数, 目前已知的是 输入音频采样个数 , 输出音频采样率 , 输入音频采样率 , 需要计算出输出的音频采样个数 ;


2 . 计算公式如下 :

音频播放时间=输入音频采样个数输入音频采样率音频播放时间 = \frac{输入音频采样个数}{输入音频采样率}音频播放时间=输入音频采样率输入音频采样个数​


输出音频采样个数=音频播放时间×输出音频采样率输出音频采样个数= 音频播放时间 \times 输出音频采样率输出音频采样个数=音频播放时间×输出音频采样率


输出音频采样个数=输入音频采样个数输入音频采样率×输出音频采样率输出音频采样个数= \frac{输入音频采样个数}{输入音频采样率} \times 输出音频采样率输出音频采样个数=输入音频采样率输入音频采样个数​×输出音频采样率


3 . 计算溢出问题 : 上面涉及到的计算数据过大 , 音频采样率 与 采样个数 相乘 , 如 44100 Hz 采样率 , 10 万采样 , 相乘结果为 4,410,000,000 , 这个数量级有溢出的风险 , 为了解决计算溢出问题 , FFMPEG 给出了专门的函数 av_rescale_rnd ( ) 来处理这个计算 ;


4 . av_rescale_rnd ( ) 函数原型 : 该函数传入上述 输入音频采样个数 , 输入音频采样率 , 输出音频采样率 参数 , 进行上述计算 , 没有溢出问题 ; 计算公式是 a * b / c ;


① int64_t a 参数 : 输入音频采样个数 ;

② int64_t b 参数 : 输出音频采样率 ;

③ int64_t c 参数 : 输入音频采样率 ;

④ enum AVRounding rnd 参数 : 小数转为整数的方式 , 如四舍五入 , 向上取整 , 或向下取整 等 ;


int64_t av_rescale_rnd(int64_t a, int64_t b, int64_t c, enum AVRounding rnd) av_const;

5 . FFMPEG 计算音频重采样输出缓冲区大小 代码示例 :


int64_t out_count = av_rescale_rnd(
        avFrame->nb_samples + delay, //本次要处理的数据个数
        44100,
        avFrame->sample_rate ,
        AV_ROUND_UP );


VII . FFMPEG 输出样本缓冲区初始化

音频重采样后 , 需要初始化一段内存 , 用于保存重采样后的样本数据 ; 为其分配内存 , 并初始化内存数据 ;


uint8_t *data = static_cast(malloc(44100 * 2 * 2)); 
//初始化内存数据
memset(data, 0, 44100 * 2 * 2);


VIII . FFMPEG 音频重采样

1 . 音频重采样 : 上面准备好了音频重采样的所有参数 , 音频重采样上下文 SwrContext , 输出样本个数 , 输出缓冲区 uint8_t *data , AVFrame 中封装了输入音频的数据内容 , 采样率 , 采样位数 等信息 , 调用 swr_convert ( ) 函数 , 传入上述参数 , 即可进行音频重采样 ;


2 . swr_convert ( ) 函数原型 : FFMPEG 音频重采样的核心方法 ;


① struct SwrContext *s 参数 : 音频重采样上下文结构体指针 ;

② uint8_t **out 参数 : 输出的缓冲区 , 二维指针 ;

③ int out_count 参数 : 输出的缓冲区最大可接受的样本个数

④ const uint8_t **in 参数 : 输入的音频数据 ;

⑤ int in_count 参数 : 输入的样本个数

⑥ int 返回值 : 返回值是每个通道的样本个数 , 这里注意 , 如果是立体声 ,实际 样本数 是返回值 * 2 ;


int swr_convert(struct SwrContext *s, uint8_t **out, int out_count,
                                const uint8_t **in , int in_count);
//参数注释
int swr_convert(
       struct SwrContext *s,   //上下文
       uint8_t **out,          //输出的缓冲区 ( 需要计算 )
       int out_count,          //输出的缓冲区最大可接受的样本个数 ( 需要计算 )
       const uint8_t **in ,    //输入的数据
       int in_count);          //输入的数据大小

3 . FFMPEG 音频重采样 swr_convert ( ) 函数 代码示例 :


int samples_per_channel_count = swr_convert(
        swrContext ,
        &data,
        out_count ,
        (const uint8_t **)avFrame->data, //普通指针转为 const 指针需要使用 const_cast 转换
        avFrame->nb_samples
        );


IX . FFMPEG 音频重采样输出的重采样数据字节数计算

1 . 初始值 : 上述调用 swr_convert ( ) 方法 , 进行音频重采样 , 返回值 samples_per_channel_count 是每个通道的样本个数 ;


2 . 立体声样本数 : 如果该音频是立体声音频数据 , 其样本个数是 samples_per_channel_count * 2 ;


3 . 16 位立体声样本个数 : 如果该音频是 16 位立体声数据 , 其数据字节大小是 samples_per_channel_count * 2 * 2 字节 ;


4 . 计算字节数代码示例 :

//根据样本个数计算样本的字节数
pcm_data_bit_size = samples_per_channel_count * 2 * 2;


X . FFMPEG 音频重采样部分代码总结
// I . 音频重采样输出缓冲区准备

uint8_t *data = static_cast(malloc(44100 * 2 * 2)); 
//初始化内存数据
memset(data, 0, 44100 * 2 * 2);
// II . 音频重采样上下文 初始化

swrContext = swr_alloc_set_opts(
        0 ,                     //现在还没有 SwrContext 上下文 , 先传入 0
        //输出的音频参数
        AV_CH_LAYOUT_STEREO ,   //双声道立体声
        AV_SAMPLE_FMT_S16 ,     //采样位数 16 位
        44100 ,                 //输出的采样率
        //从编码器中获取输入音频格式
        avCodecContext->channel_layout, //输入的声道数
        avCodecContext->sample_fmt,     //输入的采样位数
        avCodecContext->sample_rate,    //输入的采样率
        0, 0    //日志参数 设置 0 即可
        );
//注意创建完之后初始化
swr_init(swrContext);
// III . 获取延迟数据
//OpenSLES 播放器设定播放的音频格式是 立体声 , 44100 Hz 采样 , 16位采样位数
//  解码出来的 AVFrame 中的数据格式不确定 , 需要进行重采样

int64_t delay = swr_get_delay(swrContext , avFrame->sample_rate);
// IV . 计算输出样本个数

int64_t out_count = av_rescale_rnd(
        avFrame->nb_samples + delay, //本次要处理的数据个数
        44100,
        avFrame->sample_rate ,
        AV_ROUND_UP );
// V . 音频重采样

int samples_per_channel_count = swr_convert(
        swrContext ,
        &data,
        out_count ,
        (const uint8_t **)avFrame->data, //普通指针转为 const 指针需要使用 const_cast 转换
        avFrame->nb_samples
        );
// VI . 最终重采样后的数据字节大小
//根据样本个数计算样本的字节数
pcm_data_bit_size = samples_per_channel_count * 2 * 2;

作者:韩曙亮


免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

【Android FFMPEG 开发】FFMPEG 音频重采样 ( 初始化音频重采样上下文 SwrContext | 计算音频延迟 | 计算输出样本个数 | 音频重采样 swr_convert )

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

【Android FFMPEG 开发】FFMPEG 音频重采样 ( 初始化音频重采样上下文 SwrContext | 计算音频延迟 | 计算输出样本个数 | 音频重采样 swr_convert )

文章目录I . FFMPEG 播放视频流程II . FFMPEG 音频重采样流程III . FFMPEG 音频重采样IV . FFMPEG 初始化音频重采样上下文 SwrContextV . FFMPEG 计算音频延迟样本数VI . FFM
2022-06-06

编程热搜

  • Android:VolumeShaper
    VolumeShaper(支持版本改一下,minsdkversion:26,android8.0(api26)进一步学习对声音的编辑,可以让音频的声音有变化的播放 VolumeShaper.Configuration的三个参数 durati
    Android:VolumeShaper
  • Android崩溃异常捕获方法
    开发中最让人头疼的是应用突然爆炸,然后跳回到桌面。而且我们常常不知道这种状况会何时出现,在应用调试阶段还好,还可以通过调试工具的日志查看错误出现在哪里。但平时使用的时候给你闹崩溃,那你就欲哭无泪了。 那么今天主要讲一下如何去捕捉系统出现的U
    Android崩溃异常捕获方法
  • android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
    系统的设置–>电池–>使用情况中,统计的能耗的使用情况也是以power_profile.xml的value作为基础参数的1、我的手机中power_profile.xml的内容: HTC t328w代码如下:
    android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
  • Android SQLite数据库基本操作方法
    程序的最主要的功能在于对数据进行操作,通过对数据进行操作来实现某个功能。而数据库就是很重要的一个方面的,Android中内置了小巧轻便,功能却很强的一个数据库–SQLite数据库。那么就来看一下在Android程序中怎么去操作SQLite数
    Android SQLite数据库基本操作方法
  • ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
    工作的时候为了方便直接打开编辑文件,一些常用的软件或者文件我们会放在桌面,但是在ubuntu20.04下直接直接拖拽文件到桌面根本没有效果,在进入桌面后发现软件列表中的软件只能收藏到面板,无法复制到桌面使用,不知道为什么会这样,似乎并不是很
    ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
  • android获取当前手机号示例程序
    代码如下: public String getLocalNumber() { TelephonyManager tManager =
    android获取当前手机号示例程序
  • Android音视频开发(三)TextureView
    简介 TextureView与SurfaceView类似,可用于显示视频或OpenGL场景。 与SurfaceView的区别 SurfaceView不能使用变换和缩放等操作,不能叠加(Overlay)两个SurfaceView。 Textu
    Android音视频开发(三)TextureView
  • android获取屏幕高度和宽度的实现方法
    本文实例讲述了android获取屏幕高度和宽度的实现方法。分享给大家供大家参考。具体分析如下: 我们需要获取Android手机或Pad的屏幕的物理尺寸,以便于界面的设计或是其他功能的实现。下面就介绍讲一讲如何获取屏幕的物理尺寸 下面的代码即
    android获取屏幕高度和宽度的实现方法
  • Android自定义popupwindow实例代码
    先来看看效果图:一、布局
  • Android第一次实验
    一、实验原理 1.1实验目标 编程实现用户名与密码的存储与调用。 1.2实验要求 设计用户登录界面、登录成功界面、用户注册界面,用户注册时,将其用户名、密码保存到SharedPreference中,登录时输入用户名、密码,读取SharedP
    Android第一次实验

目录