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;