Skip to main content

无线音频控制系统技术文档

一、概述

本系统为无线音频传输与控制系统,主要包含 TX(发送端)RX(接收端)

  • TX端:采集音频(MIC/I2S/USB),处理音频数据并通过无线协议发送,同时发送控制状态字(1字节)。
  • RX端:接收音频包和控制状态字,解析控制命令,触发对应功能(LED、音效、噪声控制等)。

控制状态通过 1 字节命令字传输,其中:

  • 高 4 位:同步码 INFO_BIT_SYNC(0xA0),保证 RX 能正确解析
  • 低 4 位:功能位(如 INFO_BIT_BLUENS),表示各功能开关状态

二、关键宏与变量

1. 功能位宏

#define INFO_BIT_BLUENS 1   // 蓝牙噪声消除
//#define INFO_BIT_BATLOW 2 // 其他功能位,可扩展
#define INFO_BIT_MASK 0x0F // 低4位为功能位
#define INFO_BIT_SYNC 0xA0 // 高4位同步码

2. 状态字变量

uint8_t InfoBits = INFO_BIT_SYNC;  // 1字节状态字:高4位同步码,低4位功能位

3. 操作接口

uint8_t InfoGet(void) { return InfoBits; }  // 获取当前状态字
  • InfoBitsSet(bit):设置对应功能位
  • InfoBitsClear(bit):清除对应功能位

三、TX端控制状态流程

  1. 事件触发

系统事件或按键触发:

case MSG_EFFECT_DENOISE:
InfoBitsSet(INFO_BIT_BLUENS); // 设置功能位
LedStateSet(); // LED 状态更新
break;
  1. 状态获取
uint8_t state = InfoGet(); // 返回 InfoBits
  1. 音频包构建与发送

audio_process() 中,每个音频包发送时,状态字随音频数据一起打包:

#ifdef KEY_REMOTE
audio_PackBuild(sbc_buf_out, sbcCnt, InfoGet());
#else
audio_PackBuild(sbc_buf_out, sbcCnt, 0xFF); // 没有遥控状态时填充默认值
#endif
  • 说明:每个音频包都携带最新的状态字,保证 RX 端能实时更新功能状态。

四、RX端控制状态解析

接收端通过 cmd_process() 解析状态字:

if((RemoteMsg & (~INFO_BIT_MASK)) == INFO_BIT_SYNC)  // 校验同步码
{
if(RemoteMsg & INFO_BIT_BLUENS)
InfoBitsSet(INFO_BIT_BLUENS); // 启用对应功能
else
InfoBitsClear(INFO_BIT_BLUENS); // 关闭功能
}
  • 高 4 位同步码校验通过后,低 4 位表示功能状态
  • RX 内部状态变量 InfoBits 被更新
  • 功能触发(如 LED 指示、音效处理)根据状态执行

五、音频处理流程 (audio_process())

1. 核心功能

  • MIC/I2S/USB音频采集
  • 音频效果处理(可选)
  • 单声道 → 双声道转换
  • 音频打包、SBC 编码
  • 与控制状态字一起通过无线发送

2. 主要步骤

void audio_process(void)
{
// 计算抖动
#ifdef AUDIO_WIRELESS_SYNC_EN
if(AudioInSyncFrames <= AUDIOIN_SYNC_COUNT && AudioInSyncOffset > AUDIOIN_SYNC_JITTER)
Jitter = AudioInSyncOffset * 2 / 3;
#endif

// 判断是否有足够音频帧
#ifdef CODEC_DMAHALF_MODE
if(AudioADC1_DataLenGet_DMA_HALF() >= frame_size + Jitter && source_mic && sink_wireless_out)
#else
if(AudioADC1_DataLenGet() >= frame_size + Jitter && source_mic && sink_wireless_out)
#endif
{
// 清理音频缓冲区
PT_BUF_CLEAR(source_mic, PT_NET_FRAME(source_mic) * 4);
PT_BUF_CLEAR(source_usb_in, PT_NET_FRAME(source_usb_in) * 4);
PT_BUF_CLEAR(source_i2s_in, PT_NET_FRAME(source_i2s_in) * 4);

// MIC采集数据
#ifdef CODEC_DMAHALF_MODE
AudioADC1_DataGet_DMA_HALF(source_mic, frame_size - ADC_DeleteSample);
#else
AudioADC1_DataGet(source_mic, frame_size - ADC_DeleteSample);
#endif

// 单声道转双声道
upmix_1to2_apply(source_mic, source_mic, frame_size - ADC_DeleteSample);

// USB / I2S音频处理(可选)
UsbAudioSpeakerDataGet(source_usb_in, frame_size);

// 提示音混音处理(可选)
AudioRemindMixData(source_remind, frame_size);

// 音效处理或直接无线透传
AudioWirelessBypassProcess(...);

// SBC 编码并发送,每个音频包附带 InfoBits 状态字
#ifdef KEY_REMOTE
audio_PackBuild(sbc_buf_out, sbcCnt, InfoGet());
#else
audio_PackBuild(sbc_buf_out, sbcCnt, 0xFF);
#endif
}
}

六、状态字周期发送机制

  • TX端:每个音频包都包含 InfoBits
  • RX端:每次解析音频包,更新功能状态
  • 保证状态实时性:无需额外指令,只需修改 InfoBits

七、功能位设计原则

  1. 可扩展性:低 4 位用于功能状态,未来可增加更多功能
  2. 同步性:高 4 位固定同步码 INFO_BIT_SYNC,防止误解析
  3. 单字节传输:符合无线传输带宽限制
  4. 软件控制:通过 InfoBitsSet() / InfoBitsClear() 修改状态

八、TX → RX 控制状态流程图

┌─────────────┐
│ TX端事件/按键 │
│ SystemEventProcess() │
└───────┬─────┘


┌─────────────┐
│ InfoBitsSet/ │
│ InfoBitsClear│
└───────┬─────┘

┌─────────────┐
│ InfoGet() │
└───────┬─────┘

┌─────────────┐
│ audio_PackBuild() │
│ SBC数据 + InfoBits │
└───────┬─────┘

┌─────────────┐
│ 无线发送音频包 │
└───────┬─────┘

┌─────────────┐
│ RX端接收音频包 │
└───────┬─────┘

┌─────────────┐
│ 校验同步码 InfoBits & 0xF0 │
│ 解析低4位功能状态 │
└───────┬─────┘

┌─────────────┐
│ RX端功能触发 │
│ LED / 音效 / 蓝牙噪声控制 │
└─────────────┘

九、总结

  1. InfoBits 是 TX → RX 功能状态的载体
  2. InfoGet() 提供接口获取状态字
  3. 每个音频包都携带状态字,保证 RX 实时更新
  4. 宏定义功能位便于扩展,保证代码可维护性
  5. 系统设计简单、高效、低带宽,占用无线传输资源少

这份文档可以作为开发和维护无线音频控制系统的技术说明书,涵盖了:

  • 代码结构
  • 控制状态字设计
  • 音频处理流程
  • TX → RX 状态传输机制
  • 可扩展设计原则

/******************** TX 端音频处理主流程 ****************************/
void audio_process(void)
{
uint32_t CycCnt_Start; // 记录本帧处理起始 CPU cycle,用于 MCPS 统计
uint16_t Jitter = 0; // 音频同步抖动补偿采样数
// static unsigned char send_audio_cnt = 1; // 音频子包计数(通常为静态)

/* ===================== 音频同步抖动计算 ===================== */
#ifdef AUDIO_WIRELESS_SYNC_EN
// 当无线音频同步启用,并且同步帧数在允许范围内
// 同时检测到输入采样偏移,且偏移超过抖动阈值
if ((AudioInSyncFrames <= AUDIOIN_SYNC_COUNT) &&
(AudioInSyncOffset != 0) &&
(AudioInSyncOffset > AUDIOIN_SYNC_JITTER))
{
// 使用 2/3 偏移量作为丢样值,避免一次性修正过猛
Jitter = AudioInSyncOffset * 2 / 3;
}
#endif

/* ===================== 判断是否具备一帧处理条件 ===================== */
#ifdef CODEC_DMAHALF_MODE
// DMA half 模式下,检查 ADC half buffer 中的数据量
if ((AudioADC1_DataLenGet_DMA_HALF() >= frame_size + Jitter)
#else
// 普通模式下,检查 ADC buffer 中的数据量
if ((AudioADC1_DataLenGet() >= frame_size + Jitter)
#endif
&& source_mic // 麦克风源存在
&& sink_wireless_out) // 无线输出通道存在
{
uint32_t sbc_len = 0; // SBC 编码输出长度
// 记录当前 CPU cycle,用于统计本帧耗时
CycCnt_Start = __nds32__mfsr(NDS32_SR_PFMC0);

#ifdef CFG_FUNC_I2S_IN_DELAY_LOCK_EN
// 记录 ADC / I2S 采样数,用于后续时钟漂移校正
ADCSamples = AudioADC1_DataLenGet_DMA_HALF();
I2SDMASamples = I2SDMADataLenGet();
// I2S 与 ADC 采样率不同,进行比例换算
I2SDMASamples = I2SDMASamples * 441 / 480;
I2SSamples = I2SDateLenGet();
#endif

/* ===================== 清空各音频源 Buffer(防残留干扰) ===================== */
#ifdef CFG_FUNC_AUDIO_EFFECT_EN
// 开启音效时,清空音效源缓冲
AudioEffectFlowSourceClear();
#else
// 未开启音效时,清空各音频输入通道
PT_BUF_CLEAR(source_remind, PT_NET_FRAME(source_remind) * 4);
PT_BUF_CLEAR(source_mic, PT_NET_FRAME(source_mic) * 4);
PT_BUF_CLEAR(source_usb_in, PT_NET_FRAME(source_usb_in) * 4);
PT_BUF_CLEAR(source_i2s_in, PT_NET_FRAME(source_i2s_in) * 4);
#endif

/* ===================== 同步抖动处理(丢弃多余采样) ===================== */
#ifdef AUDIO_WIRELESS_SYNC_EN
if (Jitter)
{
#ifdef CODEC_DMAHALF_MODE
// 丢弃 Jitter 个 ADC 采样,用于时钟同步
AudioADC1_DataGet_DMA_HALF(MICInPcmBuf, Jitter);
#else
AudioADC1_DataGet(MICInPcmBuf, Jitter);
#endif
}
// 同步偏移已处理,清零
AudioInSyncOffset = 0;
#endif

/* ===================== 获取麦克风音频数据 ===================== */
#ifdef CODEC_DMAHALF_MODE
// 从 ADC 取一帧 PCM 数据(单声道)
AudioADC1_DataGet_DMA_HALF(source_mic, frame_size - ADC_DeleteSample);
#else
AudioADC1_DataGet(source_mic, frame_size - ADC_DeleteSample);
#endif
// 记录本帧输入采样数
get_inram_audiolen = frame_size - ADC_DeleteSample;

/* ===================== 正弦波测试信号(调试用) ===================== */
#ifdef AUDIO_SINE_TEST_EN
for (int i = 0; i < frame_size; i++)
{
source_mic[i] = SineMono[FrameOffset++ % TEST_PERIOD] / 2;
}
#endif

/* ===================== 单声道 → 双声道 ===================== */
// 麦克风输入为单声道,后续处理统一使用双声道
upmix_1to2_apply(source_mic, source_mic, frame_size - ADC_DeleteSample);

/* ===================== I2S 音频输入 ===================== */
#ifdef AUDIO_SOURCE_I2SIN
if (source_i2s_in && IS_PT_WIRELESSOUT_NET(source_i2s_in))
{
I2SInDateGet(source_i2s_in, frame_size);
}
#endif

/* ===================== ADC / I2S 同步删除采样 ===================== */
if (ADC_DeleteSample)
{
ADC_DeleteSample = 0;
#ifdef CFG_FUNC_I2S_IN_DELAY_LOCK_EN
if (I2S_DeleteSample)
{
// 删除 I2S 对应采样,保持多路同步
I2SDMADataProcess(I2S_DeleteSample);
memset(source_i2s_in, 0, frame_size * 4);
I2S_DeleteSample = 0;
}
#endif
#if defined(WIRELESS_TURNKEY5_3)
// 某些方案下直接静音一帧
memset(source_mic, 0, frame_size * 4);
#else
return; // 不支持时直接退出本帧
#endif
}

/* ===================== I2S / ADC 延迟锁定算法 ===================== */
#ifdef CFG_FUNC_I2S_IN_DELAY_LOCK_EN
{
static int32_t SampleSTotal = 0;
static int32_t Cnt = 0;

Cnt++;
// 计算 I2S 与 ADC 采样差
SampleSTotal += I2SSamples + I2SDMASamples - ADCSamples;

if (Cnt == 4)
{
SampleSTotal /= Cnt;
Cnt = 0;

// 根据偏差决定输入微调方向
if (SampleSTotal >= 81)
in_adjust = -1;
else if (SampleSTotal <= 79)
in_adjust = 1;

SampleSTotal = 0;
}
}
#endif

/* ===================== USB Audio 输入处理 ===================== */
#ifdef AUDIO_SOURCE_USB_AUDIO_IN_EN
// USB Speaker → 无线发送
if (source_usb_in && IS_PT_WIRELESSOUT_NET(source_usb_in))
{
UsbAudioSpeakProcess();

if ((UsbAudioSpeakerDataLenGet() > frame_size) &&
UsbSpeakerStateGet())
{
// 取一帧 USB PCM 数据
UsbAudioSpeakerDataGet(source_usb_in, frame_size);

// 音量渐变处理
...
}
UsbAudioSpeakProcess();
}
#endif

/* ===================== 提示音混音 ===================== */
#ifdef REMIND_EN
if (source_remind)
{
AudioRemindMixData(source_remind, frame_size);
// 单声道 → 双声道
for (int i = frame_size; i > 0; i--)
{
source_remind[2*(i-1)] = source_remind[i-1];
source_remind[2*i-1] = source_remind[i-1];
}
// 提示音音量控制
Audio_gain_control(...);
}
#endif

/* ===================== 音效 / 混音 ===================== */
#ifdef CFG_FUNC_AUDIO_EFFECT_EN
// 双声道音效处理
roboeffect_apply(context_memory);
#else
// 多路音频直通混音,输出到无线 / DAC / USB / I2S
AudioWirelessBypassProcess(
source_mic, source_usb_in, source_i2s_in, source_remind,
sink_dac, sink_usb_out, sink_wireless_out, sink_i2s_out,
frame_size);
#endif

/* ===================== 编码前声道处理 ===================== */
if (ENCODE_CH == 1)
{
// 编码为单声道时,双声道下混
downmix_2to1_apply(sink_wireless_out, sink_wireless_out, frame_size);
}

/* ===================== SBC 编码 ===================== */
if (device1.ConStatus != CONNECT_NONE)
{
wireless_sbc_encoder_aplly(
&sbc_enc,
sink_wireless_out,
&sbc_buf_out[SBC_ENC_LEN_PER_FREME * (send_audio_cnt - 1)],
&sbc_len);

/* ===================== 音频包构建 + 无线发送 ===================== */
if (((send_audio_cnt % RFPACK_NAUDIO) == 0) &&
(Wireless_TransSpaceLen() > RFAUDIO_TRANS_LEN))
{
send_audio_cnt = 1;

#ifdef KEY_REMOTE
// 音频包中附带 1 字节控制信息(TX → RX)
audio_PackBuild(sbc_buf_out, sbcCnt, InfoGet());
#else
audio_PackBuild(sbc_buf_out, sbcCnt, 0xFF);
#endif
// 写入无线发送 FIFO
Wireless_TransBufWrite(&sbc_buf_out[CRC_PACKSUB],
RFAUDIO_TRANS_LEN);
get_inram_audiolen = 0;
}
else
{
send_audio_cnt++;
}
sbcCnt++;
}

/* ===================== 本地音频输出 ===================== */
#ifndef PACKET_AUDIO_CH_BACKWARD
#ifdef DAC0_EN
if (sink_dac && IS_PT_WIRELESSOUT_NET(sink_dac))
{
downmix_2to1_apply(sink_dac, sink_dac, frame_size);
AudioDAC0_DataSet(sink_dac, frame_size);
}
#endif
#endif

/* ===================== 性能统计 ===================== */
LastEncodeCyc = __nds32__mfsr(NDS32_SR_PFMC0) - CycCnt_Start;
CycCnt = LastEncodeCyc;
}

/* ===================== MCPS 调试统计 ===================== */
#ifdef DEBUG_LOG_AUDIO_MCPS_EN
uint16_t McpsVal = CycCnt * (SAMPLE_RATE / frame_size) / 1000000 + 30;
if (McpsVal > McpsMax)
{
McpsMax = McpsVal;
DBG("AudioMax:%d M\n", McpsVal);
}
#endif
}