音视频编程: iOS 使用 faac 编码

简介

本文分享如何将 WAV 格式的音频文件转换(编码)为 AAC 格式的音频文件并使用 AVAudioPlayer 播放编码后的文件.

这里的编码功能使用 faac 这个库来实现.

该系列博文:

函数介绍

获取 faac 的版本

1
int FAACAPI faacEncGetVersion(char **faac_id_string, char **faac_copyright_string);

示例:

1
2
3
4
char *version;
char *copyright;
faacEncGetVersion(&version, &copyright);
printf("FAAC version: %s, copyright: %s", version, copyright);

打印结果:

1
2
3
4
FAAC version: 1.28, copyright: FAAC - Freeware Advanced Audio Coder (http://www.audiocoding.com/)
Copyright (C) 1999,2000,2001 Menno Bakker
Copyright (C) 2002,2003 Krzysztof Nikiel
This software is based on the ISO MPEG-4 reference source code.

打开并初始化 faac 编码器

1
2
3
4
5
6
7
8
// sampleRate: 采样率
// numChannels: 通道数量,1-单声道 2-立体声
// inputSamples: 编码后的数据长度
// maxOutputBytes: 编码后的信息最大长度
faacEncHandle FAACAPI faacEncOpen(unsigned long sampleRate,
unsigned int numChannels,
unsigned long *inputSamples,
unsigned long *maxOutputBytes);

该函数返回一个 faac 编码器句柄. 其很多函数都需要这个句柄.

关闭 faac 编码器

1
int FAACAPI faacEncClose(faacEncHandle hEncoder);

将开启 faac 编码器返回的句柄传入即可.

获取配置和设置配置

1
2
3
4
5
6
faacEncConfigurationPtr FAACAPI
faacEncGetCurrentConfiguration(faacEncHandle hEncoder);
int FAACAPI faacEncSetConfiguration(faacEncHandle hEncoder,
faacEncConfigurationPtr config);

faac 的配置被定义为了一个结构体 faacEncConfiguration, 大家可以看源码.

编码(编码一帧音频数据)

1
2
3
4
5
6
7
8
// hEncoder: faacEncOpen 返回的编码器句柄
// inputBuffer: 输入信息缓冲区
// samplesInput: faacEncOpen编码后的数据长度,即缓冲区长度
// outputBuffer: 编码后输出信息缓冲区
// bufferSize: 输出信息长度
int FAACAPI faacEncEncode(faacEncHandle hEncoder, int32_t * inputBuffer, unsigned int samplesInput,
unsigned char *outputBuffer,
unsigned int bufferSize);

实例

可以自己新建一个 iOS 工程.

准备好一个 wav 格式的音频文件.

效果如下图所示:
1

新建一个 C++ 文件, MZCodec.

MZCodec.hpp

1
2
3
4
5
6
7
8
#ifndef MZCodec_hpp
#define MZCodec_hpp
#include <stdio.h>
int codeWAV(const char *srcFilePath, const char *destPath);
#endif /* MZCodec_hpp */

MZCodec.cpp

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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
#include "MZCodec.hpp"
#include "faac.h"
#include <stdio.h>
int codeWAV(const char *srcFilePath, const char *destPath) {
unsigned long nSampleRate = 44100;//采样率
unsigned int nChannels = 2;//声道数
unsigned int nPCMBitSize = 16;//单样本位数
unsigned long nInputSamples = 0;
unsigned long nMaxOutputBytes = 0;
int nRet;
faacEncHandle hEncoder;
faacEncConfigurationPtr pConfiguration;
size_t nBytesRead;
unsigned long nPCMBufferSize;
unsigned char *pbPCMBuffer;
unsigned char *pbAACBuffer;
FILE *fpIn; // WAV file for input
FILE *fpOut; // AAC file for output
/// 获取 faac 版本信息
{
char *version;
char *copyright;
faacEncGetVersion(&version, &copyright);
printf("FAAC version: %s, copyright: %s", version, copyright);
}
fpIn = fopen(srcFilePath, "rb");
if (NULL == fpIn) {
return -2;
}
fpOut = fopen(destPath, "wb");
/// 1. 打开 FAAC
hEncoder = faacEncOpen(nSampleRate, nChannels, &nInputSamples, &nMaxOutputBytes);
if (NULL == hEncoder) {
printf("[ERROR] Failed to call faacEncOpen()\n");
return -1;
}
nPCMBufferSize = nInputSamples * nPCMBitSize / 8;
pbPCMBuffer = new unsigned char[nPCMBufferSize];
pbAACBuffer = new unsigned char[nMaxOutputBytes];
/// 2.1. 获取当前的编码器配置
pConfiguration = faacEncGetCurrentConfiguration(hEncoder);
pConfiguration->inputFormat = FAAC_INPUT_16BIT;
// 对象类型只有为 LOW, iOS 的 AVAudioPlayer 才能播放
pConfiguration->aacObjectType = LOW;
// 0 = Raw; 1 = ADTS
pConfiguration->outputFormat = 1;
pConfiguration->mpegVersion = MPEG4;
pConfiguration->useTns = 1;
pConfiguration->bitRate = 30;
/// 2.2. 配置编码器
nRet = faacEncSetConfiguration(hEncoder, pConfiguration);
//是wav格式, 先读取前面的
fseek(fpIn, 58, SEEK_SET);
do {
//读入的实际字节数,最大不会超过 nPCMBufferSize
nBytesRead = fread(pbPCMBuffer, 1, nPCMBufferSize, fpIn);
//输入样本数,用实际读入字节数计算
//一般只有读到文件尾时才不是 nPCMBufferSize/(nPCMBitSize/8)
nInputSamples = nBytesRead / (nPCMBitSize / 8);
/// 3. 编码
nRet = faacEncEncode(hEncoder,
(int *)pbPCMBuffer,
(unsigned int)nInputSamples,
pbAACBuffer,
(unsigned int)nMaxOutputBytes);
fwrite(pbAACBuffer, 1, nRet, fpOut);
printf("FaacEncEncode returns %d\n", nRet);
} while (nBytesRead > 0);
/// 4. 关闭 FAAC
nRet = faacEncClose(hEncoder);
delete[] pbPCMBuffer;
delete[] pbAACBuffer;
fclose(fpIn);
fclose(fpOut);
return 0;
}

使用 MZCodec 的 Controller 需要命名为 .mm 文件.

ViewController.mm

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
NSBundle *bundle = [NSBundle mainBundle];
NSString *resPath = [bundle pathForResource:@"m" ofType:@"wav"];
NSLog(@"The path of wav file: %@", resPath);
NSArray<NSString *> *docPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *destPath = [[docPath lastObject] stringByAppendingString:@"/out.aac"];
NSLog(@"The path of aac file: %@", destPath);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
codeWAV([resPath UTF8String], [destPath UTF8String]);
dispatch_async(dispatch_get_main_queue(), ^{
//转换完成.
});
});

编码过程需要点时间, 如果文件很大, 时间越久. 所以, 新开线程来进行编码.

编码完成后, 可在对应的沙盒目录找到 out.aac 文件.

具体代码在 Github 上面, 感兴趣的可以点击 前往.

注意事项

这个 aacObjectType 需要注意, 之前我在这里折腾了很久.

1
pConfiguration->aacObjectType = LOW;

如果设置为其他三种, 编码后的 aac 文件, AVAudioPlayer 播放不了, 初始化 AVAudioPlayer 就会报错, 报错信息如下:

1

坚持原创技术分享!