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

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

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 参数 : 小数转为整数的方式 , 如四舍五入 , 向上取整 , 或向下取整 等 ;


/**
 * Rescale a 64-bit integer with specified rounding.
 *
 * The operation is mathematically equivalent to `a * b / c`, but writing that
 * directly can overflow, and does not support different rounding methods.
 *
 * @see av_rescale(), av_rescale_q(), av_rescale_q_rnd()
 */
int64_t av_rescale_rnd(int64_t a, int64_t b, int64_t c, enum AVRounding rnd) av_const;



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


/*
    将 a 个数据 , 由 c 采样率转换成 b 采样率后 , 返回多少数据
    int64_t av_rescale_rnd(int64_t a, int64_t b, int64_t c, enum AVRounding rnd) av_const;
    下面的方法时将 avFrame->nb_samples 个数据 , 由 avFrame->sample_rate 采样率转为 44100 采样率
    返回的数据个数
    AV_ROUND_UP : 向上取整
 */
int64_t out_count = av_rescale_rnd(
        avFrame->nb_samples + delay, //本次要处理的数据个数
        44100,
        avFrame->sample_rate ,
        AV_ROUND_UP );



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


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


/**
 * 存放重采样后的数据缓冲区 , 这个缓冲区存储 1 秒的数据 
 * 44100 Hz 采样率 , 16 位采样位数 , 双声道立体声 , 占用内存 44100 * 2 * 2 字节 
 */
uint8_t *data = static_cast<uint8_t *>(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 ;


/** Convert audio.
 *
 * in and in_count can be set to 0 to flush the last few samples out at the
 * end.
 *
 * If more input is provided than output space, then the input will be buffered.
 * You can avoid this buffering by using swr_get_out_samples() to retrieve an
 * upper bound on the required number of output samples for the given number of
 * input samples. Conversion will run directly without copying whenever possible.
 *
 * @param s         allocated Swr context, with parameters set
 * @param out       output buffers, only the first one need be set in case of packed audio
 * @param out_count amount of space available for output in samples per channel
 * @param in        input buffers, only the first one need to be set in case of packed audio
 * @param in_count  number of input samples available in one channel
 *
 * @return number of samples output per channel, negative value on error
 */
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 swr_convert(
        struct SwrContext *s,   //上下文
        uint8_t **out,          //输出的缓冲区 ( 需要计算 )
        int out_count,          //输出的缓冲区最大可接受的样本个数 ( 需要计算 )
        const uint8_t **in ,    //输入的数据
        int in_count);          //输入的样本个数
返回值 : 转换后的采样个数 , 是样本个数 , 每个样本是 16 位 , 两个字节 ;
        samples_out_count 是每个通道的样本数 , samples_out_count * 2 是立体声双声道样本个数
        samples_out_count * 2 * 2 是字节个数
 */
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 . 音频重采样输出缓冲区准备



/**
 * 存放重采样后的数据缓冲区 , 这个缓冲区存储 1 秒的数据 
 * 44100 Hz 采样率 , 16 位采样位数 , 双声道立体声 , 占用内存 44100 * 2 * 2 字节 
 */
uint8_t *data = static_cast<uint8_t *>(malloc(44100 * 2 * 2)); 
//初始化内存数据
memset(data, 0, 44100 * 2 * 2);
// II . 音频重采样上下文 初始化
/*
 设置音频重采样的上下文参数
 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);
 */
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 swr_get_delay(
    struct SwrContext *s,
    int64_t base
    );
    转码的过程中 , 输入 10 个数据 , 并不一定都能处理完毕并输出 10 个数据 , 可能处理输出了 8 个数据
    还剩余 2 个数据没有处理
    那么在下一次处理的时候 , 需要将上次没有处理完的两个数据处理了 ;
    如果不处理上次的2个数据 , 那么数据会一直积压 , 如果积压数据过多 , 最终造成很大的延迟 , 甚至崩溃
    因此每次处理的时候 , 都要尝试将上次剩余没有处理的数据加入到本次处理的数据中
    如果计算出的 delay 一直等于 0 , 说明没有积压数据
 */
int64_t delay = swr_get_delay(swrContext , avFrame->sample_rate);
// IV . 计算输出样本个数
/*
    将 a 个数据 , 由 c 采样率转换成 b 采样率后 , 返回多少数据
    int64_t av_rescale_rnd(int64_t a, int64_t b, int64_t c, enum AVRounding rnd) av_const;
    下面的方法时将 avFrame->nb_samples 个数据 , 由 avFrame->sample_rate 采样率转为 44100 采样率
    返回的数据个数
    AV_ROUND_UP : 向上取整
 */
int64_t out_count = av_rescale_rnd(
        avFrame->nb_samples + delay, //本次要处理的数据个数
        44100,
        avFrame->sample_rate ,
        AV_ROUND_UP );
// V . 音频重采样
/*
 int swr_convert(
        struct SwrContext *s,   //上下文
        uint8_t **out,          //输出的缓冲区 ( 需要计算 )
        int out_count,          //输出的缓冲区最大可接受的样本个数 ( 需要计算 )
        const uint8_t **in ,    //输入的数据
        int in_count);          //输入的样本个数
返回值 : 转换后的采样个数 , 是样本个数 , 每个样本是 16 位 , 两个字节 ;
        samples_out_count 是每个通道的样本数 , samples_out_count * 2 是立体声双声道样本个数
        samples_out_count * 2 * 2 是字节个数
 */
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;


目录
相关文章
|
2月前
|
Linux 开发工具 Android开发
FFmpeg开发笔记(六十)使用国产的ijkplayer播放器观看网络视频
ijkplayer是由Bilibili基于FFmpeg3.4研发并开源的播放器,适用于Android和iOS,支持本地视频及网络流媒体播放。本文详细介绍如何在新版Android Studio中导入并使用ijkplayer库,包括Gradle版本及配置更新、导入编译好的so文件以及添加直播链接播放代码等步骤,帮助开发者顺利进行App调试与开发。更多FFmpeg开发知识可参考《FFmpeg开发实战:从零基础到短视频上线》。
190 2
FFmpeg开发笔记(六十)使用国产的ijkplayer播放器观看网络视频
|
2月前
|
编解码 语音技术 内存技术
FFmpeg开发笔记(五十八)把32位采样的MP3转换为16位的PCM音频
《FFmpeg开发实战:从零基础到短视频上线》一书中的“5.1.2 把音频流保存为PCM文件”章节介绍了将媒体文件中的音频流转换为原始PCM音频的方法。示例代码直接保存解码后的PCM数据,保留了原始音频的采样频率、声道数量和采样位数。但在实际应用中,有时需要特定规格的PCM音频。例如,某些语音识别引擎仅接受16位PCM数据,而标准MP3音频通常采用32位采样,因此需将32位MP3音频转换为16位PCM音频。
78 0
FFmpeg开发笔记(五十八)把32位采样的MP3转换为16位的PCM音频
|
2月前
|
XML 开发工具 Android开发
FFmpeg开发笔记(五十六)使用Media3的Exoplayer播放网络视频
ExoPlayer最初是为了解决Android早期MediaPlayer控件对网络视频兼容性差的问题而推出的。现在,Android官方已将其升级并纳入Jetpack的Media3库,使其成为音视频操作的统一引擎。新版ExoPlayer支持多种协议,解决了设备和系统碎片化问题,可在整个Android生态中一致运行。通过修改`build.gradle`文件、布局文件及Activity代码,并添加必要的权限,即可集成并使用ExoPlayer进行网络视频播放。具体步骤包括引入依赖库、配置播放界面、编写播放逻辑以及添加互联网访问权限。
177 1
FFmpeg开发笔记(五十六)使用Media3的Exoplayer播放网络视频
|
2月前
|
Web App开发 安全 程序员
FFmpeg开发笔记(五十五)寒冬里的安卓程序员可进阶修炼的几种姿势
多年的互联网寒冬在今年尤为凛冽,坚守安卓开发愈发不易。面对是否转行或学习新技术的迷茫,安卓程序员可从三个方向进阶:1)钻研谷歌新技术,如Kotlin、Flutter、Jetpack等;2)拓展新功能应用,掌握Socket、OpenGL、WebRTC等专业领域技能;3)结合其他行业,如汽车、游戏、安全等,拓宽职业道路。这三个方向各有学习难度和保饭碗指数,助你在安卓开发领域持续成长。
82 1
FFmpeg开发笔记(五十五)寒冬里的安卓程序员可进阶修炼的几种姿势
|
3月前
|
XML Java Android开发
FFmpeg开发笔记(五十二)移动端的国产视频播放器GSYVideoPlayer
GSYVideoPlayer是一款国产移动端视频播放器,支持弹幕、滤镜、广告等功能,采用IJKPlayer、Media3(EXOPlayer)、MediaPlayer及AliPlayer多种内核。截至2024年8月,其GitHub星标数达2万。集成时需使用新版Android Studio,并按特定步骤配置依赖与权限。提供了NormalGSYVideoPlayer、GSYADVideoPlayer及ListGSYVideoPlayer三种控件,支持HLS、RTMP等多种直播链接。
116 18
FFmpeg开发笔记(五十二)移动端的国产视频播放器GSYVideoPlayer
|
2月前
|
Linux API 开发工具
FFmpeg开发笔记(五十九)Linux编译ijkplayer的Android平台so库
ijkplayer是由B站研发的移动端播放器,基于FFmpeg 3.4,支持Android和iOS。其源码托管于GitHub,截至2024年9月15日,获得了3.24万星标和0.81万分支,尽管已停止更新6年。本文档介绍了如何在Linux环境下编译ijkplayer的so库,以便在较新的开发环境中使用。首先需安装编译工具并调整/tmp分区大小,接着下载并安装Android SDK和NDK,最后下载ijkplayer源码并编译。详细步骤包括环境准备、工具安装及库编译等。更多FFmpeg开发知识可参考相关书籍。
110 0
FFmpeg开发笔记(五十九)Linux编译ijkplayer的Android平台so库
|
3月前
|
Linux 开发工具 Android开发
FFmpeg开发笔记(五十三)移动端的国产直播录制工具EasyPusher
EasyPusher是一款国产RTSP直播录制推流客户端工具,支持Windows、Linux、Android及iOS等系统。尽管其GitHub仓库(安卓版:https://github.com/EasyDarwin/EasyPusher-Android)已多年未更新,但通过一系列改造,如升级SDK版本、迁移到AndroidX、指定本地NDK版本及更新Gradle版本等,仍可在最新Android Studio上运行。以下是针对Android Studio Dolphin版本的具体改造步骤。
66 3
FFmpeg开发笔记(五十三)移动端的国产直播录制工具EasyPusher
|
2月前
|
Android开发 开发者
FFmpeg开发笔记(五十七)使用Media3的Transformer加工视频文件
谷歌推出的Transformer,作为Jetpack Media3架构的一部分,助力开发者实现音视频格式转换与编辑。Media3简化了媒体处理流程,提升了定制性和可靠性。Transformer可用于剪辑、添加滤镜等操作,其示例代码可在指定GitHub仓库中找到。要使用Transformer,需在`build.gradle`中添加相关依赖,并按文档编写处理逻辑,最终完成音视频转换任务。具体步骤包括配置剪辑参数、设置空间效果以及监听转换事件等。
61 0
FFmpeg开发笔记(五十七)使用Media3的Transformer加工视频文件
|
2月前
|
Linux 视频直播
FFmpeg开发笔记(五十四)使用EasyPusher实现移动端的RTSP直播
本文介绍了如何使用EasyPusher-Android实现RTSP直播流程。首先对比了RTSP、RTMP、SRT和RIST四种流媒体协议,并以RTSP为例,详细说明了使用EasyPusher-Android向流媒体服务器进行RTSP直播推流的方法。文中还提供了OBS Studio配置RTSP插件及ZLMediaKit云服务器部署的相关信息,通过修改EasyPusher-Android源码使其支持通用RTSP地址,最终验证了直播功能的成功实现。
71 0
FFmpeg开发笔记(五十四)使用EasyPusher实现移动端的RTSP直播
|
3月前
|
编解码 API 数据安全/隐私保护
FFmpeg开发笔记(五十四)使用EasyPusher实现移动端的RTSP直播
【9月更文挑战第21天】本文介绍了如何使用FFmpeg和EasyPusher实现移动端RTSP直播。首先概述了EasyPusher的功能及其API,接着详细描述了安装FFmpeg、获取EasyPusher库、初始化对象、打开输入流、配置推送参数及读取推送帧的具体步骤,并提醒开发者注意网络环境、编码参数和权限管理等问题,以确保直播质量与稳定性。
下一篇
DataWorks