转:PCM音频数据
PCM(Pulse Code Modulation,脉冲编码调制)音频数据是未经压缩的音频采样数据裸流,它是由模拟信号经过采样、量化、编码转换成的标准数字音频数据。
描述PCM数据的6个参数:
推荐的PCM数据播放工具:
//播放格式为f32le,单声道,采样频率48000Hz的PCM数据
ffplay -f f32le -ac 1 -ar 48000 pcm_audio
如果是单声道的音频文件,采样数据按时间的先后顺序依次存入(有的时候也会采用LRLRLR方式存储,只是另一个声道的数据为0),如果是双声道的话就按照LRLRLR的方式存储,存储的时候与字节序有关。big-endian模式如下图所示:
PCM.png
使用ffmpeg -formats命令,获取ffmpeg支持的音视频格式,其中我们可以找到支持的PCM格式。
DE alaw PCM A-law
DE f32be PCM 32-bit floating-point big-endian
DE f32le PCM 32-bit floating-point little-endian
DE f64be PCM 64-bit floating-point big-endian
DE f64le PCM 64-bit floating-point little-endian
DE mulaw PCM mu-law
DE s16be PCM signed 16-bit big-endian
DE s16le PCM signed 16-bit little-endian
DE s24be PCM signed 24-bit big-endian
DE s24le PCM signed 24-bit little-endian
DE s32be PCM signed 32-bit big-endian
DE s32le PCM signed 32-bit little-endian
DE s8 PCM signed 8-bit
DE u16be PCM unsigned 16-bit big-endian
DE u16le PCM unsigned 16-bit little-endian
DE u24be PCM unsigned 24-bit big-endian
DE u24le PCM unsigned 24-bit little-endian
DE u32be PCM unsigned 32-bit big-endian
DE u32le PCM unsigned 32-bit little-endian
DE u8 PCM unsigned 8-bit
s是有符号,u是无符号,f是浮点数。
be是大端,le是小端。
FFmpeg中音视频数据基本上都有Packed和Planar两种存储方式,对于双声道音频来说,Packed方式为两个声道的数据交错存储;Planar方式为两个声道分开存储。假设一个L/R为一个采样点,数据存储的方式如下所示:
FFmpeg音频解码后的数据是存放在AVFrame结构中的。
下面为FFmpeg内部存储音频使用的采样格式,所有的Planar格式后面都有字母P标识。
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_S64, ///< signed 64 bits
AV_SAMPLE_FMT_S64P, ///< signed 64 bits, planar
AV_SAMPLE_FMT_NB ///< Number of sample formats. DO NOT USE if linking dynamically
};
说明:
谈到字节序的问题,必然牵涉到两大CPU派系。那就是Motorola的PowerPC系列CPU和Intel的x86系列CPU。PowerPC系列采用big endian方式存储数据,而x86系列则采用little endian方式存储数据。那么究竟什么是big endian,什么又是little endian?
big endian是指低地址存放最高有效字节(MSB,Most Significant Bit),而little endian则是低地址存放最低有效字节(LSB,Least Significant Bit)。
下面用图像加以说明。比如数字0x12345678在两种不同字节序CPU中的存储顺序如下所示:
Big Endian
低地址 高地址
----------------------------------------------------------------------------->
| 12 | 34 | 56 | 78 |
Little Endian
低地址 高地址
----------------------------------------------------------------------------->
| 78 | 56 | 34 | 12 |
所有网络协议都是采用big endian的方式来传输数据的。所以也把big endian方式称之为网络字节序。当两台采用不同字节序的主机通信时,在发送数据之前都必须经过字节序的转换成为网络字节序后再进行传输。
6.1 分离双声道PCM音频数据左右声道的数据
按照双声道的LRLRLR的PCM音频数据可以通过将它们交叉的读出来的方式来分离左右声道的数据。
int pcm_s16le_split(const char* file, const char* out_lfile, const char* out_rfile) {
FILE *fp = fopen(file, "rb+");
if (fp == NULL) {
printf("open %s failed\n", file);
return -1;
}
FILE *fp1 = fopen(out_lfile, "wb+");
if (fp1 == NULL) {
printf("open %s failed\n", out_lfile);
return -1;
}
FILE *fp2 = fopen(out_rfile, "wb+");
if (fp2 == NULL) {
printf("open %s failed\n", out_rfile);
return -1;
}
char * sample = (char *)malloc(4);
while(!feof(fp)) {
fread(sample, 1, 4, fp);
//L
fwrite(sample, 1, 2, fp1);
//R
fwrite(sample + 2, 1, 2, fp2);
}
free(sample);
fclose(fp);
fclose(fp1);
fclose(fp2);
return 0;
}
作者:smallest_one
链接:https://www.jianshu.com/p/fd43c1c82945
对于ffmpeg来说,音频数据会保存在AVFrame中extended_data数组中,如果是打包模式(packed),就只用extended_data[0],如果是planar模式,则每个channel分别保存在extended_data[i]中。对于音频,只有linesize[0]有效,打包模式保存整个音频帧的buff大小,planar模式保存每个channel的buff大小。
ffmpeg中对两种模式(planar和packed)的说明(在frame.h文件中有详细说明):
* For planar audio, each channel has a separate data pointer, and
* linesize[0] contains the size of each channel buffer.
* For packed audio, there is just one data pointer, and linesize[0]
* contains the total size of the buffer for all channels.
下面是一个小例子来存储格式数据(利用ffmpeg):
short *sample_buffer_L = pFrame->extended_data[0];//存放着左声道的数据
short *sample_buffer_R = pFrame->extended_data[1];//存放着右声道的数据
两者都是16bit,而裸的PCM文件里的数据是按照 LRLRLRLR 这样存储的,所以我们需要按照这种格式存储16bit的数据:
//Left channel
data[i] = (char)(sample_buffer_L[j] & 0xff);//左声道低8位
data[i+1] = (char)((sample_buffer_L[j]>>8) & 0xff);;//左声道高8位
//Right channel
data[i+2] = (char)(sample_buffer_R[j] & 0xff);//右声道低8位
data[i+3] = (char)((sample_buffer_R[j]>>8) & 0xff);;//右声道高8位
https://my.oschina.net/u/589963/blog/167526