ESP2-I2S音频播放笔记

ESP32 电子创客营 1019次浏览 已收录 0个评论 扫描二维码

ESP32的I2S设计的比较奇怪,或者也可以说比较强大。I2S在我们印象中是用来传输音频数字信号的通信接口,但是参考ESP32的数据手册会发现远远不止如此。初次看他这部分的手册总是会把人看的云里雾里。

ESP32的硬件I2S可以实现功能主要有以下几个场景:

  • 驱动LCD液晶屏
  • 可以连接CAMERA
  • 可以连接内部DA实现音频播放
  • 可以连接内部AD实现录音

下面笔记记录的都是和音频播放相关,其他模式暂时没做讨论。

ESP32一共有两个I2SI2S0、I2S1。

但是只有I2S0支持连接内部ADC和DAC,也就是要实现录音和播放只能使用I2S0。

当   I2S—ADC 要把I2S0配置为主机接收模式

当   I2S—DAC 要把I2S0配置为主机发送模式

I2S的时钟源有两个:

  • PLL_D2_CLK(ADC/DAC模式下要使用该时钟)
  • APLL_CLK 

使用ESP32-IDF配置音频播放的时候,不需要手动指定哪底层已经封装好了

I2S  FIFO读写:

ESP32的FIFO读写长度都是按照32位来的。而对FIFO的操作方式有两种:一种是通过CPU直接进行操作;另一种是通过DMA进行读写操作。音频播放呢就是把写入FIFO中的数据通过DMA搬到DA然后输出一个模拟量。录音就是通过DMA把AD采集到的值搬倒FIFO里面去,然后读走保存。

关于FIFO的配置还涉及到单声道双声道的问题,可以去研读esp32参考手册。

音频播放位数问题:

理论上我们采集的音频使用的AD位数越高,失真率就越小,播放出来音质就会越高。但是ESP32有个限制就是DA输出是8位的,也就是不管你用16位音源还是12位或者8位音源最终都会被转成8位的送到DAC去播放。所以这个也限制了ESP32使用内部DA播放做不到太好的音质。

播放的音频文件也没必要用16位采样的音源,浪费存储空间,数据还要进行转换才能送入DA播放。

音频播放初始化代码:

static void csound_audioInit()
{
    i2s_config_t i2s_config = {
.mode = I2S_MODE_MASTER | I2S_MODE_TX | I2S_MODE_DAC_BUILT_IN,
.sample_rate = 16000,
.bits_per_sample = 16,
     .communication_format = I2S_COMM_FORMAT_I2S_MSB,
     .channel_format = I2S_CHANNEL_FMT_ONLY_RIGHT, //影响数据传输的格式,根据音频文件进行选择
     .intr_alloc_flags = 0,
     .dma_buf_count = 4,
     .dma_buf_len = 256,
    };
i2s_driver_install(I2S_NUM_0, &i2s_config, 0, NULL); //install and start i2s driver
i2s_set_dac_mode(I2S_DAC_CHANNEL_RIGHT_EN); //可以控制有几个声道出声音
}
static void csound_audioDeinit()
{
i2s_set_dac_mode(I2S_DAC_CHANNEL_DISABLE);
i2s_driver_uninstall(I2S_NUM_0);
}

把音频数据进行转换的代码:

// Scale data to 16bit/32bit for I2S DMA output.
// DAC can only output 8bit data value.
// I2S DMA will still send 16 bit or 32bit data, the highest 8bit contains DAC data.
// 并且根据音量计算出播放DA值的大小
static int csound_audioDataScale(uint8_t* d_buff, uint8_t* s_buff, uint32_t len)
{
	float scale=csoundVolume*0.01;
	float value=0;
	uint32_t j = 0;
	for (int i = 0; i < len; i++) {
		d_buff[j++] = 0;
		value=(float)s_buff[i]*scale; //根据音量大小成比例的调整DA输出值的幅度
		d_buff[j++] = (uint8_t)value;
	}
	return (len * 2);
}

我们的音频PCM数据采样位数是8位,但是在DMA传输过程中,最少是一次传输16位(前面提到FIFO按照32位来读写和这里不矛盾,低16位会自动被填充0)。那么我们就要把8位的数据转换成16位的,再送去FIFO。

这里我自己加了个音量转换,最终送出去的数据,会根据音量大小进行成比例的放大缩小。

最后就是播放音频的代码:

//播放音频
static void csound_audioPlay(audioTableStruct *ats)
{
	size_t bytes_written;
	uint8_t* i2s_write_buff = (uint8_t*) calloc(4096, sizeof(char));
	int offset = 0;
	int tot_size=ats->length;
	while (offset < tot_size) 
	{
		int play_len = ((tot_size - offset) > (4 * 256)) ? (4 * 256) : (tot_size - offset);
		int i2s_wr_len = csound_audioDataScale(i2s_write_buff, (uint8_t*)(ats->ptable + offset), play_len);
		i2s_write(I2S_NUM_0, i2s_write_buff, i2s_wr_len, &bytes_written, portMAX_DELAY);
		offset += play_len;
	}
	free(i2s_write_buff);
}

播放的思路,就是分段从我的音频table里面读数据,读回来以后进行数据转换,然后再写入i2s缓冲区中,等待DMA把他都搬入DA,重复执行直到播放完毕。

ESP2-I2S音频播放笔记

转载请注明转自电子创客营:ESP2-I2S音频播放笔记! 了解我们点击这里
喜欢 (4)or分享 (0)
电子创客营
关于作者:

您必须 登录 才能发表评论!