【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天前
|
Java API 开发工具
java与Android开发入门指南
java与Android开发入门指南
8 0
|
3天前
|
Android开发 Kotlin
Kotlin开发Android之基础问题记录
Kotlin开发Android之基础问题记录
15 1
|
3天前
|
Java Android开发
Android开发@IntDef完美替代Enum
Android开发@IntDef完美替代Enum
12 0
|
4天前
|
Android开发
Android 盒子开发过程中遇到的问题及解决方法
Android 盒子开发过程中遇到的问题及解决方法
8 2
|
4天前
|
机器学习/深度学习 算法 Android开发
安卓应用开发:打造高效通知管理系统
【5月更文挑战第6天】 在现代移动应用的海洋中,用户经常面临信息过载的挑战。一个精心设计的通知管理系统对于提升用户体验至关重要。本文将探讨在安卓平台上如何实现一个高效的通知管理系统,包括最佳实践、系统架构设计以及性能优化技巧。通过分析安卓通知渠道和优先级设置,我们的目标是帮助开发者构建出既能吸引用户注意,又不会引发干扰的智能通知系统。
16 2
|
5天前
|
安全 Linux Android开发
FFmpeg开发笔记(十六)Linux交叉编译Android的OpenSSL库
该文介绍了如何在Linux服务器上交叉编译Android的FFmpeg库以支持HTTPS视频播放。首先,从GitHub下载openssl源码,解压后通过编译脚本`build_openssl.sh`生成64位静态库。接着,更新环境变量加载openssl,并编辑FFmpeg配置脚本`config_ffmpeg_openssl.sh`启用openssl支持。然后,编译安装FFmpeg。最后,将编译好的库文件导入App工程的相应目录,修改视频链接为HTTPS,App即可播放HTTPS在线视频。
FFmpeg开发笔记(十六)Linux交叉编译Android的OpenSSL库
|
Android开发
Android Studio 编译输出中文乱码的解决办法
Android Studio 编译输出中文乱码的解决办法
155 0
|
17天前
|
消息中间件 网络协议 Java
Android 开发中实现数据传递:广播和Handler
Android 开发中实现数据传递:广播和Handler
16 1
|
19天前
|
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配置以确保顺利运行。
FFmpeg开发笔记(九)Linux交叉编译Android的x265库
|
20天前
|
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工程指定目录。
FFmpeg开发笔记(八)Linux交叉编译Android的FFmpeg库