分类: Windows平台
2017-05-22 14:39:15
出处:http://www.cnblogs.com/wangguchangqing/p/5851490.html
前段时间,在学习试用FFmpeg播放音频的时候总是有杂音,网上的很多教程是基于之前版本的FFmpeg的,而新的FFmepg3中audio增加了平面(planar)格式,而SDL播放音频是不支持平面格式的,所以通过FFmpeg解码出来的数据不能直接发送到SDL进行播放,需要进行一个格式转换。通过网上一些资料,也能够正确的播放音频了,但是对具体的音频转换过程不是很了解,这里就对FFmpeg的对音频的存储格式及格式转换做个总结。本文主要有以下几个方面的内容:
音频解码API avcodec_decode_audio4在新版中已废弃,替换为使用更为简单的avcodec_send_packet和avcodec_receive_frame。本文简单的介绍了该API的使用。
在FFmpeg中使用枚举AVSampleFormat表示音频的采样格式,其声明如下:
enum AVSampleFormat { AV_SAMPLE_FMT_NONE = -1, AV_SAMPLE_FMT_U8, ///< unsigned 8 bits AV_SAMPLE_FMT_S16, ///< signed 16 bits AV_SAMPLE_FMT_S32, ///< signed 32 bits AV_SAMPLE_FMT_FLT, ///< float AV_SAMPLE_FMT_DBL, ///< double AV_SAMPLE_FMT_U8P, ///< unsigned 8 bits, planar AV_SAMPLE_FMT_S16P, ///< signed 16 bits, planar AV_SAMPLE_FMT_S32P, ///< signed 32 bits, planar AV_SAMPLE_FMT_FLTP, ///< float, planar AV_SAMPLE_FMT_DBLP, ///< double, planar AV_SAMPLE_FMT_NB ///< Number of sample formats. DO NOT USE if linking dynamically };
和图像的像素存储格式类似,可以使用8位无符号整数、16位有符号整数、32位有符号整数以及单精度浮点数,双精度浮点数表示一个采样。但是,没有使用
24位的有符号整数,这是因为这些不同的格式使用的是原生的C类型,而C中是没有24位的长度的类型的。
Sample value can be expressed by native C types,hence the lack of a signed 24-bit sample format even though
it is a common raw audio data format.
对于浮点格式,其值在[-1.0,1.0]之间,任何在该区间之外的值都超过了最大音量的范围。
和YUV的图像格式格式,音频的采样格式分为平面(planar)和打包(packed)两种类型,在枚举值中上半部分是packed类型,后面(有P后缀的)是planar类型。
对于planar格式的,每一个通道的值都有一个单独的plane,所有的plane必须有相同的大小;对于packed类型,所有的数据在同一个数据平面中,不同通道的数据
交叉保存。
另外,在AVFrame中表示音频采样格式的字段format是一个int型,在使用AVSampleFormat时候需要进行一个类型转换,将int转换为AVSampleFormat枚举值。
在头文件samplefmt.h提供了和音频采样格式相关的一些函数,现列举一些如下:
从上面可知,sample有两种类型的存储方式:平面(planar)和打包(packed),在planar中每一个通道独自占用一个存储平面;在packed中,所有通道的sample交织存储在同一个
平面。但是,对于planar格式不知道具体的某一通道所在的平面;对于packed格式各个通道的数据是以怎么样的顺序交织存储的。这就需要借助于channel_layout。
首先来看下FFmpeg对channel_layout的定义:
channel_layout是一个64位整数,每个值为1的位对应一个通道。也就说,channel_layout的位模式中值为1的个数等于其通道数量。
A channel_layout is a 64-bits interget with a bit set for every channel.The number of bits set must be equal to the number of channels.
在头文件channel_layout.h中为将每个通道定义了一个mask,其定义如下:
#define AV_CH_FRONT_LEFT 0x00000001 #define AV_CH_FRONT_RIGHT 0x00000002 #define AV_CH_FRONT_CENTER 0x00000004 #define AV_CH_LOW_FREQUENCY 0x00000008 #define AV_CH_BACK_LEFT 0x00000010 #define AV_CH_BACK_RIGHT 0x00000020 #define AV_CH_FRONT_LEFT_OF_CENTER 0x00000040 #define AV_CH_FRONT_RIGHT_OF_CENTER 0x00000080 #define AV_CH_BACK_CENTER 0x00000100 #define AV_CH_SIDE_LEFT 0x00000200 #define AV_CH_SIDE_RIGHT 0x00000400 #define AV_CH_TOP_CENTER 0x00000800 #define AV_CH_TOP_FRONT_LEFT 0x00001000 #define AV_CH_TOP_FRONT_CENTER 0x00002000 #define AV_CH_TOP_FRONT_RIGHT 0x00004000 #define AV_CH_TOP_BACK_LEFT 0x00008000 #define AV_CH_TOP_BACK_CENTER 0x00010000 #define AV_CH_TOP_BACK_RIGHT 0x00020000 #define AV_CH_STEREO_LEFT 0x20000000 ///< Stereo downmix. #define AV_CH_STEREO_RIGHT 0x40000000 ///< See AV_CH_STEREO_LEFT.
这样,一个channel_layout就是上述channel mask的组合,部分定义如下:
#define AV_CH_LAYOUT_MONO (AV_CH_FRONT_CENTER) #define AV_CH_LAYOUT_STEREO (AV_CH_FRONT_LEFT|AV_CH_FRONT_RIGHT) #define AV_CH_LAYOUT_2POINT1 (AV_CH_LAYOUT_STEREO|AV_CH_LOW_FREQUENCY) #define AV_CH_LAYOUT_2_1 (AV_CH_LAYOUT_STEREO|AV_CH_BACK_CENTER) #define AV_CH_LAYOUT_SURROUND (AV_CH_LAYOUT_STEREO|AV_CH_FRONT_CENTER) #define AV_CH_LAYOUT_3POINT1 (AV_CH_LAYOUT_SURROUND|AV_CH_LOW_FREQUENCY) #define AV_CH_LAYOUT_4POINT0 (AV_CH_LAYOUT_SURROUND|AV_CH_BACK_CENTER) #define AV_CH_LAYOUT_4POINT1 (AV_CH_LAYOUT_4POINT0|AV_CH_LOW_FREQUENCY) #define AV_CH_LAYOUT_2_2 (AV_CH_LAYOUT_STEREO|AV_CH_SIDE_LEFT|AV_CH_SIDE_RIGHT) #define AV_CH_LAYOUT_QUAD (AV_CH_LAYOUT_STEREO|AV_CH_BACK_LEFT|AV_CH_BACK_RIGHT) #define AV_CH_LAYOUT_5POINT0 (AV_CH_LAYOUT_SURROUND|AV_CH_SIDE_LEFT|AV_CH_SIDE_RIGHT) #define AV_CH_LAYOUT_5POINT1 (AV_CH_LAYOUT_5POINT0|AV_CH_LOW_FREQUENCY) ...
AV_CH_LAYOUT_STEREO是立体声(2通道),其通道的存放顺序为LEFT | RIGHT;AV_CH_LAYOUT_4POINT0是4通道,其通道的存放顺序为
LEFT|RIGHT|FRONT-CENTER|BACK-CENTER;其它数量的声道与此类似。
下面列举一些和channel_layout相关的函数
int av_get_channel_layout_channel_index(uint64_t channel_layout,uint64_t channel); 返回通道在layout中的index,也就是某一通道
在layout的存储位置。
av_get_channel_layout_channel_index的实现如下:
int av_get_channel_layout_channel_index(uint64_t channel_layout,
uint64_t channel) { if (!(channel_layout & channel) ||
av_get_channel_layout_nb_channels(channel) != 1) return AVERROR(EINVAL);
channel_layout &= channel - 1; return av_get_channel_layout_nb_channels(channel_layout);
}
首先判断传入的layout包含该通道,并且保证该传入的通道是一个单通道。
以4通道AV_CH_LAYOUT_4POINT0为例,说明下计算方法。AV_CH_LAYOUT_4POINT0 = AV_CH_FRONT_LEFT | AV_CH_FRONT_RIGHT | AV_CH_FRONT_CENTER | AV_CH_BACK_CENTER
其二进制表示为0001,0000,0111,假如想找AV_CH_BACK_CENTER在该layout中的index。AV_CH_BACK_CENTER的十六进制为0x0100,二进制为0001,0000,0000,那么
AV_CH_BACK_CENTER - 1 = 1111,1111。 0001,0000,0111 & 0000,1111,1111 = 0111,函数av_get_channel_layout_nb_channels是获取某个layout对应的通道的数量,
前面提到,layout中值为1的位的个数和通道的数量相等,所以AV_CH_BACK_CENTER在layoutAV_CH_LAYOUT_4POINT0的index为3。
在FFmpeg中进行音频的格式转换主要有三个步骤
有以下两种方式来实例SwrContext,并设置参数:
使用swr_alloc
SwrContext *swr = swr_alloc(); av_opt_set_channel_layout(swr, "in_channel_layout", AV_CH_LAYOUT_5POINT1, 0); av_opt_set_channel_layout(swr, "out_channel_layout", AV_CH_LAYOUT_STEREO, 0); av_opt_set_int(swr, "in_sample_rate", 48000, 0); av_opt_set_int(swr, "out_sample_rate", 44100, 0); av_opt_set_sample_fmt(swr, "in_sample_fmt", AV_SAMPLE_FMT_FLTP, 0); av_opt_set_sample_fmt(swr, "out_sample_fmt", AV_SAMPLE_FMT_S16, 0);
使用 swr_alloc_set_opts
SwrContext *swr = swr_alloc_set_opts(NULL, // we're allocating a new context AV_CH_LAYOUT_STEREO, // out_ch_layout AV_SAMPLE_FMT_S16, // out_sample_fmt 44100, // out_sample_rate AV_CH_LAYOUT_5POINT1, // in_ch_layout AV_SAMPLE_FMT_FLTP, // in_sample_fmt 48000, // in_sample_rate 0, // log_offset NULL); // log_ctx
上述两种方法设置那个的参数是将5.1声道,channel layout为AV_CH_LAYOUT_5POINT1,采样率为48KHz转换为2声道,channel_layout为AV_SAMPLE_FMT_S16,采样率为44.1KHz。
计算转换后的sample个数
转后后的sample个数的计算公式为:src_nb_samples * dst_sample_rate / src_sample_rate,其计算如下:
int dst_nb_samples = av_rescale_rnd(swr_get_delay(swr_ctx, frame->sample_rate) + frame->nb_samples, frame->sample_rate, frame->sample_rate, AVRounding(1));
函数av_rescale_rnd是按照指定的舍入方式计算a * b / c 。调用 swr_convert进行转换
int nb = swr_convert(swr_ctx, &audio_buf, dst_nb_samples, (const uint8_t**)frame->data, frame->nb_samples);
其返回值为转换的sample个数。
首先使用avcodec_send_packet和avcodec_receive_frame获取解码后的原始数据
int ret = avcodec_send_packet(aCodecCtx, &pkt); if (ret < 0 && ret != AVERROR(EAGAIN) && ret != AVERROR_EOF)
return -1; ret = avcodec_receive_frame(aCodecCtx, frame); if (ret < 0 && ret != AVERROR_EOF)
return -1;
这里不再使用avcodec_decode_audio4进行音频的解码,在FFmpeg3中该函数已被废弃,使用avcodec_send_packet和avcodec_receive_frame替代。新的解码API使用更为方便,
具体参见官方文档。
设置通道数量和channel layout
在编码的时候有可能丢失通道数量或者channel layout ,这里根据获取的参数设置其默认值
if (frame->channels > 0 && frame->channel_layout == 0)
frame->channel_layout = av_get_default_channel_layout(frame->channels); else if (frame->channels == 0 && frame->channel_layout > 0)
frame->channels = av_get_channel_layout_nb_channels(frame->channel_layout);
如果channel layout未知(channel_layout = 0),根据通道数量获取其默认的channel layout;如同通道的数量未知,则根据其channel layout得到其通道数量。
设置输出格式
由于SDL2的sample格式不支持浮点型(FFmpeg中是支持的浮点型的),这里简单的设置输出格式为AV_SAMPLE_FMT_S16(16位有符号整型),输出的channel layout也
根据通道数量设置为默认值 dst_layout = av_get_default_channel_layout(frame->channels)(SDL2不支持planar格式)。实例化SwrContext
swr_ctx = swr_alloc_set_opts(nullptr, dst_layout, dst_format, frame->sample_rate,
frame->channel_layout, (AVSampleFormat)frame->format, frame->sample_rate, 0, nullptr); if (!swr_ctx || swr_init(swr_ctx) < 0)
return -1;
在设置完参数后,一定要调用swr_init进行初始化。
转换
// 计算转换后的sample个数 a * b / c int dst_nb_samples = av_rescale_rnd(swr_get_delay(swr_ctx, frame->sample_rate) + frame->nb_samples, frame->sample_rate, frame->sample_rate, AVRounding(1)); // 转换,返回值为转换后的sample个数 int nb = swr_convert(swr_ctx, &audio_buf, dst_nb_samples, (const uint8_t**)frame->data, frame->nb_samples);
data_size = frame->channels * nb * av_get_bytes_per_sample(dst_format);
最后data_size中保存的是转换的数据的字节数:通道数 * sample个数 * 每个sample的字节数。
本文主要介绍了在FFmepg中对音频两个重要属性:采样格式和channel layout的表示方法,并简单的实现了一个音频的格式转换。