如今以抖音、快手为代表的短视频秀无处不在,比如它们一个很普通的功能就是使用流行音乐替换作为视频的背景音乐。而在视频中音频一般都是以AAC的形成存在,但流行音乐大多以mp3的格式传播, 因此需要完成背景音乐替换这个功能,其中的一个步骤就需要完成mp3转aac这样的一个音频转转码的过程。
按照我们以往的经验,转码的大致流程应该是这样的:
解封装->提取音频流->解码成PCM->重新编码成AAC
流程是这样没错,但是内部的出来细节是怎样的呢?是mp3解码出来后的AVFrame可以通过函数avcodec_send_frame
送进aac编码器即可吗? 很明显这是不行的,因为mp3每帧是1152个采样点,而aac每帧是1024个采样点。它们每帧的采样点数不同,所以不能直接通过avcodec_send_frame
进行编码。
AVAudioFifo是一个音频缓冲区,是一个先进先出的队列。使用它可以很方便地储存我们的音频缓冲数据,例如在mp3转码aac的过程中,因为它们的采样点数不同,我们就可以把mp3解码出来的 pcm数据放入到AVAudioFifo中去,然后每次从AVAudioFifo中获取1024个采样点送进aac编码器,这样的做法让我们的音频转码变得非常的方便灵活。AVAudioFifo让我们在采样层面做操作,而不用关心底层的字节层面;而且它支持多种格式的单次采样,如支持planar或packed的采样格式,支持不同的通道数等等。
AVAudioFifo的API使用也非常简单,主要包含分配、释放、获取可读写空间长度、写入音频数据、读取音频数据等相关函数:
首先是分配和释放操作:
//分配一个AVAudioFifo。
//sample_fmt指定采样格式
//nb_samples则指定AVAudioFifo的缓冲区大小,可以通过av_audio_fifo_realloc重新分配
AVAudioFifo *av_audio_fifo_alloc(enum AVSampleFormat sample_fmt, int channels,int nb_samples);
//重新分配缓冲区大小
//成功返回0,失败返回负的错误值
int av_audio_fifo_realloc(AVAudioFifo *af, int nb_samples);
//释放AVAudioFifo
void av_audio_fifo_free(AVAudioFifo *af);
查询操作:
//返回fifo中当前存储的采样数量
int av_audio_fifo_size(AVAudioFifo *af);
//返回fifo中当前可写的采样数量,即尚未使用的空间数量
int av_audio_fifo_space(AVAudioFifo *af);
// 以上两个函数的返回值之和等于AVAudioFifo的缓冲区大小
读取操作:
//将采样写入到AVAudioFifo
//成功则返回实际写入的采样数,如果写入成功,返回值必定等于nb_samples,失败返回负的错误值
int av_audio_fifo_write(AVAudioFifo *af, void **data, int nb_samples);
//peek:读取数据,但读到的数据并不会从fifo中删除
int av_audio_fifo_peek(AVAudioFifo *af, void **data, int nb_samples);
//从指定的偏移位置peek数据
int av_audio_fifo_peek_at(AVAudioFifo *af, void **data, int nb_samples, int offset);
//读取数据,读到的数据会从fifo中删除
int av_audio_fifo_read(AVAudioFifo *af, void **data, int nb_samples);
//从fifo中删除nb_samples个采样
int av_audio_fifo_drain(AVAudioFifo *af, int nb_samples);
//删除fifo中的所有采样,清空
void av_audio_fifo_reset(AVAudioFifo *af);
音频转码
有了AVAudioFifo,那么我们音频的转码流程就变成了以下这样子:
解封装 -> 提取音频流 -> 解码成PCM->将PCM数据写入AVAudioFifo -> 每次从AVAudioFifo获取1024个采样点送进aac编码器 -> 重新编码成AAC
如果到了最后没有可输入的PCM数据了,但是AVAudioFifo中可读取的采样点数依然不满足aac的1024个采样点的话,可以通过填充静音的方式补充…
上代码:
#include
extern "C" {
#include
#include
#include
#include
}
class Mp3ToAAC {
public:
void mp3_to_aac(const char *mp3, const char *aac) {
avFormatContext = avformat_alloc_context();
int ret = avformat_open_input(&avFormatContext, mp3, nullptr, nullptr);
if (ret < 0) {
std::cout codec_type) {
audio_index = i;
std::cout channels = av_get_channel_layout_nb_channels(encode_CodecContext->channel_layout);
// 如果解码出来的pcm不是44100的则需要进行重采样,重采样需要主要音频时长不变
encode_CodecContext->sample_rate = 44100;
// 比如使用 22050的采样率进行编码,编码后的时长明显是比实际音频长的
// encode_CodecContext->sample_rate = 22050;
encode_CodecContext->codec_type = AVMEDIA_TYPE_AUDIO;
encode_CodecContext->sample_fmt = AV_SAMPLE_FMT_FLTP;
encode_CodecContext->profile = FF_PROFILE_AAC_LOW;
//ffmpeg默认的aac是不带adts,而fdk_aac默认带adts,这里我们强制不带
encode_CodecContext->flags = AV_CODEC_FLAG_GLOBAL_HEADER;
// 打开编码器
ret = avcodec_open2(encode_CodecContext, aac_encoder, nullptr);
if (ret < 0) {
char error[1024];
av_strerror(ret, error, 1024);
std::cout pb, aac, AVIO_FLAG_WRITE);
if (ret < 0) {
std::cout streams[audio_index]->codecpar);
ret = avcodec_open2(avCodecContext, decoder, nullptr);
if (ret < 0) {
std::cout frame_size;
encode_frame->sample_rate = encode_CodecContext->sample_rate;
encode_frame->channel_layout = encode_CodecContext->channel_layout;
encode_frame->channels = encode_CodecContext->channels;
encode_frame->format = encode_CodecContext->sample_fmt;
av_frame_get_buffer(encode_frame, 0);
// 初始化audiofifo
audiofifo = av_audio_fifo_alloc(encode_CodecContext->sample_fmt, encode_CodecContext->channels,
encode_CodecContext->frame_size);
while (true) {
ret = av_read_frame(avFormatContext, avPacket);
if (ret < 0) {
std::cout data),
encode_CodecContext->frame_size);
if (ret < 0) {
std::cout pts = cur_pts;
ret = avcodec_send_frame(encode_CodecContext, encode_frame);
if (ret < 0) {
std::cout
关注
打赏
最近更新
- 深拷贝和浅拷贝的区别(重点)
- 【Vue】走进Vue框架世界
- 【云服务器】项目部署—搭建网站—vue电商后台管理系统
- 【React介绍】 一文带你深入React
- 【React】React组件实例的三大属性之state,props,refs(你学废了吗)
- 【脚手架VueCLI】从零开始,创建一个VUE项目
- 【React】深入理解React组件生命周期----图文详解(含代码)
- 【React】DOM的Diffing算法是什么?以及DOM中key的作用----经典面试题
- 【React】1_使用React脚手架创建项目步骤--------详解(含项目结构说明)
- 【React】2_如何使用react脚手架写一个简单的页面?