i2s输出6路16k音频方案
1. 简介
- 本文介绍了一种在
8008/8008c
上i2s输出 6通道16k音频(16bit PCM格式) 的方案。4通道8k和12通道8k类似,可以参考实现。8008/8008c最高输出i2s采样率是48k,所以最高的输出带宽是2通道48k。
- 标准的
I2S(Inter-IC Sound)
协议是一种用于数字音频传输的串行协议,通常用于连接音频编解码器和数字信号处理器。它仅支持双通道音频传输,即左声道和右声道。扩展的协议TDM(Time-Division Multiplexing)
实现了多通道音频传输。虽然8008/8008c
的i2s输出不支持TDM协议,但是能通过提高i2s的时钟采样率,使得6通道16k的音频通过2通道48k传输。
2. 详解

- 如图所示,通过软件把6路16k的数据编辑成2路48k数据,再正常调用i2s输出接口把数据输出。
- 如图所示,由于无法实现同步传输,接收方有可能会以L1 L2 L0 或 L2 L0 L1 的顺序接收到数据。为了解决这个问题,我们可以为每个声道的数据添加标记。首先,我们可以将L0和R0的最后一位设置为1,而其他声道(L1、L2、R1、R2)的最后一位设置为0。损失了最小一位的精度,对音频质量基本没有影响。
- 接收方在接收到数据后,首先解析出L0和R0的数据,通过检查最后一位是否为1来确定。然后,接收方可以根据L0和R0的数据来判断其他声道的顺序。这样,接收方就可以正确地解析出不同声道的数据。
3. VSP使用示例
- vsp_sdk提供一个参考例子,把4个mic数据和2个ref数据通过i2s输出。
cp configs/example_lib/8008c_wukong_v1.4_example_lib_i2sout_6ch_16kdata_4mic+2ref.config .config
make clean; make
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 | IRAM0_TEXT_ATTR int VspDoI2sOutput6Channel16kData(VSP_CONTEXT *context)
{
VSP_CONTEXT_HEADER *ctx_header = context->ctx_header;
int ref_num = ctx_header->ref_num;
int mic_num = ctx_header->mic_num;
if (mic_num != 4 || ref_num != 2)
return -1;
short *mic[4];
short *ref[2];
for (int i = 0; i < mic_num; i++) {
mic[i] = VspProcessGetMicFrame(context,i,0); //获取到N个mic数据
}
for (int i = 0; i < ref_num; i++) {
ref[i] = VspProcessGetRefFrame(context,i,0); //获取到N个ref数据
}
VspCopy6Channel16kDataTo2Channel48kSpk(context, mic[0], mic[1], mic[2], mic[3], ref[0], ref[1]);
return 0;
}
|
4. linux平台上解码示例
在linux平台,通过 tinycap 录音48K双通道,16bit PCM音频
tinycap sdcard/test.wav -D 0 -d 1 -c 2 -b 16 -r 48000
将录制的音频解析后,按照通道顺序重组为PCM音频

- 下面是swapChannel的源代码。swapChannel把乱序通道的音频重组为顺序通道的音频。实现把获取到的3个通道16k数据
record.pcm
按照最小位解析,还原回原来的实际传输的顺序output.pcm
。接收方可以参考高亮部分。

swapChannel.c |
---|
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 | #include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
// Number of channels in the data
#define NUM_CHANNELS (3)
// Number of samples to read
#define SAMPLES_PER_READ (100 * NUM_CHANNELS)
/**
* Swaps the channels of an audio data array.
*
* @param data - Pointer to the audio data array
* @param numSamples - Number of samples in the audio data array
*/
void swapChannels(uint16_t* data, int numSamples)
{
// Iterate over the audio data array
for (int i = 0; i < numSamples; i += NUM_CHANNELS) {
// Iterate over the channels
for (int c = 0; c < NUM_CHANNELS; c++) {
// Check if the least significant bit of the current sample is 1
if ((data[i + c] & 0x0001) == 1) {
int temp[NUM_CHANNELS];
// Swap the channels by rotating the array elements
for (int j = 0; j < NUM_CHANNELS; j++) {
temp[j] = data[i + ((j + c) % NUM_CHANNELS)];
}
// Copy the swapped channels back to the audio data array
for (int k = 0; k < NUM_CHANNELS; k++) {
data[i + k] = temp[k];
}
}
}
}
}
int main(int argc, char *argv[]) {
const char* inputFileName = argv[1];
const char* outputFileName = "output.pcm";
FILE* inputFile = fopen(inputFileName, "rb");
if (!inputFile) {
perror("Error opening input file");
return 1;
}
FILE* outputFile = fopen(outputFileName, "wb");
if (!outputFile) {
perror("Error opening output file");
fclose(inputFile);
return 1;
}
int numChannels = NUM_CHANNELS;
int numSamples = 0;
int samplesPerRead = SAMPLES_PER_READ;
fseek(inputFile, 0, SEEK_END);
long fileSize = ftell(inputFile);
numSamples = fileSize / sizeof(uint16_t);
fseek(inputFile, 0, SEEK_SET);
printf("## numSample %d\n", numSamples);
uint16_t* pcmData = (uint16_t*)malloc(numSamples * sizeof(uint16_t));
if (!pcmData) {
perror("Memory allocation error");
fclose(inputFile);
fclose(outputFile);
return 1;
}
int samplesRead;
uint16_t pcmDataL[samplesPerRead];
uint16_t pcmDataR[samplesPerRead];
while ((samplesRead = fread(pcmData, sizeof(uint16_t), samplesPerRead*2, inputFile)) > 0) {
for (int i = 0; i < samplesPerRead; i++) {
pcmDataL[i] = pcmData[2*i];
pcmDataR[i] = pcmData[2*i+1];
}
swapChannels(pcmDataL, samplesPerRead);
swapChannels(pcmDataR, samplesPerRead);
for (int i = 0; i < samplesPerRead; i++) {
pcmData[2*i] = pcmDataL[i];
pcmData[2*i+1] = pcmDataR[i];
}
size_t bytesWritten = fwrite(pcmData, sizeof(uint16_t), samplesRead, outputFile);
if (bytesWritten != samplesRead) {
perror("Error writing output file");
break;
}
}
// Clean up
free(pcmData);
fclose(inputFile);
fclose(outputFile);
printf("Channels swapped and output to %s\n", outputFileName);
return 0;
}
|