音视频编程: iOS 使用 faad2

本次分享使用 faad2 解码 AAC 音频文件, 将 AAC 文件转换为 WAV 文件并使用 AVAudioPlayer 进行播放.

在博文 音视频编程: 简单分析 WAV 文件 给大家简单的分析了一下 WAV 的数据头协议, 其实也是为了这篇博文来服务的, 所以阅读本文之前, 建议先看上文.

该系列博文:

例子简介

本文以一个实际的例子, 使用 faad2的各个函数来解码 AAC 数据.

主要有以下几个步骤:

  • 获取输入文件
  • 获取 faad 解码器句柄
  • 初始化 faad 解码器
  • 根据文件解析文件帧, 并写入输出文件中
  • 写入文件头将其封装为 WAV 格式的音频文件
  • 关闭 faad 解码器句柄

工程实战

引入 faad2

将编译好的 faad2 导入工程即可.

1
其中关键的 API 在 neaacdec.h 中有描述.

工程效果图:
1

编码实现

音视频编程: 简单分析 WAV 文件 中已经定义了 WAV 的数据头.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct MZWavAudioFileHeader
{
char riff[4]; // 字符串 "RIFF"
uint32_t totalLength; // 文件总大小, 包括PCM 数据大小和该文件头大小
char wave[4]; // 字符串 "WAVE"
char fmt[4]; // 字符串 "fmt "
uint32_t format; // WAV 头大小, 固定为值 16
uint16_t pcm; // PCM 编码方式, 固定值为 1
uint16_t channels; // 声道数量, 为 2
uint32_t frequency; // 采样频率
uint32_t bytes_per_second; // 每秒字节数(码率), 其值=采样率x通道数x位深度/8
uint16_t bytes_by_capture; // 采样块大小
uint16_t bits_per_sample; // 采样点大小, 这里是 16 位
char data[4]; // 字符串 "data"
uint32_t bytes_in_pcmdata; // pcm 数据长度
};

现在实现写入数据头的方法 mz_write_wav_header

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
/**
* 写入 wav 头数据.
*
* @param file wav 文件指针.
* @param total_samples_per_channel 每个声道的采样数.
* @param samplerate 采样率.
* @param channels 声道数.
*/
void mz_write_wav_header(FILE *file, int total_samples_per_channel, int samplerate, int channels) {
if (NULL == file) {
return;
}
if (total_samples_per_channel <= 0) {
return;
}
printf("FAAD. total_samples_per_channel: %i, samplerate: %i, channels: %i\n",
total_samples_per_channel, samplerate, channels);
struct MZWavAudioFileHeader wavHeader;
// 写入 RIFF
strcpy(wavHeader.riff, "RIFF");
wavHeader.bits_per_sample = 16;
wavHeader.totalLength = (total_samples_per_channel * channels * wavHeader.bits_per_sample/8) + sizeof(wavHeader) - 8;
// 写入 WAVE 和 fmt
strcpy(wavHeader.wave, "WAVE");
strcpy(wavHeader.fmt, "fmt ");
wavHeader.format = 16;
wavHeader.pcm = 1;
wavHeader.channels = channels;
wavHeader.frequency = samplerate;
// 每秒的字节数(码率)=采样率x通道数x位深度/8
wavHeader.bytes_per_second = wavHeader.channels * wavHeader.frequency * wavHeader.bits_per_sample/8;
wavHeader.bytes_by_capture = wavHeader.channels*wavHeader.bits_per_sample/8;
wavHeader.bytes_in_pcmdata = total_samples_per_channel * wavHeader.channels * wavHeader.bits_per_sample/8;
// 写入 data
strcpy(wavHeader.data, "data");
fwrite(&wavHeader, 1, sizeof(wavHeader), file);
}

解码主要用到了 FAAD2 中的 NeAACDecDecode 函数. 函数原型如下:

1
2
3
4
void* NEAACDECAPI NeAACDecDecode(NeAACDecHandle hDecoder,
NeAACDecFrameInfo *hInfo,
unsigned char *buffer,
unsigned long buffer_size);

对应帧定义的结构体: NeAACDecFrameInfo, 定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
typedef struct NeAACDecFrameInfo
{
unsigned long bytesconsumed;
unsigned long samples;
unsigned char channels;
unsigned char error;
unsigned long samplerate;
/* SBR: 0: off, 1: on; upsample, 2: on; downsampled, 3: off; upsampled */
unsigned char sbr;
/* MPEG-4 ObjectType */
unsigned char object_type;
/* AAC header type; MP4 will be signalled as RAW also */
unsigned char header_type;
/* multichannel configuration */
unsigned char num_front_channels;
unsigned char num_side_channels;
unsigned char num_back_channels;
unsigned char num_lfe_channels;
unsigned char channel_position[64];
/* PS: 0: off, 1: on */
unsigned char ps;
} NeAACDecFrameInfo;

具体的解码实现, 我放到了 Github 上面了, 大家可以去 这里 查看.

麻烦

解码 aac, 解决采样频率和通道数不对的问题

1
2
3
4
//防止采样频率加倍
NeAACDecConfigurationPtr conf = NeAACDecGetCurrentConfiguration(decoder);
conf->dontUpSampleImplicitSBR = 1;
NeAACDecSetConfiguration(decoder, conf);
1
2
3
4
5
6
//从双声道的数据中提取单通道
for(i=0,j=0; i<4096 && j<2048; i+=4, j+=2) {
frame_mono[j]=pcm_data[i];
frame_mono[j+1]=pcm_data[i+1];
}

具体可以查阅 FAAD2 的源码. 感谢 使用FAAD库解码AAC实例及 及 faad解码后的通道数不正确的问题 提供.

坚持原创技术分享!