2013-01-07

USB audio dock for Android

If you want to get audio from your Android device to your speakers in a transparent (not application-specific) way, there have traditionally been two options: Bluetooth and the 3.5 mm jack (I guess MHL and HDMI are also valid options on some devices). Android 4.1 introduced another way: audio over USB. This has one additional benefit: you don't have to plug in an additional cable for the phone to charge. Problem is, I'm not aware of any actual products like audio docks that would make use of this feature. So I decided to make one myself.



I used a simple USB dock and a Raspberry Pi. The Raspberry Pi's audio output is connected to my living room amplifier. Below I describe the steps necessary to make this work.

Obviously you'll need a dock that matches your phone's physical shape (or you can skip the actual dock and just use a USB cable). The Raspberry Pi could be replaced by any computer running Linux. The Pi is a good fit here because of its small size. Then again it is a bad fit because its audio components are low quality (this could be worked around by using HDMI as the audio output or connecting an external sound card via USB, but I haven't tested these options).

The good thing about Android's audio over USB feature is that it uses a standard protocol, so if you connect your phone to a modern Linux box, it just shows up as an audio input. Therefore we only need to do two things here: enable the audio over USB feature after the device is plugged in and route the sound from the input to the output that goes to the speakers. We'll use a simple Python script to send the necessary magic over USB and PulseAudio for the audio routing.

I used a Raspbian "wheezy" image on the Pi, if you're using another distribution (or not using a Raspberry Pi), some adjustments may be necessary.

First, install git and PulseAudio:
sudo apt-get update
sudo apt-get install pulseaudio git
Use git to install a newer version of PyUSB than is available in Debian repositories:
git clone https://github.com/walac/pyusb
cd pyusb
sudo python setup.py install
Next we configure PulseAudio. Edit /etc/defaults/pulseaudio to start the daemon at boot and allow module loading:
PULSEAUDIO_SYSTEM_START=1
DISALLOW_MODULE_LOADING=0
In /etc/pulse/system.pa change the line that says:
load-module module-native-protocol-unix
to:
load-module module-native-protocol-unix auth-anonymous=1
(This will allow us to use the pactl command without worrying about authentication.)

Add the following line to /etc/pulse/daemon.conf:
resample-method = trivial
(I don't know why this is necessary. It wasn't needed on my regular PC, but audio wasn't working without it on the Pi.)

Then create a file named /etc/udev/rules.d/dock.rules and put the following line in it:
ACTION=="add",SUBSYSTEM=="usb",ATTR{idVendor}=="04e8",ATTR{idProduct}=="685c",RUN+="/home/pi/phone_docked.sh %s{idVendor} %s{idProduct}"
This will run /home/pi/phone_docked.sh every time my phone is connected. As you can see, unfortunately the USB vendor and product IDs for my specific phone are hardcoded here (they are then passed to the script as parameters). You'll need to change them to match your phone (you can look up the IDs by running lsusb with the phone connected). I don't know how to write a udev rule that will trigger when any Android phone is connected (instead it's probably more feasible to write a rule that matches Android phones and also some other devices and then just try to talk to it as if it was an Android phone and gracefully handle the situation if it doesn't respond).

The phone_docked.sh script does two things, first it runs the Python script (android-usb-audio.py) that enables audio over USB on the phone (passing along the vendor and product IDs), then it loads a PulseAudio module that routes the audio from the phone to the default output. Here's what the script looks like (put this in /home/pi/phone_docked.sh):
#!/bin/bash

/home/pi/android-usb-audio.py $1 $2
(sleep 3s ; pactl load-module module-loopback source=`pactl list sources short | grep alsa_input.usb | cut -f 1`) &
As you can see, the second part is not very elegant, it just waits 3 seconds where it should wait for the PulseAudio source to actually show up. Also it assumes there are no other USB audio sources.

Finally, here's the Python script that sends the necessary USB magic to the phone. This tells the phone to send audio over USB. The script gets the USB vendor and product IDs from command line parameters (put this in /home/pi/android-usb-audio.py):
#!/usr/bin/env python

import usb.core
import time
import sys

dev = usb.core.find(idVendor=int(sys.argv[1], 16), idProduct=int(sys.argv[2], 16))
mesg = dev.ctrl_transfer(0xc0, 51, 0, 0, 2)
# here we should check if it returned version 2
time.sleep(1)
# requesting audio
dev.ctrl_transfer(0x40, 0x3a, 1, 0, "")
# putting device in accessory mode
dev.ctrl_transfer(0x40, 53, 0, 0, "")
(The magic numbers come from the Android Open Accessory protocol.)

Give the two scripts executable permissions:
chmod 755 /home/pi/android-usb-audio.py
chmod 755 /home/pi/phone_docked.sh
And that's it, reboot, connect your phone and enjoy audio coming from your speakers. (I know what you're thinking: all this work just to avoid plugging in the 3.5 mm jack??)

Since the phone will charge from the Pi's USB port, you should use a power supply that will be enough for the Pi and for the 500 mA that the phone will draw.

I'm not demonstrating it here, but while the phone is connected, you can also send HID commands like play/pause/next/previous to it. This way you could make some physical (or web) controls for the dock and pass them through to the device.

While you're at it, you could plug a Bluetooth USB dongle to the Rasbperry Pi and make your dock also accept audio via Bluetooth. There are tutorials on the web showing how to do that.

12 comments:

  1. This is very awesome! I just got a Pi and hooked it up as a media center that streams content from my desktop computer. Your project seems like a great reason to buy another RP :) Nice work

    ReplyDelete
  2. Definitely upgrade to a USB sound card. The Pi sound output is atrociously bad for music.

    ReplyDelete
  3. is there some way to do the reverse, i.e have the android phone play audio streamed over USB by the pc. The android phone basically acts like a USB soundcard. I wanted to do this because I like the audio quality on the phone way better than my PC's.

    ReplyDelete
    Replies
    1. I second that request. I'd like a procedure for doing it from a Pi that's as detailed as this one. With that plus what you have detailed here a Pi could be set up as an insert to filter a phone's audio stream and do magic that would be much harder to do in the phone itself if possible at all.

      Delete
  4. Thank you for your help.

    ReplyDelete
  5. Thank you! This solves half of a design problem I have.

    ReplyDelete
  6. But I'm struggling to get any audio out:

    I know the phone and Rpi are definetly communicating because I can see the phone in the sources list, alsamixer/pavucontrol
    The phone recognises the USB as an audio sync because when I pull the cable out, the music stops.
    I've checked the volumes using alsamixer and pavucontrol
    pacmd shows both the sink and source as running. Just no audio. I've tried with and without trivial audio sampling.

    Any ideas?

    ReplyDelete
  7. is there a way to make this work with an ipod/iphone as well?

    ReplyDelete
  8. Is there a way to remote control the Android device? Like "play", "pause", "next" ...

    ReplyDelete
  9. I'm using this command to make accessories mode "python android-usb-audio.py <****VID> <****PID>" and the phone is going to accessories mode but the problem is i need hdmi output as well as usb o/p and audio should play in hdmi-tv/usb-audio. I configure all the settings provide above but not getting sound.
    I m also tried using arecord & aplay for normal mic&Speaker ,It was working fine but when i use my android phone in accessories mode as a audio device i'm just getting a file of 44 bytes but nothing is recording in that file. please help me.
    pi@raspberrypi ~/usb $ cat /proc/asound/cards
    0 [ALSA ]: bcm2835 - bcm2835 ALSA
    bcm2835 ALSA
    1 [U0x6e60x7210 ]: USB-Audio - USB Device 0x6e6:0x7210
    USB Device 0x6e6:0x7210 at usb-bcm2708_usb-1.2, full speed
    2 [Lenovo ]: USB-Audio - Lenovo
    Lenovo Lenovo at usb-bcm2708_usb-1.3, high speed

    pi@raspberrypi ~/usb $ lsusb
    Bus 001 Device 002: ID 0424:9514 Standard Microsystems Corp.
    Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
    Bus 001 Device 003: ID 0424:ec00 Standard Microsystems Corp.
    Bus 001 Device 006: ID 06e6:7210 Tiger Jet Network, Inc. Composite Device
    Bus 001 Device 008: ID 18d1:2d03 Google Inc.


    pi@raspberrypi ~/usb $ arecord -l
    **** List of CAPTURE Hardware Devices ****
    card 1: U0x6e60x7210 [USB Device 0x6e6:0x7210], device 0: USB Audio [USB Audio]
    Subdevices: 1/1
    Subdevice #0: subdevice #0
    card 2: Lenovo [Lenovo], device 0: USB Audio [USB Audio]
    Subdevices: 1/1
    Subdevice #0: subdevice #0

    pi@raspberrypi ~/android-usb-pi $ aplay -l
    **** List of PLAYBACK Hardware Devices ****
    card 0: ALSA [bcm2835 ALSA], device 0: bcm2835 ALSA [bcm2835 ALSA]
    Subdevices: 8/8
    Subdevice #0: subdevice #0
    Subdevice #1: subdevice #1
    Subdevice #2: subdevice #2
    Subdevice #3: subdevice #3
    Subdevice #4: subdevice #4
    Subdevice #5: subdevice #5
    Subdevice #6: subdevice #6
    Subdevice #7: subdevice #7
    card 0: ALSA [bcm2835 ALSA], device 1: bcm2835 ALSA [bcm2835 IEC958/HDMI]
    Subdevices: 1/1
    Subdevice #0: subdevice #0
    card 1: U0x6e60x7210 [USB Device 0x6e6:0x7210], device 0: USB Audio [USB Audio]
    Subdevices: 1/1
    Subdevice #0: subdevice #0

    Android mobile is not showing in aplay -l command as a play device but it showing in arecord -l as a recording device.But sound is not recorded.

    ReplyDelete
  10. Would you still recommend running this through raspberry PI or would you recommend arduino?
    any updates domne to this?

    ReplyDelete