
Re: I2S: Anyone got it running?
Audio over ethernet (not TCP/IP including UDP but plain Ethernet L2) would be also a good solution:
Links:
http://electronics.stackexchange.com/qu ... r-ethernet
http://www.tfwm.com/newsletter_82508lesson
http://us.focusrite.com/ethernet-audio-interfaces
http://www.digigram.com/products/produc ... _key=11100
http://www.ethersound.com/
...
IMHO it will be the next big thing in digital audio interfaces.
Links:
http://electronics.stackexchange.com/qu ... r-ethernet
http://www.tfwm.com/newsletter_82508lesson
http://us.focusrite.com/ethernet-audio-interfaces
http://www.digigram.com/products/produc ... _key=11100
http://www.ethersound.com/
...
IMHO it will be the next big thing in digital audio interfaces.
Re: I2S: Anyone got it running?
Well, I've managed to get an interrupt based ALSA driver working on the pi. It's still not great at the moment. It seems to work, but appears to 'warble' (for want of a better description) if the cpu usage is non zero. It's odd, it sort of reminds me of cassette wow and flutter!
Anyway, progressing. I think I'm in a good position to start using DMA.
Anyway, progressing. I think I'm in a good position to start using DMA.
-
- Posts: 19
- Joined: Sat Jan 05, 2013 8:11 am
Re: I2S: Anyone got it running?
Nice work Philpoole. I have been following with interest, but have little useful to offer yet.philpoole wrote:Well, I've managed to get an interrupt based ALSA driver working on the pi. It's still not great at the moment. It seems to work, but appears to 'warble' (for want of a better description) if the cpu usage is non zero. It's odd, it sort of reminds me of cassette wow and flutter!
Anyway, progressing. I think I'm in a good position to start using DMA.
I am eager to see this implemented though as I would love to make my own extension board with high quality audio DACs terminating directly to the processor. Keep up the hard work!

Re: I2S: Anyone got it running?
Sounds like very bad jitter. Would it be somehow related to the clock generation on RPi? Using DAC/CODEC in master mode with the clock generation on converter board would propably solve that warbling.philpoole wrote:Well, I've managed to get an interrupt based ALSA driver working on the pi. It's still not great at the moment. It seems to work, but appears to 'warble' (for want of a better description) if the cpu usage is non zero. It's odd, it sort of reminds me of cassette wow and flutter!
Anyway, progressing. I think I'm in a good position to start using DMA.
Re: I2S: Anyone got it running?
No, it's not clock jitter. If it was, then there'd be a similar problem with the userspace program I used.
It's to do with copy being called too much, and basically filling up all the spare buffers I have.
Essentially a buffer overflow. Even though the 'buffer' is essentially 50% bigger than ALSA is told.
The limited ALSA documentation doesn't help much either. What should the copy operator return when it can't complete? Should the pointer operator refer to the frame position in a buffer? If so should it handle wrapping? Or should it be the total count since pcm_open()? None of this is clear, and I now have to refer to the ALSA source code to understand what's going on.
But hey! It's open source, so I can see the code.
It's to do with copy being called too much, and basically filling up all the spare buffers I have.
Essentially a buffer overflow. Even though the 'buffer' is essentially 50% bigger than ALSA is told.
The limited ALSA documentation doesn't help much either. What should the copy operator return when it can't complete? Should the pointer operator refer to the frame position in a buffer? If so should it handle wrapping? Or should it be the total count since pcm_open()? None of this is clear, and I now have to refer to the ALSA source code to understand what's going on.
But hey! It's open source, so I can see the code.
Re: I2S: Anyone got it running?
I think the most difficult part in writing an ALSA driver is in keeping the playback position. So you need to call the snd_pcm_period_elapsed() when you've consumed lets say 32 (mono) or 16 (stereo) 24-bit samples (if using 24-bit words packed to 32-bit FIFO). I would set the FIFO threshold so that the IRQ is triggered when FIFO is half or less empty. At that point I would write exactly 32 samples to buffer. Then the period_bytes_min and _max should be 32*4, and there could be min 1, max. 32 periods (to start with some value). So the buffer size would be 32*4*32 (ok I haven't though channels yet, I assume you set SNDRV_PCM_INFO_INTERLEAVED and have two channel which should be taken account somehow). You've allocated that many bytes, and report in period callback the position inside the buffer in frames (there's the bytes_to_frames function which can be used as helper). So getting it righ requires giving correct hardware descriptor and using correct units (bytes/frames) to report current position and to deduct when to call snd_period_elapsed().
ALSA mid layer shouldn't call the copy function indefinitely if you call snd_pcm_period_elapsed() from IRQ every time the TX FIFO empty has consumed the 32 samples, and the pointer function returns correct position (current hardware position) inside buffer. I guess it marks the period inside buffer which cannot be written to yet but only after the snd_pcm_period_elapsed() call is made from the driver (at least then it queries the hardware pointer again). Now, I assume that the copy callback should write the data to the beginning of the (ring) buffer if it becomes full (wraparound or what the correct term was), that's what ALSA waits as it calculates how much there are free space available. A nice drawing might help to understand how the ringbuffer filling and position / period reporting works.
http://en.wikipedia.org/wiki/Circular_buffer
ALSA mid layer shouldn't call the copy function indefinitely if you call snd_pcm_period_elapsed() from IRQ every time the TX FIFO empty has consumed the 32 samples, and the pointer function returns correct position (current hardware position) inside buffer. I guess it marks the period inside buffer which cannot be written to yet but only after the snd_pcm_period_elapsed() call is made from the driver (at least then it queries the hardware pointer again). Now, I assume that the copy callback should write the data to the beginning of the (ring) buffer if it becomes full (wraparound or what the correct term was), that's what ALSA waits as it calculates how much there are free space available. A nice drawing might help to understand how the ringbuffer filling and position / period reporting works.
http://en.wikipedia.org/wiki/Circular_buffer
Re: I2S: Anyone got it running?
Ok. The buffer I knocked up was quick and dirty in order to try and get something running.
Basically, I had a dummy ALSA driver running (based on the example file dummy.c), and have changed it to work with hardware.
Working with what I've got, the copy operator is called with a count of 1024 (for me, that's 4k - stereo, 16 bits).
I have told ALSA that the buffer size is 64k in size (I'm assuming that ALSA is assuming that is bytes, not frames, I might test that theory). I parallel to that, I have an array of 128 pointers, each pointing to a pre allocated buffer (allocated with get_zeroed_page(GFP_KERNEL)).
I know it's overkill, but at this stage, I'm just trying to get it to work.
I have a flag per buffer, saying if its free or in use.
So, copy operator is called. I copy from user to the next free buffer (I have an incrementor to ensure they are allocated in order, and it will wrap to zero if end of buffers found) and set the relevant flag to mark it as filled (for the ISR to use).
In parallel to this, my ISR is happilly bombing along injecting 32 words at a time (two samples per 32 bit word). It keeps check of the total injected and detects when it should move onto the next buffer.
When the ISR moves on to the next buffer, it labels the previous one as free, and calls snd_pcm_period_elapsed().
So, I call elapsed() when the amount passed in through the copy has been processed (1024 samples). Seems reasonable to me.
Anyway, after elapsed() has been called, ALSA calls the pointer operator, where I return the number of frames processed by the ISR so far, divisible by 1024 (i.e. not counting any half processed chunks - possibly something to investigate).
All seems sensible behaviour, but obviously ALSA's not very happy about it.
Next, I've noticed that if I return -1 from copy if there's an overflow then mpc stops, and snd_pcm_close() is called.
So, I'm in the process of trying to work out what ALSA is actually expecting, and what I've done (or, more likely, not done) that has lead it to form these expectations.
Basically, I had a dummy ALSA driver running (based on the example file dummy.c), and have changed it to work with hardware.
Working with what I've got, the copy operator is called with a count of 1024 (for me, that's 4k - stereo, 16 bits).
I have told ALSA that the buffer size is 64k in size (I'm assuming that ALSA is assuming that is bytes, not frames, I might test that theory). I parallel to that, I have an array of 128 pointers, each pointing to a pre allocated buffer (allocated with get_zeroed_page(GFP_KERNEL)).
I know it's overkill, but at this stage, I'm just trying to get it to work.
I have a flag per buffer, saying if its free or in use.
So, copy operator is called. I copy from user to the next free buffer (I have an incrementor to ensure they are allocated in order, and it will wrap to zero if end of buffers found) and set the relevant flag to mark it as filled (for the ISR to use).
In parallel to this, my ISR is happilly bombing along injecting 32 words at a time (two samples per 32 bit word). It keeps check of the total injected and detects when it should move onto the next buffer.
When the ISR moves on to the next buffer, it labels the previous one as free, and calls snd_pcm_period_elapsed().
So, I call elapsed() when the amount passed in through the copy has been processed (1024 samples). Seems reasonable to me.
Anyway, after elapsed() has been called, ALSA calls the pointer operator, where I return the number of frames processed by the ISR so far, divisible by 1024 (i.e. not counting any half processed chunks - possibly something to investigate).
All seems sensible behaviour, but obviously ALSA's not very happy about it.
Next, I've noticed that if I return -1 from copy if there's an overflow then mpc stops, and snd_pcm_close() is called.
So, I'm in the process of trying to work out what ALSA is actually expecting, and what I've done (or, more likely, not done) that has lead it to form these expectations.
Re: I2S: Anyone got it running?
I think it would be easier to handle the period handling if you used period size=hardware buffer size=32 samples, what can be written to FIFO in one interrupt.
So the dummy.c constants would look like this:
/* defaults */
#define MAX_BUFFER_SIZE (128*1024) /* bytes */
#define MIN_PERIOD_SIZE 128 /* = 32*4 bytes */
#define MAX_PERIOD_SIZE 128
#define USE_FORMATS (SNDRV_PCM_FMTBIT_S16_LE)
#define USE_RATE SNDRV_PCM_RATE_44100
#define USE_RATE_MIN 44100
#define USE_RATE_MAX 44100
#define USE_CHANNELS_MIN 2
#define USE_CHANNELS_MAX 2
#define USE_PERIODS_MIN 1
#define USE_PERIODS_MAX 1024
and the hardware description like this:
static struct snd_pcm_hardware dummy_pcm_hardware = {
.info = (SNDRV_PCM_INFO_INTERLEAVED ),
.formats = USE_FORMATS,
.rates = USE_RATE,
.rate_min = USE_RATE_MIN,
.rate_max = USE_RATE_MAX,
.channels_min = USE_CHANNELS_MIN,
.channels_max = USE_CHANNELS_MAX,
.buffer_bytes_max = MAX_BUFFER_SIZE,
.period_bytes_min = MIN_PERIOD_SIZE,
.period_bytes_max = MAX_PERIOD_SIZE,
.periods_min = USE_PERIODS_MIN,
.periods_max = USE_PERIODS_MAX,
.fifo_size = 0,
};
Now using above you shouldn't need .asoundrc to specify anything else than your device, though to be able to use single hw format you must configure dmixer:
http://alsa.opensrc.org/Dmix
Now just allocate single buffer in you driver do just like in dummy.c:
struct snd_pcm *pcm;
/* .... */
snd_pcm_lib_preallocate_pages_for_all(pcm,
SNDRV_DMA_TYPE_CONTINUOUS,
snd_dma_continuous_data(GFP_KERNEL),
0, MAX_BUFFER_SIZE);
So the dummy.c constants would look like this:
/* defaults */
#define MAX_BUFFER_SIZE (128*1024) /* bytes */
#define MIN_PERIOD_SIZE 128 /* = 32*4 bytes */
#define MAX_PERIOD_SIZE 128
#define USE_FORMATS (SNDRV_PCM_FMTBIT_S16_LE)
#define USE_RATE SNDRV_PCM_RATE_44100
#define USE_RATE_MIN 44100
#define USE_RATE_MAX 44100
#define USE_CHANNELS_MIN 2
#define USE_CHANNELS_MAX 2
#define USE_PERIODS_MIN 1
#define USE_PERIODS_MAX 1024
and the hardware description like this:
static struct snd_pcm_hardware dummy_pcm_hardware = {
.info = (SNDRV_PCM_INFO_INTERLEAVED ),
.formats = USE_FORMATS,
.rates = USE_RATE,
.rate_min = USE_RATE_MIN,
.rate_max = USE_RATE_MAX,
.channels_min = USE_CHANNELS_MIN,
.channels_max = USE_CHANNELS_MAX,
.buffer_bytes_max = MAX_BUFFER_SIZE,
.period_bytes_min = MIN_PERIOD_SIZE,
.period_bytes_max = MAX_PERIOD_SIZE,
.periods_min = USE_PERIODS_MIN,
.periods_max = USE_PERIODS_MAX,
.fifo_size = 0,
};
Now using above you shouldn't need .asoundrc to specify anything else than your device, though to be able to use single hw format you must configure dmixer:
http://alsa.opensrc.org/Dmix
Now just allocate single buffer in you driver do just like in dummy.c:
struct snd_pcm *pcm;
/* .... */
snd_pcm_lib_preallocate_pages_for_all(pcm,
SNDRV_DMA_TYPE_CONTINUOUS,
snd_dma_continuous_data(GFP_KERNEL),
0, MAX_BUFFER_SIZE);
Re: I2S: Anyone got it running?
Well, I have it working. It's stable, doesn't crash, can be stopped and removed and reinstalled.
BUT, it's not very good quality. Better than bitbanging, but still a bit crackly and warbly if the cpu is loaded.
The overflow issue I had was because the period size and number of periods was wrong. I admit I had to fiddle, but eventually set them to match the amount passed to the copy operator, and it stopped overflowing.
It obviously needs to be rewritten to use DMA to overcome the sound quality issues. I think the ISR is possibly not getting to fill the empty FIFO quick enough and so it drops a data symbol on the I2S lines every now and then. That's my assumption at least. It doesn't do it as much with the user space, non ALSA version
Another possibility might be to use an FIQ interrupt (not sure if that's possible, or how to change the kernel to suit that), or maybe overclocking.
But I can essentially use GMPC, to play FLAC or WAV files (I don't have any MP3s to hand to test) to listen to music on a DAC over I2S.
I want to tidy up what I have so far, then I might be in a position to put it on github.
BUT, it's not very good quality. Better than bitbanging, but still a bit crackly and warbly if the cpu is loaded.
The overflow issue I had was because the period size and number of periods was wrong. I admit I had to fiddle, but eventually set them to match the amount passed to the copy operator, and it stopped overflowing.
It obviously needs to be rewritten to use DMA to overcome the sound quality issues. I think the ISR is possibly not getting to fill the empty FIFO quick enough and so it drops a data symbol on the I2S lines every now and then. That's my assumption at least. It doesn't do it as much with the user space, non ALSA version
Another possibility might be to use an FIQ interrupt (not sure if that's possible, or how to change the kernel to suit that), or maybe overclocking.
But I can essentially use GMPC, to play FLAC or WAV files (I don't have any MP3s to hand to test) to listen to music on a DAC over I2S.
I want to tidy up what I have so far, then I might be in a position to put it on github.
Re: I2S: Anyone got it running?
That is really great news!
Re: I2S: Anyone got it running?
This sounds really positive, I'm looking forward to seeing some code and seeing if I can make it work with the DAC I've got.
Re: I2S: Anyone got it running?
Good work, Phil! Considering ARM interrupt handler's software nature it's no wonder there are dropouts. If I remember correctly there can be only one FIQ at a time (not %100 sure though, but now only the USB IRQ is a FIQ), but It is also possible to patch the irq handler in kernel so that it first checks the IRQ 81, that would help a little bit. DMA is not a real solution (more like workaround) as many would like to have as low latency as possible though the user mode can be used for those applications. DMA doesn't work either unless you use much bigger DMA buffer. Only real improvement with DMA is the "panic mode" where AXI priority can be adjusted automatically to avoid FIFO under/overflow.
Because there is no standard hardware to use with I2S the choice will be that of users. ALSA (FIFO or DMA) is good solution with playback only systems where the latency is no issue (with the exception of software synthesizers where the shortest MIDI to PCM/I2S out latencies are important). ALSA SoC support for BCM2835 would be best solution as there is the largest CODEC support. Like almost all Wolfson CODEC's are supported already, just plug in the CODEC with it's I2S or SPI and I2S ports and you are done if you have the ALSA SoC support for your machine and platform (means I2S FIFO or DMA and I2C and SPI implementation). Finally, there's the User Mode solution which is more like "bare metal". This means that you write you own application which take full control of the PCM/I2S interface. Sometime that's better choice than ALSA because you have no depencies whatsoever - you may use any DAC or CODEC chip if you can. I've been considering using RPi's for applications like speaker crossovers or guitar FX boxes which dedicate the RPi to only one use so it doesn't matter does it support ALSA or not. You can always add software support for Jack, LADSPA, LV2 or VST or whatever you need to be able to use existing audio DSP plugins available in Linux. If you process incoming audio it's enough if you can by polling pull let's say 32 samples of data, process it and send back to output FIFO without no worries about extreme CPU usage of polling as long as it works (and of course your application would then be run as root and using RT priority, and USB and other useless services disabled).
Because there is no standard hardware to use with I2S the choice will be that of users. ALSA (FIFO or DMA) is good solution with playback only systems where the latency is no issue (with the exception of software synthesizers where the shortest MIDI to PCM/I2S out latencies are important). ALSA SoC support for BCM2835 would be best solution as there is the largest CODEC support. Like almost all Wolfson CODEC's are supported already, just plug in the CODEC with it's I2S or SPI and I2S ports and you are done if you have the ALSA SoC support for your machine and platform (means I2S FIFO or DMA and I2C and SPI implementation). Finally, there's the User Mode solution which is more like "bare metal". This means that you write you own application which take full control of the PCM/I2S interface. Sometime that's better choice than ALSA because you have no depencies whatsoever - you may use any DAC or CODEC chip if you can. I've been considering using RPi's for applications like speaker crossovers or guitar FX boxes which dedicate the RPi to only one use so it doesn't matter does it support ALSA or not. You can always add software support for Jack, LADSPA, LV2 or VST or whatever you need to be able to use existing audio DSP plugins available in Linux. If you process incoming audio it's enough if you can by polling pull let's say 32 samples of data, process it and send back to output FIFO without no worries about extreme CPU usage of polling as long as it works (and of course your application would then be run as root and using RT priority, and USB and other useless services disabled).
Re: I2S: Anyone got it running?
https://github.com/philpoole/snd_pi_i2s

(please, don't expect audiophile quality yet)
DMA of 4k a time would probably be better than how it's currently working. 1k frames isn't a very long time, but much more comfortable to work with than 32 frames.
Another option I'm toying with is possibly a thread that fills the fifo, but waits on a semaphore, triggered by the ISR. If the ISR triggers when 8 frames are remaining (CS_A |= 1<<5), then that hopefully wouldn't drop out.
Please feel free to have a play, and improve, but I can't do much towards tailoring to others' needs.

(please, don't expect audiophile quality yet)
DMA of 4k a time would probably be better than how it's currently working. 1k frames isn't a very long time, but much more comfortable to work with than 32 frames.
Another option I'm toying with is possibly a thread that fills the fifo, but waits on a semaphore, triggered by the ISR. If the ISR triggers when 8 frames are remaining (CS_A |= 1<<5), then that hopefully wouldn't drop out.
Please feel free to have a play, and improve, but I can't do much towards tailoring to others' needs.
Re: I2S: Anyone got it running?
Just discovered a lot of (not all) my corruption vanished when I disconnected the super-cheap keyboard I was using at the time! I guess it's not fully compliant with USB, and so hammered the high priority IRQ, forcing mine to wait.
Without the, let's politely say, troublesome keyboard connected, or with a sensible replacement, it is MUCH better!
Not perfect, but much better.
I occasionally notice the channels swapping, but not really dropping out. So, progress.
Without the, let's politely say, troublesome keyboard connected, or with a sensible replacement, it is MUCH better!
Not perfect, but much better.
I occasionally notice the channels swapping, but not really dropping out. So, progress.
Re: I2S: Anyone got it running?
Hi ,
I just tried the driver together with my Y2 DAC (http://www.amb.org/audio/gamma2/). It works
There are some electrical issues, it only started to work when I touched the data line with a scope probe. But with a small capacitor from data to ground it is fairly stable. Maybe the pi needs some output buffering.
Thanks Phil keep up the good work.
Leon
I just tried the driver together with my Y2 DAC (http://www.amb.org/audio/gamma2/). It works

There are some electrical issues, it only started to work when I touched the data line with a scope probe. But with a small capacitor from data to ground it is fairly stable. Maybe the pi needs some output buffering.
Thanks Phil keep up the good work.
Leon
Re: I2S: Anyone got it running?
Mods should change the title to "I2S: Someone got it running!"
Thank you, Phil!
Thank you, Phil!
Re: I2S: Anyone got it running?
I looked at the code and noticed there is only single period defined with the size of 4096. With ring buffers usually at least two are needed, one is the one which is currently played back and the other is the one application and drivers are filling for the the next period time. Got to play with the parameters in the hardware description, got my TDA1543 DAC finally wired.
Re: I2S: Anyone got it running?
Thanks for the positive responses, and all the help and support over the recent weeks.
mhelin, I agree it's not ideal. I resorted to fiddling until it worked, I was losing the plot a bit to use reasonable values that late at night. I agree it should be more than one period, but I couldn't get it right at the time (it was late).
lbrouwer, it's good to hear it works elsewhere. Sounds like you might need termination resistors on your I2S lines or similar. I don't recall anyone using them in parallel, but I've seen resistances in parallel, typically acting as a low pass filter on the data lines. Might be worth considering.
There are some issues. I noticed very occasionally when progressing to the next track with MPC, there'd be a lot of clicks for a second or two, then would play from the start as normal. Shall have to debug that at some point.
mhelin, I agree it's not ideal. I resorted to fiddling until it worked, I was losing the plot a bit to use reasonable values that late at night. I agree it should be more than one period, but I couldn't get it right at the time (it was late).
lbrouwer, it's good to hear it works elsewhere. Sounds like you might need termination resistors on your I2S lines or similar. I don't recall anyone using them in parallel, but I've seen resistances in parallel, typically acting as a low pass filter on the data lines. Might be worth considering.
There are some issues. I noticed very occasionally when progressing to the next track with MPC, there'd be a lot of clicks for a second or two, then would play from the start as normal. Shall have to debug that at some point.
Re: I2S: Anyone got it running?
thanks for the code and work you've done so far phil, just a quick question - do you know what I need to change in the driver if I plan on using an external clock for I2S?
cheers.
cheers.
Re: I2S: Anyone got it running?
Hey SniperSniper435 wrote:thanks for the code and work you've done so far phil, just a quick question - do you know what I need to change in the driver if I plan on using an external clock for I2S?
cheers.
I'd like to know how do you use a external clock for i2s. As far as I know i2s transmits clock within.
Do you mean replacing the onboard oscillator to get the master clock?
Let me know your approach please
Bests
Re: I2S: Anyone got it running?
sorry I wasn't very clear - I was meaning that we'll be supplying our own master clock rather than using the onboard oscillator
Re: I2S: Anyone got it running?
I can't recall off the top of my head exactly which bit to toggle, but there is a bit in one of the registers (probably either MODE or CS, or actually more likely the clock registers) that determines if it will use an internal or external clock.
I intend to do the same myself at some point, so will probably set it as a compile flag option.
I intend to do the same myself at some point, so will probably set it as a compile flag option.
Re: I2S: Anyone got it running?
A quick peruse of the data sheet tells me its bit 23, CLKM, of MODE_A. You would need to set it to 1 to make the clock an input.
Re: I2S: Anyone got it running?
Great project!!
Do you think it could work directly with something like this?? http://www.qnktc.com/ab_1_12_13.php
Do you think it could work directly with something like this?? http://www.qnktc.com/ab_1_12_13.php