前段时间,在学习试用 FFmpeg 播放音频的时候总是有杂音,网上的很多教程是基于之前版本的 FFmpeg 的,而新的 FFmepg3 中 audio 增加了平面(planar)格式,而 SDL 播放音频是不支持平面格式的,所以通过 FFmpeg 解码出来的数据不能直接发送到 SDL 进行播放,需要进行一个格式转换。通过网上一些资料,也能够正确的播放音频了,但是对具体的音频转换过程不是很了解,这里就对 FFmpeg 的对音频的存储格式及格式转换做个总结。本文主要有以下几个方面的内容:
在新版中已废弃,替换为使用更为简单的
- avcodec_decode_audio4
和
- avcodec_send_packet
。本文简单的介绍了该 API 的使用。
- avcodec_receive_frame
在 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
是一个 int 型,在使用
- format
时候需要进行一个类型转换,将 int 转换为
- AVSampleFormat
枚举值。
- AVSampleFormat
在头文件
提供了和音频采样格式相关的一些函数,现列举一些如下:
- samplefmt.h
根据枚举值获取其相应的格式名称(字符串)
- const char *av_get_sample_fmt_name(enum AVSampleFormat sample_fmt)
根据格式名字(字符串)获取相应的枚举值
- enum AVSampleFormat av_get_sample_fmt(const char *name)
传入 planar 类型的采样格式,返回其可转换的 packed 类型的采样格式。例如传入
- enum AVSampleFormat av_get_packed_sample_fmt(enum AVSampleFormat sample_fmt)
,其返回值为
- AV_SAMPLE_FMT_S32P
。
- AV_SAMPLE_FMT_S32
和上面函数类似,不同的是传入的是 packed 类型的格式。
- enum AVSampleFormat av_get_planar_sample_fmt(enum AVSampleFormat sample_fmt)
判断一个采样格式是不是 planar 类型的
- int av_sample_fmt_is_planar(enum AVSampleFormat sample_fmt
每个采样值所占用的字节数
- int av_get_bytes_per_sample(enum AVSampleFormat sample_fmt)
根据输入的参数,计算其所占用空间的大小(字节数)。
- int av_samples_get_buffer_size(int *linesize, int nb_channels, int nb_samples,enum AVSampleFormat sample_fmt, int align)
可设为 null,align 是 buff 空间的对齐格式(0=default,1 = no alignment)
- linesize
从上面可知,sample 有两种类型的存储方式:平面(planar)和打包(packed),在 planar 中每一个通道独自占用一个存储平面;在 packed 中,所有通道的 sample 交织存储在同一个
平面。但是,对于 planar 格式不知道具体的某一通道所在的平面;对于 packed 格式各个通道的数据是以怎么样的顺序交织存储的。这就需要借助于 channel_layout。
首先来看下 FFmpeg 对 channel_layout 的定义:
channel_layout 是一个 64 位整数,每个值为 1 的位对应一个通道。也就说,
的位模式中值为 1 的个数等于其通道数量。
- channel_layout
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.
在头文件
中为将每个通道定义了一个 mask,其定义如下:
- channel_layout.h
- #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)...
是立体声(2 通道),其通道的存放顺序为
- AV_CH_LAYOUT_STEREO
;
- LEFT | RIGHT
是 4 通道,其通道的存放顺序为
- AV_CH_LAYOUT_4POINT0
;其它数量的声道与此类似。 下面列举一些和 channel_layout 相关的函数
- LEFT|RIGHT|FRONT-CENTER|BACK-CENTER
根据传入的字符串,返回相对应的 channel_layout。传入的参数可以是:
- uint64_t av_get_channel_layout(const char *name)
根据通道的 layout 返回通道的个数
- int av_get_channel_layout_nb_channels(uint64_t channel_layout)
根据通道的个数返回默认的 layout
- int64_t av_get_default_channel_layout(int nb_channels)
返回通道在 layout 中的 index,也就是某一通道 在 layout 的存储位置。
- int av_get_channel_layout_channel_index(uint64_t channel_layout,uint64_t channel);
的实现如下:
- av_get_channel_layout_channel_index
首先判断传入的 layout 包含该通道,并且保证该传入的通道是一个单通道。 以 4 通道
- 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);
- }
为例,说明下计算方法。
- 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
在该 layout 中的 index。
- AV_CH_BACK_CENTER
的十六进制为
- AV_CH_BACK_CENTER
,二进制为
- 0x0100
,那么
- 0001,0000,0000
。
- AV_CH_BACK_CENTER - 1 = 1111,1111
,函数
- 0001,0000,0111 & 0000,1111,1111 = 0111
是获取某个 layout 对应的通道的数量, 前面提到,layout 中值为 1 的位的个数和通道的数量相等,所以
- av_get_channel_layout_nb_channels
在 layout
- AV_CH_BACK_CENTER
的 index 为 3。
- AV_CH_LAYOUT_4POINT0
在 FFmpeg 中进行音频的格式转换主要有三个步骤
,并设置转换所需的参数:通道数量、channel layout、sample rate
- SwrContext
有以下两种方式来实例
,并设置参数:
- 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
上述两种方法设置那个的参数是将 5.1 声道,channel layout 为 AV_CH_LAYOUT_5POINT1,采样率为 48KHz 转换为 2 声道,channel_layout 为 AV_SAMPLE_FMT_S16,采样率为 44.1KHz。
- 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
函数
- 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));
是按照指定的舍入方式计算 a * b / c 。
- av_rescale_rnd
得到输入 sample 和输出 sample 之间的延迟,并且其返回值的根据传入的第二个参数不同而不同。如果是输入的采样率,则返回值是输入 sample 个数;如果输入的是输出采样率,则返回值是输出 sample 个数。
- swr_get_delay
进行转换
- swr_convert
其返回值为转换的 sample 个数。
- int nb = swr_convert(swr_ctx, &audio_buf, dst_nb_samples, (const uint8_t * *) frame - >data, frame - >nb_samples);
和
- 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;
这里不再使用进行音频的解码,在 FFmpeg3 中该函数已被废弃,使用
- avcodec_decode_audio4
和
- avcodec_send_packet
替代。新的解码 API 使用更为方便,
- avcodec_receive_frame
具体参见官方文档。
如果 channel layout 未知(channel_layout = 0),根据通道数量获取其默认的 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);
(16 位有符号整型),输出的 channel layout 也 根据通道数量设置为默认值
- AV_SAMPLE_FMT_S16
(SDL2 不支持 planar 格式)。实例化
- dst_layout = av_get_default_channel_layout(frame->channels)
- 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);
中保存的是转换的数据的字节数:通道数 * sample 个数 * 每个 sample 的字节数。
- data_size
本文主要介绍了在 FFmepg 中对音频两个重要属性:采样格式和 channel layout 的表示方法,并简单的实现了一个音频的格式转换。
本文代码
来源: http://www.cnblogs.com/wangguchangqing/p/5851490.html