32F417: MP3 encoding - Page 1
Forum section: electronics / general

author: peter-h
author_rank: Super Contributor
timestamp: April 06, 2022, 11:48:07 am
content: I am looking at implementing an mp3 encoder which, commencing at power-up, uses the 12 bit ADC and writes out an mp3 file at power-down. It is a sort of an automatic sound recorder. I posted the outline herehttps://www.eevblog.com/forum/projects/how-hard-is-analog-audio-to-mp3-auto-save-to-file-on-power-down/msg4100011/#msg4100011Looking at this in more detail, and having looked at the ST mp3 library, it seems to me that the best way to implement this is by driving the ADC with a timer and using DMA to store the values in RAM, and then running the encoder as an RTOS thread, every 100ms or so. This is because the sampling obviously has to be at regular intervals, and one doesn't want to run the encoder, and the file system, in a timer ISR.A quick look at the ST library (not had time to read all 200+MB of it) shows thiswhich suggests that is exactly what they intend i.e. the ADC data ("PCM", which sounds like amplitude-compressed ADC data) is provided as a block of RAM, and the output is similarly a block of RAM.So the encoder would run periodically (before the input buffer overflows; only a few k is needed if processing it every 100ms) and each time it is run, one would write the data to a file (FatFS; SPI FLASH).The FLASH writing speed is 30kbytes/sec which seems plenty fast enough.Does this make sense? If so, it is quite simple; nothing much "real time" especially if DMA is used to read the ADC.

author: ledtester
author_rank: Super Contributor
timestamp: April 06, 2022, 12:42:21 pm
content: You might be interested in this video which explains how to do double buffering with DMA:STM32 I2S ADC DMA & Double Buffering - Digital Audio Processing with STM32 #4 - Phil's Lab #55https://youtu.be/zlGSxZGwj-E

author: peter-h
author_rank: Super Contributor
timestamp: April 06, 2022, 12:56:52 pm
content: Yes; I have used double buffering in a waveform generator (timer -> DAC -> DMA) where one of the two buffers can be filled with new data while the DMA is feeding from the other one.There were some problems with that, with inadvertent synchronicity causing the new values to never appear if they were loaded at the same frequency as the DMA switching the buffersIn this case, probably the best way would be for the RTOS thread to flip the buffers manually, otherwise you have to read the buffer selector bit to see which one is being filled.

author: Sauli
author_rank: Contributor
timestamp: April 06, 2022, 02:18:15 pm
content: Having the RTOS thread swap the buffers may have its own problems. You will probably want the buffer size to be a multiple of the block size you feed to the encoder (576 samples). The double buffer DMA mode would nicely handle swapping the buffer, and you could trigger running of the encoding thread from the end-of-transfer interrupt.

author: peter-h
author_rank: Super Contributor
timestamp: April 06, 2022, 03:49:18 pm
content: Yes that's clever. Otherwise, the unavoidable one with an RTOS thread reading the not currently used buffer is that you need to check how close the DMA pointer is to the end of the buffer - to avoid a buffer swap while you are still processing the buffer which you thought you owned.If one uses an interrupt to kick off the RTOS thread, or equivalently one polls the buffer-swap indicator bit, then you know that you have a certain time to work in.I don't think it is trivial whatever you do because you also have to write the output data to a file, and each 512 byte sector takes 15ms, and you can't just not do that. It all has to work, in a pipeline.So one may as well loop on the buffer-swap indicator bit (it is 0 if buffer1 is used, 1 if buffer2 is used), with say a 1ms delay loop, and when you see it freshly swapped, then process that whole buffer to mp3 and write whatever the resulting data is (should beless than576 bytes, but can you guarantee that?) to the flash file system.

author: DavidAlfa
author_rank: Super Contributor
timestamp: April 06, 2022, 10:43:00 pm
content: PCM is nothing fancy. Plain ADC values, but usually int16 (signed ).If the adc can output signed data, great, else you'll have to process the buffer.From the pdf, couldn't be easier! This is almost it:https://pastecode.io/s/ao2mcb2jEdit: I forgot to mention, the library probably needs CRC module enabled! The decoder needs it or won't work.You might want also to playback it, check this project:https://github.com/deividAlfa/STM32F411-Black-pill-USB-WAV-MP3-player

author: peter-h
author_rank: Super Contributor
timestamp: April 07, 2022, 11:57:56 am
content: Interesting - thank you.I thought "PCM" was "classic PCM" e.g. mu-lawhttps://en.wikipedia.org/wiki/%CE%9C-law_algorithmBut that would be daft in the context of any "quality" audio.CRC enabled only for the decoder, not encoder?

author: DavidAlfa
author_rank: Super Contributor
timestamp: April 07, 2022, 12:18:02 pm
content: I said it'll probaby need it (The decoder does), as MP3 frames have crc, it makes sense that both use it.Maybe you referred to DPCM or ADPCM, those use work using relative steps to the existing sample.If pcm samples are 1000, 1010, 1020, those would do something like 1000, +10, +10, not exactly like that but explains the concept, thus reducing the required bandwidth, but at the expense of sound quality, as you can't change the sample as fast as the pcm does, ex. you can't go from -32.000 to +32.000 in a single step.The ADC outputs absolute values, so it's PCM.Also, consider expanding the samples or the playback volume will be low, ex. ((0...4095)-2048)*16 = -32752...32768

author: peter-h
author_rank: Super Contributor
timestamp: April 07, 2022, 12:57:26 pm
content: QuoteYes. This application is for speech and my guess is that this primitive ADC will be fine. It is actually about 10 good bits in reality; on any on-chip ADC I have ever used, the last 2 bits are random, no matter what PCB design practices one has doneI could use a better ADC if needed, or I could oversample the existing ADC, by sampling at say 440kHz (20x oversampling) and doing an FIR filter on that. One would do Timer -> ADC ->DMA at 440kHz into a 20-word circular buffer, and an ISR running at DMA pointer wrap (22kHz) would add up the 20 samples, divide by 20, and store the value as an int16_t into the 576 word buffer used by the encoder.The latency of that ISR is not important because it will always be adding up the latest 20. So long as it doesn't vary much.To get 16 bits from 12 bits with no gaps, one would need to add up 16 samples. And maybe inject some random noise, although that is what a 32F417 does anyway

author: DavidAlfa
author_rank: Super Contributor
timestamp: April 07, 2022, 02:36:39 pm
content: I also played music (ACDC, Guns'n Roses) using 8-bit pwm as dac, and it sounded surprisingly well, a lot better than what I expected.Project there:https://github.com/deividAlfa/STM32F411-Black-pill-USB-wav-playerWhat makes the difference is the sample rate!Sampling at 44100Hz, the ISR will be triggered at (44100/576)=76.5Hz, the stm32 should be fine with that.

author: peter-h
author_rank: Super Contributor
timestamp: April 07, 2022, 08:17:23 pm
content: We may not be talking of the same interrupt rate.Let's go for x16 ADC oversampling, to get 16 bits out of the 12 bit ADCRun the ADC at 44x16=704kHz. Timer -> ADC -> DMA, into a circular buffer, 16 words long.ISR when the buffer wraps around; 22.7us. The ISR will add up the 16 uint8_t values and store the sum in the 576-word buffer, increment a counter. Should be possible in well under 22us. This code (SPC=128), which does a whole lot more, runs in 20usCode:for (int i=0;i<SPC;i++){dac1val = 2048+((sintab*amplitude0)/262144L);dac2val = 2048+((sintab*amplitude1)/262144L);wavebuf0=dac1val|(dac2val<<16);wavebuf1=wavebuf0;}When the counter reaches 576, reset it to zero and set a flag for the encoder RTOS thread to pick up the 576 values.Better to have two 576-byte buffers, so the encoder can process one while the above stuff is filling the other.To summarise, because I am oversampling the 12 bit ADC 16x, I have an ISR at every 22.7us.I don't need 44kHz but with 22kHz I would need a steep filter on the input; around 7-8kHz. I have a 3rd order Sallen-Key on a similar project so know how to do that bit. But would that be steep enough?

author: DavidAlfa
author_rank: Super Contributor
timestamp: April 07, 2022, 09:06:52 pm
content: Isn't oversampling a little overkill just for storing voice?Or are you going record a Pink Floyd album?I'd try the simple approach first. You might find it's perfectly fine and save a lot of work and processing power!

author: peter-h
author_rank: Super Contributor
timestamp: April 08, 2022, 02:49:47 pm
content: I had a dig around to see how other people do this and e.g. thishttps://www.st.com/resource/en/application_note/an5012-analogtodigital-audio-conversion-example-using-stm32l4-series-microcontroller-peripherals-stmicroelectronics.pdfjust uses the 12 bit ADC directly, with no averaging.If the input is scaled appropriately, 12 bits ought to be plenty good enough - it is 1/4096 which is 0.02% resolution. Anything below around 0.1% is inaudible, although obviously plenty will disagreeAnd scaling may actually be an issue, because AGC brings a load of other problems.

author: SiliconWizard
author_rank: Super Contributor
timestamp: April 08, 2022, 05:31:31 pm
content: 12-bit is plenty for just voice recording. (For music, that's pretty lo-fi.)Did you consider something else than MP3 (such as ADPCM) as I suggested earlier?

author: peter-h
author_rank: Super Contributor
timestamp: April 08, 2022, 06:28:34 pm
content: I want mp3 purely for dumb user acceptance

author: peter-h
author_rank: Super Contributor
timestamp: April 10, 2022, 06:48:58 pm
content: I've found that I also need to somehow encode date/time.IIRC, this is done with a "sidecar" (a separate file). It's like encoding GPS position on videos.

author: SiliconWizard
author_rank: Super Contributor
timestamp: April 10, 2022, 07:00:33 pm
content: Is this a surveillance device?

author: peter-h
author_rank: Super Contributor
timestamp: April 11, 2022, 10:34:05 am
content: NoI can't disclose the application, sorry.One could record the date/time by setting the file datestamp to the time at thestartof the recording. That is easy.

author: ledtester
author_rank: Super Contributor
timestamp: April 11, 2022, 10:41:55 am
content: Quote from: peter-h on April 11, 2022, 10:34:05 amHow about storing it in an ID3v2 tag?https://en.wikipedia.org/wiki/ID3#ID3v2

author: peter-h
author_rank: Super Contributor
timestamp: April 11, 2022, 11:05:05 am
content: Excellent point. I totally forgot mp3 files store the artist name etc. Any modern mp3 encoder must support these tags.What I was getting at further back was acontinuousstorage of time. This is done in the video world and there are various ways. One of the reasons one needs it is that it enables the video camera to be started and stopped as needed, but you can still tell afterwards which clip was shot when. Typically this issue arises where the video is recorded by one device (a camera without a mike, which is start/stopped) and the sound by another device (which runs continuously).

author: newbrain
author_rank: Super Contributor
timestamp: April 11, 2022, 01:41:21 pm
content: Quote from: peter-h on April 07, 2022, 08:17:23 pmJust remember that 16 × oversampling will give youtwoadditional 'good' bits, not four - though the codes will be 16 bit.This is under the general assumption that noise accumulates in power, while signal accumulates in voltage, so a 4 × summation will give you a fourfold increase in voltage, but a twofold increase in noise, i.e. a one bit improvement in S/n.In this particular case it's already probably overkill (we lived quite happily with µ or A law companded 12 bit), still it's good to be aware of this mechanism.

author: peter-h
author_rank: Super Contributor
timestamp: April 11, 2022, 05:51:02 pm
content: Yes, sorry; assuming random noise, and a perfect ADC, the resolution increases with the square root of the samples taken.It just seems simple to take 16 samples and add them up and that gives you "16 bits" of "stuff".QuoteThat I don't understand.QuoteI am sure it is and 12 bits (10 real bits) should be enough. I am just concerned about the dynamic range. I can't really do AGC on this because it is mostly speech, some beeps, and a fair bit of background noise which any AGC would just wind right up.Also I find that one should work on a project continuously from start to finish and do the very best one can right from the start, because if you have to get back to something after a few months, it can be very hard.

author: newbrain
author_rank: Super Contributor
timestamp: April 11, 2022, 07:51:51 pm
content: Quote from: peter-h on April 11, 2022, 05:51:02 pmSorry, I was possibly too terse, if you are not familiar with the topic - not that I pretend to be an expert.When you sum two noises together, as they are random, the resulting signal will have double thepowerof the original signals, i.e. the average amplitude increased by Sqrt(2).OTOH, when two you sum the signal part, very correlated voltages, the result is that the average amplitude is twice the original one.This is, in fact, an old analogue trick; you could sometimes find paralleled transistors in (pre)amplifiers: the increase in signal was larger than the one in noise.So, to increase the real resolution by 1 bit, (i.e. double it), you need 4× oversampling, as you gain only half bit (Sqrt(2)) of S/n every time you double the samples.In general, fornbits, 2^2n (=4^n) oversampling is needed.See, for example, Wikipedia page onOversampling, ormanyvendors'applicationnotes(each word a different link, the Microchip one goes in some theoretical detail), see also the relevant chapter in Lyons's "Understanding Digital Signal Processing" (on Amazon, ormaybesome pdf might be found floating in the net...).

author: peter-h
author_rank: Super Contributor
timestamp: April 21, 2022, 08:08:23 am
content: I am trying to psyche myself up to going through the 200MB+ ST Cube mp3 library :smile:There has to be some procedure... but one doesn't want to bloat one's project with a load of other code. In some contexts, code does not actually get included into the project. Obviously #defines can be used (if you can find them among the hundreds of #defines) or somehow a library might be loaded only if actual functions get referenced (not sure about that one).The actual mp3 encoder should be just one file .c and .h.

author: DavidAlfa
author_rank: Super Contributor
timestamp: April 21, 2022, 05:52:10 pm
content: How can you say it's so complicated when I already posted an example?The encoder is just one header file and a pre-compiled library.You don't get the source for it, it's closed-source.

author: SiliconWizard
author_rank: Super Contributor
timestamp: April 21, 2022, 07:01:43 pm
content: As an alternative, you could consider a dedicated chip like the VS1063:https://www.vlsi.fi/en/products/vs1063.htmland just interface it with your MCU.Not the cheapest alternative of course, but would get you both encoding and decoding, also support other formats than MP3, with minimal CPU intervention.