https://github.com/espressif/esp8266_mp3_decoder

I2S MP3 webradio streaming example//I2S MP3 webradio流例子

This is an example of how to use the I2S module inside the ESP8266 to output
sound. In this case, it is used to output decoded MP3 data (actually, more
accurately: MPEG2 layer III data): the code described here basically is a
webradio streamer which can connect to an Icecast server, take the MP3 data
the server sends out, decode it and output it over the I2S bus to a DAC. The
MP3 decoder has been tested for bitrates up to 320KBit/s and sample
rates of up to 48KHz.

这是一个使用ESP8266的I2S模组输出声音的例子。在这个案子中,他解码MP3数据输出
数据(其实,更准确是:MPEG2 layer III 数据)。这里的代码为一个基本的webradio
连接到一个Icecast服务器,下载MP3数据,解码并且通过I2S总线输出到DAC。MP3解码器
用320KBit/s的波特和48KHz的采样率数据进行测试。

## Configuration options, building//配置控制,编译

All high-level options can be configured in mp3/user/playerconfig.h. Edit
that file to set up your access point and a webradio stream or other source of
MP3 data served over HTTP.

所有高级的控制可以通过配置mp3/user/playerconfig.h来实现。编译文件设置你的
访问点和webradio流或者其它的机遇HTTP的MP3数据服务。

To build the code, try running make.sh in the mp3/ directory. Alternatively,
the way to use ‘make’ to build this code is:
make COMPILE=gcc BOOT=none APP=0 SPI_SPEED=40 SPI_MODE=QIO SPI_SIZE=1024

编译代码,通过运行mp3/make.sh文件来实现。或者通过运行‘make’来实现:
make COMPILE=gcc BOOT=none APP=0 SPI_SPEED=40 SPI_MODE=QIO SPI_SIZE=1024

The resulting binaries will be in the bin/ folder. Please disregard the message
that pops up at the end of the make process: the addresses it mentions are
wrong. The correct addresses to load the resulting files are:

编译的结果将出现在 bin 文件夹下。请忽略弹出的消息,将make运行完成:它提到的
地址是错误的。正确的装载文件地址在如下面显示的:

bin/eagle.flash.bin     - 0x00000
bin/eagle.irom0text.bin - 0xA0000

Needed hardware //需要的硬件

If you want to have nice, high-quality buffered audio output, you will need to
connect two ICs to your ESP: a 128KByte SPI RAM and an I2S codec. Both ICs are
optional, but you will get stuttering and low-quality sound if you leave them
off.

如果你需要一个好的,高质量的音频输出,你将需要连接2个ICs到你的ESP:一个128KByte
的SPI RAM和一个I2S codec。如果你需要一个断断续续的和低质量的声音的话,你可以
不需要这两个芯片。

The SPI RAM is a Microchip 23LC1024 part and is used to buffer the incoming
MP3 data. This guards against latency isuues that are present in all but the
most quiet networks and closest connections. It is connected to the same
bus as the SPI flash:

SPI RAM采用Microchip的23LC1024芯片,用来缓存接收MP3数据。
This guards against latency isuues that are present in all but the most quiet networks and closest connections.它连接到SPI Flash相同的总线。

ESP pin   - 23LC1024 pin
------------------------
GPIO0     - /CS (1)
SD_D0     - SO/SI1 (2)
SD_D3     - SIO2 (3) *
gnd       - gnd (4)
SD_D1     - SI/SIO0 (5)
SD_CLK    - SCK (6)
SD_D2     - /HOLD/SIO3 (7) *
3.3V      - VCC (8)

*=optional, may also be connected to Vcc on 23LC1024 side.//也许可以将这些引脚连接到Vcc在23LC1024侧。

One way to make these connections is to take the SSOIC version of the 23LC1024,
bend up pin 1 (/CS) and piggyback it on the SPI flash chip that already is on the
ESP module. Solder all the pins to the same pins on the SPI flash chip except
for the bent /CS pin; use a wire to connect that to GPIO0.

As Github user milkpirate correctly remarked, GPIO0 is also used to enter
programming mode on the ESP8266 and will interfere with correct flashing if kept
low. The correct way of flashing the module is to make GPIO0 low, reset the
ESP8266 to enter programming mode, then make GPIO0 high again. Tools like
esptool.py generally follow this method to automatically enter programming
mode; if you manually enter programming mode you may have to adjust your
methodology.

For the I2S codec, pick whatever chip or board works for you; this code was
written using a ES9023 chip, but other I2S boards and chips will probably
work as well. The connections to make here are:

对于I2S codec, 不论你选择芯片或板子;这个代码使用的是ES9023芯片进行编写,但是其它I2S板子和芯片应该也可以工作的很好。引脚连接参照下面的方法:

ESP pin   - I2S signal
----------------------
GPIO2/TX1   - LRCK
GPIO3/RX0   - DATA
GPIO15      - BCLK

Also, don’t forget to hook up any supply voltages and grounds needed.

当然,不要忘记连接所有需要的电源和地。

Running without the SPI RAM part // 没有SPI RAM元件运行

To not use the SPI RAM chip, please edit mp3/user/playerconfig.h and
define FAKE_SPI_BUFF. This will use a much smaller buffer in the main
memory of the ESP8266. Because the buffer is much smaller, the code
will be very sensitive to network latency; also, clock synchronization
with live streaming stations will not work. Expect the sound to cut
out a fair amount of times unless you have a quiet network and connect
to a server very close to you.

不使用SIP RAM芯片,请编辑 mp3/user/playerconfig.h,定义FAKE_SPI_BUF。这将在ESP8266的主内内存中使用非常小的缓冲器。因为缓冲器非常小,代码对网络延迟将非常敏感。同样,直播站的时钟同步将不工作。期望声音挖出大量的时间,除非你有一个清静的网络,并且连接的服务器距离你非常近。

Running without the I2S DAC // 没有I2S DAC 运行

To not use an I2S DAC chip, please edit mp3/user/playerconfig.h and
define PWM_HACK. This uses some code to abuse the I2S module as a
5-bit PWM generator. You can now connect an amplifier to the I2S
data pin (GPIO3/RX0) of the ESP module. Connecting a speaker
directly may also work but is not advised: the GPIOs of the ESP
are not meant to drive inductive loads directly.

不使用一个I2S DAC芯片,请编辑 mp3/user/palyerconfig.h,定义PWM_HACK。这是用一些代码滥用I2S模组作为一个5-bit PWM产生器。有可以连接一个放大器到ESP模块的I2S数据输入引脚(GPIO3/RX0)。直接连接一个喇叭也是可以工作了。但是,这是不被推荐的:ESP的GPIO的意思不是说可以直接驱动感性负载。

Sound quality // 声音质量

In the default configuration, this code will output 16-bit mono audio.
Stereo audio is possible but hasn’t been implemented yet: a stereo
synth is available but has not been modified for ESP8266 use yet. In
PWM mode, the output is a dithered 5-bit PWM output. Furthermore, the
ESP can decode a pretty wide range of bitrates: 96KBit to 320KBit
MP3s have been tested and seem to work fine. Sound quality in general
is not bad, but the scaling and clock adaption algorithms can use
some improvement if the decoder needs to output real high-quality
sound: Patches are welcome.

在默认的配置里,这个代码输出16-bit的单声道声音。立体声是可能的,但是还没有实现:立体声合成是有效的,但是还没有修改给ESP8266是用。在PWM模式下,输出是一个紧张的5-bit PWM输出。此外,ESP能够解码一个非常宽范围的波特率:96KBit - 320KBit的MP3测试,看其来工作很好。产生的声音质量也不坏,但是缩放和时钟自适应算法可以使用一些改进,如果解码器需要输出真实高质量的声音:欢迎创建分支。

About the FreeRTOS SDK used // 关于使用的 FreeRTOS SDK

The MP3 example is a very memory-sensitive piece of code: the MP3 decoder
uses almost all the RAM and the needed buffers for input and output
buffering take up the rest: when using no external SPI RAM, only a few
bytes of memory are left. The SDK libs that come with this example are
libraries that have been optimized for memory usage and are known to work.

MP3代码是一个内存非常敏感的一块代码:MP3解码器使用几乎所有的RAM,输入和输出需要的缓冲器使用了剩余:当不使用外部SPI RAM,仅剩余几个字节的内存。伴随这个例子的SDK库是一个已经优化了内存使用的库,这是众所周知的工作。

Technical details on this implementation // 这个实现的技术详情

The biggest part of this code consists of a modified version of libmad,
a fixed-point mp3 decoder. The specific version we use here has already
been modified by NXP to use less memory
(source: www.nxp.com/documents/application_note/AN10583.pdf) and has been
massaged by Espressif to store as much constants in flash as possible in
order to decrease RAM use even more. The MP3 decoder is fed from a FIFO
realized in the external 23LC1024 SPI RAM. This RAM is filled from a network
socket in a separate thread.

On the output side, the MP3 samples are fed into the I2S subsystem using
DMA. The I2S DMA basically consists of a circular buffer consisting of
a number of smaller buffers. As soon as the DMA is done emptying one of
the smaller buffers into the I2S subsystem, it will fire an interrupt. This
interrupt will put the buffer address in a queue.

在输出侧,MP3 采样数据使用DMA传送给了I2S子系统。I2S DMA基于大量的小缓冲区组成的环形缓冲区。DMA尽快排空到I2S子系统的缓冲区,它将产生一个中断。这个中断将放置buffer地址到一个序列。

When the MP3 decoder has a bunch of samples ready, it will pop a buffer
off this queue and put the samples in it until it is full, then take the
next buffer etc. The MP3 decoder generally is faster than the I2S output,
so at a certain moment there will be no free buffers left. The queue
system of FreeRTOS will suspend the mp3 decoding task when that
happens, allowing the ESP8266 to attend to other tasks.

当MP3解码器有一个采样串准备好,它将pop

While the ESP8266 is able to run at 160MHz, we’re leaving it at its
default speed of 80MHz here: it seems that at that speed the ESP8266
is perfectly capable of decoding even 320KBit MP3 data.

事实上ESP8266能够运行在160MHz,在这里我们保持它在默认80MHz速度:看起来ESP8266的速度甚至有能力解码320KBit的MP3数据。

esp8266 语音播放 //Priorities of the reader and the decoder thread. Higher = higher prio. #define PRIO_READER 11 #define PRIO_MAD 1 //The mp3 read buffer size. 2106 bytes should be enough for up to 48KHz mp3s according to the sox sources. Used by libmad. #define READBUFSZ (2106) static char readBuf[READBUFSZ]; static long bufUnderrunCt; //Reformat the 16-bit mono sample to a format we can send to I2S. static int sampToI2s(short s) { //We can send a 32-bit sample to the I2S subsystem and the DAC will neatly split it up in 2 //16-bit analog values, one for left and one for right. //Duplicate 16-bit sample to both the L and R channel int samp=s; samp=(samp)&0xffff; samp=(samp<65535) samp=65535; if (samp>11]; err=(samp&0x7ff); //Save rounding error. return samp; } //2nd order delta-sigma DAC //See http://www.beis.de/Elektronik/DeltaSigma/DeltaSigma.html for a nice explanation static int sampToI2sDeltaSigma(short s) { int x; int val=0; int w; static int i1v=0, i2v=0; static int outReg=0; for (x=0; x<32; x++) { val<0) w-=32767; else w+=32767; //Difference 1 w+=i1v; i1v=w; //Integrator 1 if (outReg>0) w-=32767; else w+=32767; //Difference 2 w+=i2v; i2v=w; //Integrator 2 outReg=w; //register if (w>0) val|=1; //comparator } return val; } //Calculate the number of samples that we add or delete. Added samples means a slightly lower //playback rate, deleted samples means we increase playout speed a bit. This returns an //8.24 fixed-point number int recalcAddDelSamp(int oldVal) { int ret; long prevUdr=0; static int cnt; int i; static int minFifoFill=0; i=spiRamFifoFill(); if (i<minFifoFill) minFifoFill=i; //Do the rest of the calculations plusminus every 100mS (assuming a sample rate of 44KHz) cnt++; if (cnt<1500) return oldVal; cnt=0; if (spiRamFifoLen()<10*1024) { //The FIFO is very small. We can't do calculations on how much it's filled on average, so another //algorithm is called for. int tgt=1600; //we want an average of this amount of bytes as the average minimum buffer fill //Calculate underruns this cycle int udr=spiRamGetUnderrunCt()-prevUdr; //If we have underruns, the minimum buffer fill has been lower than 0. if (udr!=0) minFifoFill=-1; //If we're below our target decrease playback speed, and vice-versa. ret=oldVal+((minFifoFill-tgt)*ADD_DEL_BUFFPERSAMP_NOSPIRAM); prevUdr+=udr; minFifoFill=9999; } else { //We have a larger FIFO; we can adjust according to the FIFO fill rate. int tgt=spiRamFifoLen()/2; ret=(spiRamFifoFill()-tgt)*ADD_DEL_BUFFPERSAMP; } return ret; } //This routine is called by the NXP modifications of libmad. It passes us (for the mono synth) //32 16-bit samples. void render_sample_block(short *short_sample_buff, int no_samples) { //Signed 16.16 fixed point number: the amount of samples we need to add or delete //in every 32-sample static int sampAddDel=0; //Remainder of sampAddDel cumulatives static int sampErr=0; int i; int samp; #ifdef ADD_DEL_SAMPLES sampAddDel=recalcAddDelSamp(sampAddDel); #endif sampErr+=sampAddDel; for (i=0; i(1<<24)) { sampErr-=(1<<24); //...and don't output an i2s sample } else if (sampErr<-(1<<24)) { sampErr+=(1<bufend-stream->next_frame; memmove(readBuf, stream->next_frame, rem); while (rem<sizeof(readBuf)) { n=(sizeof(readBuf)-rem); //Calculate amount of bytes we need to fill buffer. i=spiRamFifoFill(); if (i<n) n=i; //If the fifo can give us less, only take that amount if (n==0) { //Can't take anything? //Wait until there is enough data in the buffer. This only happens when the data feed //rate is too low, and shouldn't normally be needed! // printf("Buf uflow, need %d bytes.\n", sizeof(readBuf)-rem); bufUnderrunCt++; //We both silence the output as well as wait a while by pushing silent samples into the i2s system. //This waits for about 200mS for (n=0; nerror, mad_stream_errorstr(stream)); return MAD_FLOW_CONTINUE; } //This is the main mp3 decoding task. It will grab data from the input buffer FIFO in the SPI ram and //output it to the I2S port. void ICACHE_FLASH_ATTR tskmad(void *pvParameters) { int r; struct mad_stream *stream; struct mad_frame *frame; struct mad_synth *synth; //Allocate structs needed for mp3 decoding stream=malloc(sizeof(struct mad_stream)); frame=malloc(sizeof(struct mad_frame)); synth=malloc(sizeof(struct mad_synth)); if (stream==NULL) { printf("MAD: malloc(stream) failed\n"); return; } if (synth==NULL) { printf("MAD: malloc(synth) failed\n"); return; } if (frame==NULL) { printf("MAD: malloc(frame) failed\n"); return; } //Initialize I2S i2sInit(); bufUnderrunCt=0; printf("MAD: Decoder start.\n"); //Initialize mp3 parts mad_stream_init(stream); mad_frame_init(frame); mad_synth_init(synth); while(1) { input(stream); //calls mad_stream_buffer internally while(1) { r=mad_frame_decode(frame, stream); if (r==-1) { if (!MAD_RECOVERABLE(stream->error)) { //We're most likely out of buffer and need to call input() again break; } error(NULL, stream, frame); continue; } mad_synth_frame(synth, frame); } } } int getIpForHost(const char *host, struct sockaddr_in *ip) { struct hostent *he; struct in_addr **addr_list; he=gethostbyname(host); if (he==NULL) return 0; addr_list=(struct in_addr **)he->h_addr_list; if (addr_list[0]==NULL) return 0; ip->sin_family=AF_INET; memcpy(&ip->sin_addr, addr_list[0], sizeof(ip->sin_addr)); return 1; } //Open a connection to a webserver and request an URL. Yes, this possibly is one of the worst ways to do this, //but RAM is at a premium here, and this works for most of the cases. int ICACHE_FLASH_ATTR openConn(const char *streamHost, const char *streamPath) { int n, i; while(1) { struct sockaddr_in remote_ip; bzero(&remote_ip, sizeof(struct sockaddr_in)); if (!getIpForHost(streamHost, &remote_ip)) { vTaskDelay(1000/portTICK_RATE_MS); continue; } int sock=socket(PF_INET, SOCK_STREAM, 0); if (sock==-1) { continue; } remote_ip.sin_port = htons(streamPort); printf("Connecting to server %s...\n", ipaddr_ntoa((const ip_addr_t*)&remote_ip.sin_addr.s_addr)); if (connect(sock, (struct sockaddr *)(&remote_ip), sizeof(struct sockaddr))!=00) { close(sock); printf("Conn err.\n"); vTaskDelay(1000/portTICK_RATE_MS); continue; } //Cobble together HTTP request write(sock, "GET ", 4); write(sock, streamPath, strlen(streamPath)); write(sock, " HTTP/1.0\r\nHost: ", 17); write(sock, streamHost, strlen(streamHost)); write(sock, "\r\n\r\n", 4); //We ignore the headers that the server sends back... it's pretty dirty in general to do that, //but it works here because the MP3 decoder skips it because it isn't valid MP3 data. return sock; } } //Reader task. This will try to read data from a TCP socket into the SPI fifo buffer. void ICACHE_FLASH_ATTR tskreader(void *pvParameters) { int madRunning=0; char wbuf[64]; int n, l, inBuf; int t; int fd; int c=0; while(1) { fd=openConn(streamHost, streamPath); printf("Reading into SPI RAM FIFO...\n"); do { n=read(fd, wbuf, sizeof(wbuf)); if (n>0) spiRamFifoWrite(wbuf, n); c+=n; if ((!madRunning) && (spiRamFifoFree()0); close(fd); printf("Connection closed.\n"); } } //Simple task to connect to an access point, initialize i2s and fire up the reader task. void ICACHE_FLASH_ATTR tskconnect(void *pvParameters) { //Wait a few secs for the stack to settle down vTaskDelay(3000/portTICK_RATE_MS); //Go to station mode wifi_station_disconnect(); if (wifi_get_opmode() != STATION_MODE) { wifi_set_opmode(STATION_MODE); } //Connect to the defined access point. struct station_config *config=malloc(sizeof(struct station_config)); memset(config, 0x00, sizeof(struct station_config)); sprintf(config->ssid, AP_NAME); sprintf(config->password, AP_PASS); wifi_station_set_config(config); wifi_station_connect(); free(config); //Fire up the reader task. The reader task will fire up the MP3 decoder as soon //as it has read enough MP3 data. if (xTaskCreate(tskreader, "tskreader", 230, NULL, PRIO_READER, NULL)!=pdPASS) printf("Error creating reader task!\n"); //We're done. Delete this task. vTaskDelete(NULL); } //We need this to tell the OS we're running at a higher clock frequency. extern void os_update_cpu_frequency(int mhz); void ICACHE_FLASH_ATTR user_init(void) { //Tell hardware to run at 160MHz instead of 80MHz //This actually is not needed in normal situations... the hardware is quick enough to do //MP3 decoding at 80MHz. It, however, seems to help with receiving data over long and/or unstable //links, so you may want to turn it on. Also, the delta-sigma code seems to need a bit more speed //than the other solutions to keep up with the output samples, so it's also enabled there. #if defined(DELTA_SIGMA_HACK) SET_PERI_REG_MASK(0x3ff00014, BIT(0)); os_update_cpu_frequency(160); #endif //Set the UART to 115200 baud UART_SetBaudrate(0, 115200); //Initialize the SPI RAM chip communications and see if it actually retains some bytes. If it //doesn't, warn user. if (!spiRamFifoInit()) { printf("\n\nSPI RAM chip fail!\n"); while(1); } printf("\n\nHardware initialized. Waiting for network.\n"); xTaskCreate(tskconnect, "tskconnect", 200, NULL, 3, NULL); }
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值