ESP32音视频传输①用I2S通过内部DAC或MAX98357A播放音乐/录音数据及接受网络广播
Posted loveliveoil
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ESP32音视频传输①用I2S通过内部DAC或MAX98357A播放音乐/录音数据及接受网络广播相关的知识,希望对你有一定的参考价值。
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
I2S介绍
我这里只是简单介绍下,具体介绍及使用教程可看:https://diyi0t.com/i2s-sound-tutorial-for-esp32/
I2S用于收发音频信号,有三根线组成:
①串行时钟 (SCK)也称为位时钟线 (BCLK):用于在同一周期内获取所有组件。串行时钟的频率定义为:频率 = 采样率 * 每通道位数 * 通道数,例如对一个wav录音文件:
采样率:44.1 kHz
每通道位数:16
通道数:2
则串行时钟的频率为 44.1 kHz * 16 * 2 = 1.411 MHz。
②字选择 (WS)或帧选择 (FS) 线:
如果 WS = 0 → 使用通道 1(左通道)
如果 WS = 1 → 使用通道 2(右通道)
③串行数据 (SD)线:用于传输数据
ESP32有两个I2S接口,并且ESP32内部有两个8位的DAC分别对应GPIO25和GPIO26
(ESP8266没内部DAC,如果要播放录音需要外加MAX98357A)
注:而由于内部DAC方式声音太小,所以本实验优先考虑使用MAX98357A,对于内部DAC输出直接调用第三方库,而不直接对i2s就行配置了(主要原因是我直接配置内部DAC的i2s输出的声音很杂,肯定是哪里没配好,只能用第三方库了,等我搞懂了再更新吧)
一、使用外部DAC即MAX98357A播放录音
1.播放内存的录音数据
提示:主要是参考了https://www.xtronical.com/i2s-ep2/
完整示例可下载:https://www.xtronical.com/wp-content/uploads/2020/08/PlayWav.zip
我这里只是修改了一下i2s_num,以及对一些重要的注释翻译成中文
// Includes
#include "driver/i2s.h" // 适用于ESP32的I2S库
#include "WavData.h" // 把WAV格式的录音数据的bytes保存到WavData中
// Global Variables/objects
static const i2s_port_t i2s_num = I2S_NUM_1; // i2s port number,注意,如果是用内部DAC必须用I2S_NUM_0
unsigned const char* TheData;
uint32_t DataIdx=0; // index offset into "TheData" for current data t send to I2S
struct WavHeader_Struct
// RIFF Section
char RIFFSectionID[4]; // Letters "RIFF"
uint32_t Size; // Size of entire file less 8
char RiffFormat[4]; // Letters "WAVE"
// Format Section
char FormatSectionID[4]; // letters "fmt"
uint32_t FormatSize; // Size of format section less 8
uint16_t FormatID; // 1=uncompressed PCM
uint16_t NumChannels; // 1=mono,2=stereo
uint32_t SampleRate; // 44100, 16000, 8000 etc.
uint32_t ByteRate; // =SampleRate * Channels * (BitsPerSample/8)
uint16_t BlockAlign; // =Channels * (BitsPerSample/8)
uint16_t BitsPerSample; // 8,16,24 or 32
// Data Section
char DataSectionID[4]; // The letters "data"
uint32_t DataSize; // Size of the data that follows
WavHeader;
//------------------------------------------------------------------------------------------------------------------------
//------------------------------------------------------------------------------------------------------------------------
// I2S configuration structures
static const i2s_config_t i2s_config =
.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX),
.sample_rate = 44100, //设置采样率,但由于预先不知道WavData的录音数据的采样率,后面解码后会通过i2s_set_sample_rates修改
.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,
.communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB),
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, // high interrupt priority
.dma_buf_count = 8, // 8 buffers
.dma_buf_len = 1024, // 1K per buffer, so 8K of buffer space
.use_apll=0,
.tx_desc_auto_clear= true,
.fixed_mclk=-1
;
// These are the physical wiring connections to our I2S decoder board/chip from the esp32, there are other connections
// required for the chips mentioned at the top (but not to the ESP32), please visit the page mentioned at the top for
// further information regarding these other connections.
static const i2s_pin_config_t pin_config =
.bck_io_num = 27, // 时钟口,对应于MAX38357A的BCLK
.ws_io_num = 26, // 用于声道选择,对应于MAX38357A的LRC
.data_out_num = 25, // ESP32的音频输出口, 对应于MAX38357A的DIN
.data_in_num = I2S_PIN_NO_CHANGE // ESP32的音频输入接口,本例未用到
;
//------------------------------------------------------------------------------------------------------------------------
void setup()
Serial.begin(115200);
memcpy(&WavHeader,&WavData,44); // Copy the header part of the wav data into our structure
DumpWAVHeader(&WavHeader); // Dump the header data to serial, optional!
if(ValidWavData(&WavHeader))
i2s_driver_install(i2s_num, &i2s_config, 0, NULL); // ESP32 will allocated resources to run I2S
i2s_set_pin(i2s_num, &pin_config); // Tell it the pins you will be using
i2s_set_sample_rates(i2s_num, WavHeader.SampleRate); //set sample rate
TheData=WavData; // set to start of data
TheData+=44;
else // end code here
while(true);
void loop()
size_t BytesWritten; // Returned by the I2S write routine, we are not interested in it
// 这里可选择每次发32bit的数据,也就是4 bytes
i2s_write(i2s_num,TheData+DataIdx,4,&BytesWritten,portMAX_DELAY);
DataIdx+=4; // increase the data index to next two 16 bit values (4 bytes)
if(DataIdx>=WavHeader.DataSize) // If we gone past end of data reset back to beginning
DataIdx=0;
bool ValidWavData(WavHeader_Struct* Wav)
if(memcmp(Wav->RIFFSectionID,"RIFF",4)!=0)
Serial.print("Invlaid data - Not RIFF format");
return false;
if(memcmp(Wav->RiffFormat,"WAVE",4)!=0)
Serial.print("Invlaid data - Not Wave file");
return false;
if(memcmp(Wav->FormatSectionID,"fmt",3)!=0)
Serial.print("Invlaid data - No format section found");
return false;
if(memcmp(Wav->DataSectionID,"data",4)!=0)
Serial.print("Invlaid data - data section not found");
return false;
if(Wav->FormatID!=1)
Serial.print("Invlaid data - format Id must be 1");
return false;
if(Wav->FormatSize!=16)
Serial.print("Invlaid data - format section size must be 16.");
return false;
if((Wav->NumChannels!=1)&(Wav->NumChannels!=2))
Serial.print("Invlaid data - only mono or stereo permitted.");
return false;
if(Wav->SampleRate>48000)
Serial.print("Invlaid data - Sample rate cannot be greater than 48000");
return false;
if((Wav->BitsPerSample!=8)& (Wav->BitsPerSample!=16))
Serial.print("Invlaid data - Only 8 or 16 bits per sample permitted.");
return false;
return true;
void DumpWAVHeader(WavHeader_Struct* Wav)
if(memcmp(Wav->RIFFSectionID,"RIFF",4)!=0)
Serial.print("Not a RIFF format file - ");
PrintData(Wav->RIFFSectionID,4);
return;
if(memcmp(Wav->RiffFormat,"WAVE",4)!=0)
Serial.print("Not a WAVE file - ");
PrintData(Wav->RiffFormat,4);
return;
if(memcmp(Wav->FormatSectionID,"fmt",3)!=0)
Serial.print("fmt ID not present - ");
PrintData(Wav->FormatSectionID,3);
return;
if(memcmp(Wav->DataSectionID,"data",4)!=0)
Serial.print("data ID not present - ");
PrintData(Wav->DataSectionID,4);
return;
// All looks good, dump the data
Serial.print("Total size :");Serial.println(Wav->Size);
Serial.print("Format section size :");Serial.println(Wav->FormatSize);
Serial.print("Wave format :");Serial.println(Wav->FormatID);
Serial.print("Channels :");Serial.println(Wav->NumChannels);
Serial.print("Sample Rate :");Serial.println(Wav->SampleRate);
Serial.print("Byte Rate :");Serial.println(Wav->ByteRate);
Serial.print("Block Align :");Serial.println(Wav->BlockAlign);
Serial.print("Bits Per Sample :");Serial.println(Wav->BitsPerSample);
Serial.print("Data Size :");Serial.println(Wav->DataSize);
void PrintData(const char* Data,uint8_t NumBytes)
for(uint8_t i=0;i<NumBytes;i++)
Serial.print(Data[i]);
Serial.println();
对应的接线方式如图(可在代码的pin_config 中更改):
对于如何把后缀名为.wav的录音文件转化为WavData.h,可利用wsl的命令行:xxd -i xxx.wav xxx.h
2.使用第三方库ESP8266Audio
下载地址:https://github.com/earlephilhower/ESP8266Audio
本例基于:https://diyi0t.com/i2s-sound-tutorial-for-esp32/
先下载ESP8266Audio的库zip文件,并在Arduino中安装此第三方库
#include "AudioGeneratorAAC.h"
#include "AudioOutputI2S.h"
#include "AudioFileSourcePROGMEM.h"
#include "sampleaac.h"
AudioFileSourcePROGMEM *in;
AudioGeneratorAAC *aac;
AudioOutputI2S *out;
void setup()
Serial.begin(115200);
in = new AudioFileSourcePROGMEM(sampleaac, sizeof(sampleaac));
aac = new AudioGeneratorAAC();
out = new AudioOutputI2S();
out -> SetGain(0.5); //设置音量0~1
out -> SetPinout(27,33,32); //设置接到MAX98357A的引脚, GPIO27(串行时钟SCK)-->SCLK, GPIO33(字选择WS)-->LRC, GPIO32(串行数据SD)-->DIN
aac->begin(in, out);
void loop()
if (aac->isRunning())
aac->loop();
else
aac -> stop();
Serial.printf("Sound Generator\\n");
delay(1000);
3.用第三方库ESP8266Audio接收网络广播
本例基于ESP8266Audio库的StreamMP3FromHTTPToSPDIF
然后修改一下代码,绑定MAX98357A,添加把广播源换成国内的
#include <Arduino.h>
#ifdef ESP32
#include <WiFi.h>
#else
#include <ESP8266WiFi.h>
#endif
#include "AudioFileSourceICYStream.h"
#include "AudioFileSourceBuffer.h"
#include "AudioGeneratorMP3.h"
//#include "AudioOutputI2SNoDAC.h"
#include "AudioOutputI2S.h"
//
// Stream MP3 from HTTP to SPDIF
//
// To run, set your ESP8266 build to 160MHz, update the SSID info, and upload.
// Note:
// If using ESP8266 NodeMCU connect LED to RX pin and GND pin
// Enter your WiFi setup here:
#ifndef STASSID
#define STASSID "你的WiFi"
#define STAPSK "WiFi密码"
#endif
const char* ssid = STASSID;
const char* password = STAPSK;
// Examples URLs
//const char *URL="http://kvbstreams.dyndns.org:8000/wkvi-am";
// 换成了国内的广播源
const char *URL="http://lhttp.qingting.fm/live/4915/64k.mp3";
// Stream URL of Logitech Media Server, aka LMS, Version: 8.2.0 (August 2021)
// const char *URL="http://192.168.1.121:9000/stream.mp3";
AudioGeneratorMP3 *mp3;
AudioFileSourceICYStream *file;
AudioFileSourceBuffer *buff;
// Output device is SPDIF
AudioOutputI2S *out;
// Called when a metadata event occurs (i.e. an ID3 tag, an ICY block, etc.
void MDCallback(void *cbData, const char *type, bool isUnicode, const char *string)
const char *ptr = reinterpret_cast<const char *>(cbData);
(void) isUnicode; // Punt this ball for now
// Note that the type and string may be in PROGMEM, so copy them to RAM for printf
char s1[32], s2[64];
strncpy_P(s1, type, sizeof(s1));
s1[sizeof(s1)-1]=0;
strncpy_P(s2, string, sizeof(s2));
s2[sizeof(s2)-1]=0;
Serial.printf("METADATA(%s) '%s' = '%s'\\n", ptr, s1, s2);
Serial.flush();
// Called when there's a warning or error (like a buffer underflow or decode hiccup)
void StatusCallback(void *cbData, int code, const char *string)
const char *ptr = reinterpret_cast<const char *>(cbData);
// Note that the string may be in PROGMEM, so copy it to RAM for printf
char s1[64];
strncpy_P(s1, string, sizeof(s1));
s1[sizeof(s1)-1]=0;
Serial.printf("STATUS(%s) '%d' = '%s'\\n", ptr, code, s1);
Serial.flush();
void setup()
Serial.begin(115200);
delay(1000);
Serial.println("Connecting to WiFi");
WiFi.disconnect();
WiFi.softAPdisconnect(true);
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
// Try forever
while (WiFi.status() != WL_CONNECTED)
Serial.println("...Connecting to WiFi");
delay(1000);
Serial.println("Connected");
audioLogger = &Serial;
file = new AudioFileSourceICYStream(URL);
// Commented out for performance issues with high rate MP3 stream
//file->RegisterMetadataCB(MDCallback, (void*)"ICY");
buff = new AudioFileSourceBuffer(file, 4096); // Doubled form default 2048
// Commented out for performance issues with high rate MP3 stream
//buff->RegisterStatusCB(StatusCallback, (void*)"buffer");
// Set SPDIF output
out = new AudioOutputI2S();
out -> SetGain(0.2); //设置音量
out -> SetPinout(27,33,32); //设置接到MAX98357A的引脚, GPIO27(串行时钟SCK)-->SCLK, GPIO33(字选择WS)-->LRC, GPIO32(串行数据SD)-->DIN
mp3 = new AudioGeneratorMP3();
// Commented out for performance issues with high rate MP3 stream
//mp3->RegisterStatusCB(StatusCallback, (void*)"mp3");
mp3->begin(buff, out);
void loop()
// Commented out
//static int lastms = 0;
if (mp3->isRunning())
/* Commented out
if (millis()-lastms > 1000)
ESP32 DAC
ESP32有两个DAC通道,通道1链接GPIO25, 通道2链接GPIO26;
当DAC设置为 “built-in DAC mode”的时候,I2S可以通过DAC发送数据;
使用示例:
dac_output_enable(DAC_CHANNEL_1);
dac_output_voltage(DAC_CHANNEL_1, 200);
dac一共有8位,将3.3V电压按照255均分之后,就是dac的精度的最小单位;
API函数如下:
esp_err_t
dac_pad_get_io_num
(dac_channel_tchannel, gpio_num_t *gpio_num)
获取指定的DAC通道的GPIO口;
esp_err_t
dac_output_voltage
(dac_channel_tchannel, uint8_t dac_value)
设置DAC的输出电压;
esp_err_t
dac_output_enable
(dac_channel_tchannel)
DAC的输出使能;
esp_err_t
dac_output_disable
(dac_channel_tchannel)
DAC的输出失能;
esp_err_t
dac_i2s_enable
()
DAC 的I2S使能;
esp_err_t
dac_i2s_disable
()
DAC的I2S失能;
以上是关于ESP32音视频传输①用I2S通过内部DAC或MAX98357A播放音乐/录音数据及接受网络广播的主要内容,如果未能解决你的问题,请参考以下文章