274 lines
7.0 KiB
Dart
274 lines
7.0 KiB
Dart
|
|
/**
|
|||
|
|
* G711Tool - G.711音频编解码工具类
|
|||
|
|
*
|
|||
|
|
* G.711是一种用于音频压缩的ITU-T标准,主要用于电话系统。
|
|||
|
|
* 它有两种主要变体:A-law(主要用于欧洲和国际电话系统)和μ-law(主要用于北美和日本)。
|
|||
|
|
*
|
|||
|
|
* 该类提供了PCM线性音频数据与G.711 A-law/μ-law格式之间的转换功能。
|
|||
|
|
* 编码过程将16位线性PCM样本压缩为8位,解码过程则相反。
|
|||
|
|
*/
|
|||
|
|
class G711Tool {
|
|||
|
|
// 常量定义
|
|||
|
|
static const int SIGN_BIT = 0x80; // A-law字节的符号位
|
|||
|
|
static const int QUANT_MASK = 0xf; // 量化字段掩码
|
|||
|
|
static const int NSEGS = 8; // A-law段数
|
|||
|
|
static const int SEG_SHIFT = 4; // 段号左移位数
|
|||
|
|
static const int SEG_MASK = 0x70; // 段字段掩码
|
|||
|
|
static const int BIAS = 0x84; // 线性编码的偏置值
|
|||
|
|
|
|||
|
|
// 查找表
|
|||
|
|
/**
|
|||
|
|
* μ-law到A-law的转换表
|
|||
|
|
* 用于在两种编码格式之间进行转换而不需要先解码为线性PCM
|
|||
|
|
*/
|
|||
|
|
static final List<int> _u2a = [
|
|||
|
|
// u- to A-law conversions
|
|||
|
|
1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8,
|
|||
|
|
9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
|
|||
|
|
25, 27, 29, 31, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44,
|
|||
|
|
46, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62,
|
|||
|
|
64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79,
|
|||
|
|
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, 106, 107, 108, 109, 110, 111, 112,
|
|||
|
|
113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127,
|
|||
|
|
128
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* A-law到μ-law的转换表
|
|||
|
|
* 用于在两种编码格式之间进行转换而不需要先解码为线性PCM
|
|||
|
|
*/
|
|||
|
|
static final List<int> _a2u = [
|
|||
|
|
// A- to u-law conversions
|
|||
|
|
1, 3, 5, 7, 9, 11, 13, 15, 16, 17, 18, 19, 20, 21, 22, 23,
|
|||
|
|
24, 25, 26, 27, 28, 29, 30, 31, 32, 32, 33, 33, 34, 34, 35, 35,
|
|||
|
|
36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 48, 49, 49,
|
|||
|
|
50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 64,
|
|||
|
|
65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 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, 106, 107, 108, 109, 110, 111,
|
|||
|
|
112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126,
|
|||
|
|
127
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 段结束值表
|
|||
|
|
* 用于确定PCM值应该映射到哪个段
|
|||
|
|
* 每个段代表不同的量化步长
|
|||
|
|
*/
|
|||
|
|
static final List<int> _segEnd = [
|
|||
|
|
0xFF,
|
|||
|
|
0x1FF,
|
|||
|
|
0x3FF,
|
|||
|
|
0x7FF,
|
|||
|
|
0xFFF,
|
|||
|
|
0x1FFF,
|
|||
|
|
0x3FFF,
|
|||
|
|
0x7FFF
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 在表中搜索值所属的段
|
|||
|
|
*
|
|||
|
|
* @param val 要搜索的值
|
|||
|
|
* @param table 段结束值表
|
|||
|
|
* @param size 表大小
|
|||
|
|
* @return 段索引
|
|||
|
|
*/
|
|||
|
|
static int _search(int val, List<int> table, int size) {
|
|||
|
|
for (int i = 0; i < size; i++) {
|
|||
|
|
if (val <= table[i]) return i;
|
|||
|
|
}
|
|||
|
|
return size;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 将线性PCM值转换为A-law编码值
|
|||
|
|
*
|
|||
|
|
* 转换步骤:
|
|||
|
|
* 1. 确定符号位和掩码
|
|||
|
|
* 2. 对负值进行特殊处理
|
|||
|
|
* 3. 确定段号
|
|||
|
|
* 4. 根据段号计算A-law值
|
|||
|
|
*
|
|||
|
|
* @param pcmVal 16位线性PCM值
|
|||
|
|
* @return 8位A-law编码值
|
|||
|
|
*/
|
|||
|
|
static int _linear2alaw(int pcmVal) {
|
|||
|
|
int mask;
|
|||
|
|
int seg;
|
|||
|
|
int aval;
|
|||
|
|
|
|||
|
|
if (pcmVal >= 0) {
|
|||
|
|
mask = 0xD5; // sign (7th) bit = 1
|
|||
|
|
} else {
|
|||
|
|
mask = 0x55; // sign bit = 0
|
|||
|
|
pcmVal = -pcmVal - 1;
|
|||
|
|
if (pcmVal < 0) pcmVal = 32767;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
seg = _search(pcmVal, _segEnd, 8);
|
|||
|
|
|
|||
|
|
if (seg >= 8) return 0x7F ^ mask;
|
|||
|
|
|
|||
|
|
aval = seg << SEG_SHIFT;
|
|||
|
|
if (seg < 2) {
|
|||
|
|
aval |= (pcmVal >> 4) & QUANT_MASK;
|
|||
|
|
} else {
|
|||
|
|
aval |= (pcmVal >> (seg + 3)) & QUANT_MASK;
|
|||
|
|
}
|
|||
|
|
return aval ^ mask;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 将A-law编码值转换为线性PCM值
|
|||
|
|
*
|
|||
|
|
* 转换步骤:
|
|||
|
|
* 1. 异或操作恢复原始位模式
|
|||
|
|
* 2. 提取量化值和段号
|
|||
|
|
* 3. 根据段号计算线性值
|
|||
|
|
* 4. 应用符号位
|
|||
|
|
*
|
|||
|
|
* @param aVal 8位A-law编码值
|
|||
|
|
* @return 16位线性PCM值
|
|||
|
|
*/
|
|||
|
|
static int _alaw2linear(int aVal) {
|
|||
|
|
int t;
|
|||
|
|
int seg;
|
|||
|
|
|
|||
|
|
aVal ^= 0x55;
|
|||
|
|
|
|||
|
|
t = (aVal & QUANT_MASK) << 4;
|
|||
|
|
seg = (aVal & SEG_MASK) >> SEG_SHIFT;
|
|||
|
|
|
|||
|
|
switch (seg) {
|
|||
|
|
case 0:
|
|||
|
|
t += 8;
|
|||
|
|
break;
|
|||
|
|
case 1:
|
|||
|
|
t += 0x108;
|
|||
|
|
break;
|
|||
|
|
default:
|
|||
|
|
t += 0x108;
|
|||
|
|
t <<= seg - 1;
|
|||
|
|
}
|
|||
|
|
return (aVal & SIGN_BIT) != 0 ? t : -t;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 将线性PCM值转换为μ-law编码值
|
|||
|
|
*
|
|||
|
|
* 转换步骤与A-law类似,但使用不同的偏置和掩码
|
|||
|
|
*
|
|||
|
|
* @param pcmVal 16位线性PCM值
|
|||
|
|
* @return 8位μ-law编码值
|
|||
|
|
*/
|
|||
|
|
static int _linear2ulaw(int pcmVal) {
|
|||
|
|
int mask;
|
|||
|
|
int seg;
|
|||
|
|
int uval;
|
|||
|
|
|
|||
|
|
if (pcmVal < 0) {
|
|||
|
|
pcmVal = BIAS - pcmVal;
|
|||
|
|
mask = 0x7F;
|
|||
|
|
} else {
|
|||
|
|
pcmVal += BIAS;
|
|||
|
|
mask = 0xFF;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
seg = _search(pcmVal, _segEnd, 8);
|
|||
|
|
|
|||
|
|
if (seg >= 8) return 0x7F ^ mask;
|
|||
|
|
|
|||
|
|
uval = (seg << 4) | ((pcmVal >> (seg + 3)) & 0xF);
|
|||
|
|
return uval ^ mask;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 将μ-law编码值转换为线性PCM值
|
|||
|
|
*
|
|||
|
|
* @param uVal 8位μ-law编码值
|
|||
|
|
* @return 16位线性PCM值
|
|||
|
|
*/
|
|||
|
|
static int _ulaw2linear(int uVal) {
|
|||
|
|
uVal = ~uVal;
|
|||
|
|
int t = ((uVal & QUANT_MASK) << 3) + BIAS;
|
|||
|
|
t <<= (uVal & SEG_MASK) >> SEG_SHIFT;
|
|||
|
|
return (uVal & SIGN_BIT) != 0 ? (BIAS - t) : (t - BIAS);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 将A-law编码值转换为μ-law编码值
|
|||
|
|
*
|
|||
|
|
* 使用查找表直接转换,避免解码再编码的性能损失
|
|||
|
|
*
|
|||
|
|
* @param aval 8位A-law编码值
|
|||
|
|
* @return 8位μ-law编码值
|
|||
|
|
*/
|
|||
|
|
static int alaw2ulaw(int aval) {
|
|||
|
|
aval &= 0xff;
|
|||
|
|
return (aval & 0x80) != 0
|
|||
|
|
? (0xFF ^ _a2u[aval ^ 0xD5])
|
|||
|
|
: (0x7F ^ _a2u[aval ^ 0x55]);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 将μ-law编码值转换为A-law编码值
|
|||
|
|
*
|
|||
|
|
* 使用查找表直接转换,避免解码再编码的性能损失
|
|||
|
|
*
|
|||
|
|
* @param uval 8位μ-law编码值
|
|||
|
|
* @return 8位A-law编码值
|
|||
|
|
*/
|
|||
|
|
static int ulaw2alaw(int uval) {
|
|||
|
|
uval &= 0xff;
|
|||
|
|
return (uval & 0x80) != 0
|
|||
|
|
? (0xD5 ^ (_u2a[0xFF ^ uval] - 1))
|
|||
|
|
: (0x55 ^ (_u2a[0x7F ^ uval] - 1));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 将PCM数据编码为G.711格式
|
|||
|
|
*
|
|||
|
|
* @param pcm 输入PCM数据
|
|||
|
|
* @param lawFlag 编码类型标志: 0表示A-law, 1表示μ-law
|
|||
|
|
* @return 编码后的G.711数据
|
|||
|
|
*/
|
|||
|
|
static List<int> encode(List<int> pcm, int lawFlag) {
|
|||
|
|
List<int> code = List<int>.filled(pcm.length, 0);
|
|||
|
|
|
|||
|
|
if (lawFlag == 0) {
|
|||
|
|
for (int i = 0; i < pcm.length; i++) {
|
|||
|
|
code[i] = _linear2alaw(pcm[i]);
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
for (int i = 0; i < pcm.length; i++) {
|
|||
|
|
code[i] = _linear2ulaw(pcm[i]);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return code;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 将G.711数据解码为PCM格式
|
|||
|
|
*
|
|||
|
|
* @param code 输入G.711数据
|
|||
|
|
* @param lawFlag 编码类型标志: 0表示A-law, 1表示μ-law
|
|||
|
|
* @return 解码后的PCM数据
|
|||
|
|
*/
|
|||
|
|
static List<int> decode(List<int> code, int lawFlag) {
|
|||
|
|
List<int> pcm = List<int>.filled(code.length, 0);
|
|||
|
|
|
|||
|
|
if (lawFlag == 0) {
|
|||
|
|
for (int i = 0; i < code.length; i++) {
|
|||
|
|
pcm[i] = _alaw2linear(code[i]);
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
for (int i = 0; i < code.length; i++) {
|
|||
|
|
pcm[i] = _ulaw2linear(code[i]);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return pcm;
|
|||
|
|
}
|
|||
|
|
}
|