跳转至

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 test.wav

  • 下面是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;
}