etherCog teaser: UDP Audio streaming

scanlimescanlime Posts: 106
edited August 2008 in Propeller 1 Vote Up0Vote Down
I've been working on a new network driver and TCP/IP stack for the Prop lately. I call it "etherCog"- it's intended to be small, fast, and easy to use. The driver is written entirely in assembly and occupies a single cog. It has a fast 10 MHz SPI engine, and it has an asynchronous zero-copy socket implementation.

So, there is a lot of work left to do before it's a useful general-purpose library. The transmit engine needs to support retry, it needs DHCP, TCP, an ARP cache... but I got a neat demo working: I can stream CD quality audio to the Prop over UDP.

The attached "demo-udp-audio" app is set up for the Propeller demo board, but it will run on any other setup with minimal changes. I hooked up an ENC28J60 breakout board from Spark Fun on pins 0 to 3. (SO, SI, SCK, and CS) It is currently hardcoded for an IP of 192.168.1.32, and a bogus MAC address, but you can change this in the driver source. When it boots, it should immediately respond to ARP requests and pings.

Now you can stream 16-bit 44kHz WAV files to it using the included Python script. It should work on any operating system that Python works on.

Let me know if it works for you. Audio quality is okay, but the digital PLL I'm using to recover the audio clock and phase is a little twitchy- so sometimes the sampling rate does funny things. It also only has a 16 kB audio buffer, so it will be sensitive to network congestion. Let me know if it works at all for you [noparse]:)[/noparse]

If you're interested in etherCog, I've been developing it in a public Subversion repository. It's certainly not ready for prime time yet, but if you're feeling adventurous you can use it to send and receive UDP packets really fast. The latest code is always at:

svn.navi.cx/misc/trunk/propeller/etherCog/

--Micah

Comments

  • 13 Comments sorted by Date Added Votes
  • AleAle Posts: 2,283
    edited August 2008 Vote Up0Vote Down
    Micah,

    What a beautiful piece of code. You implemented many really nice ideas. Well this shows that the 496 long barrier... can be overcome without losing that much execution time. Simply put: great.
  • jazzedjazzed Posts: 11,795
    edited August 2008 Vote Up0Vote Down
    Wow. Good work Micah. This looks great! Your code opens some nice project possibilities.

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    --Steve
  • lonesocklonesock Posts: 878
    edited August 2008 Vote Up0Vote Down
    Awesome!

    (I was just about to start work on something similar, thanks for beating me to it, and for the MIT license!)

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    lonesock
    Piranha are people too.
    Free time status: see my avatar [8^)
    F32 - fast & concise floating point: OBEX, Thread
    Unrelated to the prop: KISSlicer
  • Harrison.Harrison. Posts: 484
    edited August 2008 Vote Up0Vote Down
    Good work Micah! I can't wait to try this out.
  • scanlimescanlime Posts: 106
    edited August 2008 Vote Up0Vote Down
    Thanks for the kind words, guys [noparse]:)[/noparse]

    @Ale: Yes, there are some really interesting tricks I'm using to save memory... hopefully they don't make the code too hard to follow. The "meta-LMM" thing I use for initialization is especially weird.

    --Micah
  • RaymanRayman Posts: 8,201
    edited August 2008 Vote Up0Vote Down
    Very cool! How close is this to being an internet radio receiver?
    Prop Info and Apps: http://www.rayslogic.com/
  • scanlimescanlime Posts: 106
    edited August 2008 Vote Up0Vote Down
    Rayman said...
    Very cool! How close is this to being an internet radio receiver?

    Well, as it stands it's more like Audio over IP. Very low latency, but it has a tiny buffer so you need a fairly good quality LAN segment to operate it on.

    Once etherCog has TCP sockets, it would certainly be fast enough to stream internet radio. The Prop could do it, but you'd need an external MP3 decoder and probably some external SRAM for buffering the stream.

    The original reason I started etherCog was that I wanted to build a Propeller-based audio streaming device- sort of a super-simple super-cheap version of the Squeezebox. The Audio over IP approach works okay, but it would certainly be better if I had a big chunk of buffer memory. I wonder if the Prop would still be a cost effective solution if I had to hook it up to a few megabytes of SPI-attached SRAM? At that point it's tempting to move to a larger processor, but then I probably couldn't do S/PDIF in software [noparse];)[/noparse]

    --Micah
  • lonesocklonesock Posts: 878
    edited August 2008 Vote Up0Vote Down
    This is very similar to the application I'm working on. To gain buffer space I'm doing some simple compression. Here's a link with my C++ implementation. Inspired by the audio compression as used in the HSS, but modified for multiple bit-rates and any bit-depth. Using 2 samples per byte sounds pretty good, and the de/compression is fast.

    www.lonesock.net/bba.html

    No SPIN/PASM yet, sorry. (And probably not forthcoming as I have an updated version of the compressor which sounds better for the same bit-rate, but naturally is more complex...I'm working on that ATM).

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    lonesock
    Piranha are people too.
    Free time status: see my avatar [8^)
    F32 - fast & concise floating point: OBEX, Thread
    Unrelated to the prop: KISSlicer
  • RaymanRayman Posts: 8,201
    edited August 2008 Vote Up0Vote Down
    Ok, I see the problem (needing MP3 decode...)

    Still, there's at least one uncompressed station out there:

    http://kexp.org/about/uncompressedstream.asp

    PS:· I think the compression that lonesock refers to is probably a good idea if you have buffer issues...
    Prop Info and Apps: http://www.rayslogic.com/
  • scanlimescanlime Posts: 106
    edited August 2008 Vote Up0Vote Down
    I thought about compressing the audio.. a simple ADPCM scheme could give nearly-lossless results, and it would be efficient to implement on the Propeller. But, absolute best case, I compress at maybe a 2x rate.. now my tiny fraction of a second buffer is a larger but still tiny fraction of a second. To robustly stream internet radio, you probably need a buffer of 1MB or so.

    I guess if you're buffering MP3 data before sending it to a decompressor chip, you need less space. Still, 32K of ram is kind of limiting when you need to actually store audio data.

    I think I'll stick with the Audio-over-IP approach, to keep part count down, but you could certainly have your media server download an internet radio stream, decompress it, buffer it, and send out a nice isochronous stream of UDP packets for the Prop to play back. The PLL scheme in my demo needs some tuning, but I think the basic idea is sound. I'm basically doing audio clock recovery on a stream of network packets the same way a typical stereo receiver recovers the clock from an S/PDIF stream.

    The nicest feature of the AoIP approach is that you can do really cheap multicast audio. Imagine a PA system based on the Propeller with etherCog: a central server sends out music or announcements over broadcast or multicast UDP. It's distributed by a normal Ethernet and IP network, then received by any number of PA speakers. Each speaker independently synchronizes its sample rate to the rate of the incoming audio. I was discussing this idea with a friend, and he was even interested in building the Propeller, ethernet adapter, and an amplifier directly into the speaker cabinet. Imagine a nice set of bi-amped speakers with just an RJ45 and power on the back [noparse]:)[/noparse]

    --Micah
  • lonesocklonesock Posts: 878
    edited August 2008 Vote Up0Vote Down
    You could also use a SD card as your intermediate buffer. I'm getting about 200kB/s write speeds on my cards (SD & MMC) using the FSRW code with FAT16 layer. That is just enough to save a CD-quality stereo stream, uncompressed, but you'd have to read that data back too. Reading is actually about twice as fast as writing on my hardware, so you would really need about 1.5 x 44100*2*2, or 265kB/s transfer speeds minimum. If you ignore the FAT16 layer and just do direct reads & writes you can go much faster (especially if you modify the SDSPI* code to read/write multiple blocks instead of just single blocks). So I think you would probably want to end up using some simple compression scheme, but using a SD card as a buffer should be doable.

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    lonesock
    Piranha are people too.
    Free time status: see my avatar [8^)
    F32 - fast & concise floating point: OBEX, Thread
    Unrelated to the prop: KISSlicer
  • scanlimescanlime Posts: 106
    edited August 2008 Vote Up0Vote Down
    lonesock said...
    You could also use a SD card as your intermediate buffer. I'm getting about 200kB/s write speeds on my cards (SD & MMC) using the FSRW code with FAT16 layer. That is just enough to save a CD-quality stereo stream, uncompressed, but you'd have to read that data back too. Reading is actually about twice as fast as writing on my hardware, so you would really need about 1.5 x 44100*2*2, or 265kB/s transfer speeds minimum. If you ignore the FAT16 layer and just do direct reads & writes you can go much faster (especially if you modify the SDSPI* code to read/write multiple blocks instead of just single blocks). So I think you would probably want to end up using some simple compression scheme, but using a SD card as a buffer should be doable.

    That's a good point. If I used a 1GB SD card as a huge ring buffer (no filesystem, just a raw block device) I'd wrap around so infrequently that it probably isn't worth worrying about the number of write cycles supported by the card. I might try that in the next couple days. I have a dev board with an SD slot and S/PDIF out which I've been meaning to solder an ENC28J60 to.

    And hey, if your device crashes, you have a "black box" recording of the last several hours of audio it received.

    --Micah
Sign In or Register to comment.