[alsa-devel] [RFC] Sound/HID: wiimote: add speaker support

David Herrmann dh.herrmann at gmail.com
Fri Apr 26 15:56:12 CEST 2013


On Fri, Apr 26, 2013 at 3:28 PM, David Henningsson
<david.henningsson at canonical.com> wrote:
> On 04/26/2013 03:00 PM, David Herrmann wrote:
>> Hi
>> On Thu, Apr 25, 2013 at 9:11 AM, David Henningsson
>> <david.henningsson at canonical.com> wrote:
>>> On 04/20/2013 08:14 PM, David Herrmann wrote:
>>>> Classic Wii Remotes provide a speaker on the device. We can stream PCM
>>>> data, mute and change volume via special protocol requests and remote
>>>> audio registers.
>>>> The device supports several different formats, but fully understood are
>>>> only signed 8-bit PCM and something that looks like 4-bit Yamaha ADPCM.
>>>> Theoretically, we can set data-rates from 183Hz up to 12MHz, but a
>>>> realistic range for Bluetooth l2cap is 1500-4000 Hz.
>>>> Data is streamed as 20bytes blocks and must be sent in a constant rate.
>>>> There is no way to read the current position. It would be way too slow,
>>>> anyway.
>>> Hi,
>>> Fun project!
>>> I'm not sure I'm qualified to answer, but I would have allocated a buffer
>>> of
>>> kernel memory, say 64K (or maybe less, since the sample rate is so low).
>>> That would be the buffer you expose to userspace, and userspace would set
>>> period and buffer sizes according to this buffer.
>>> Your code doesn't really expose if/how you can tell if you're allowed to
>>> send 20 more bytes or not.
>> I cannot tell at all. That's the problem. I just send data at the
>> expected rate and let the device deal with it. For instance with 8bit
>> PCM at 2000 Hz that makes one 20byte packet every 10ms. That's
>> 16kbit/s, which should work via Bluetooth l2cap.
> Btw, since this is something bluetooth related, have you checked with the
> bluez/bluetoothd project, so it isn't easier to integrate that way instead
> of in a kernel driver?
> That's how bluetooth audio is usually done.

Hehe, I'm involved in Bluetooth kernel development so I did check that
first ;) The thing is, Nintendo did a pretty bad job with audio on the
Wiimote. Bluetooth provides SCO channels specifically for audio but
Nintendo tunnels audio through an HID connection, yay! On the other
hand, the speaker is mostly used for small sound-effects so that
explains why they didn't care for higher rates.

As l2cap+HID is handled in the kernel, I would have to provide a
char-interface or sysfs PCM-stream or whatever for user-space so they
can send HID reports with audio data while the kernel sends it's own
HID reports for normal HID operation. That does work, but I thought if
I already have all the device handling in the kernel, I should at
least try to do it as sound-driver. If it doesn't work out I can
always fall back to this option.

>> Even 10x the
>> sample-rate (20kHz) with 160kbit/s should be possible, but the latency
>> gets pretty high that I doubt I can do that with a 20byte buffer. I
>> haven't figured out how big the real buffer of the device is so until
>> then I expect it to be as low as 20 bytes (the proprietary driver does
>> that, too).
>> If the transmission buffer overflows, I just drop packages so there is
>> currently no easy way for me to see whether there's enough room for a
>> next package or not. But the connection is reliable so once a packet
>> is in the buffer, it's "guaranteed" to be transmitted in order to the
>> device.
>> What's the expected thing to do if the connection is too slow? Should
>> I pause and resume or drop packets?
> Not sure what you mean here. Maybe bluetooth people know this problem
> better.
>>> But assuming you have a timer running at a
>>> reasonable interval, that code would do something like:
>>> if (ready_to_send_packet_to_wiimote()) {
>>>    send_20_bytes_to_wiimote(kernel_buffer_ptr + offset);
>>>    offset += 20;
>>>    offset %= buffer_size;
>>>    if (offset % period_size < 20)
>>>      snd_pcm_period_elapsed();
>>> }
>> Ah, ok, so I just send the data in the timer-interrupt and call
>> snd_pcm_period_elapsed() if the fill-state gets smaller than 20 bytes?
>> I guess snd_pcm_period_elapsed() is atomic so I can expect it to
>> prepare the buffer so the next timer-interrupt is guaranteed to have a
>> valid buffer or a cleared buffer?
> snd_pcm_period_elapsed() just tells userspace that userspace can start
> filling up a new period in the ringbuffer. snd_pcm_period_elapsed() does not
> prepare anything.
> E g, if userspace requests a buffer of 64K and period of 16K, you should
> call snd_pcm_period_elapsed() every time you cross a period boundary.
> Perhaps "offset % period_size < 20" can be more clearly written as "(offset
> / period_size) != (old_offset / period_size)", if that's more
> understandable?

Ah, now I understand. I thought the period-size is the maximum
transmission size that I can use in the driver (20 in this case). Ok,
your explanation helped. I think I understand the "period" concept

Apart from that, should I provide an mmap() interface for the buffer
so I can map it into the clients address-space? Or should I prevent
mmap() and let them use read/write? I guess I wouldn't need the .copy
and .silence callbacks if I provide mmap(), right? And a
vm_insert_pfn() would be quite simple to do..

What happens if user-space cannot keep up with the rates? Do I just
continue sending garbage from the buffer or does the alsa core pause
the transmission or silence it until user-space catches up?


>>> I'm guessing that the hrtimer API would be appropriate, and some kind of
>>> RT
>>> priority. But I'm not really experienced in that area of kernel
>>> programming.
>> Yeah, the hrtimer should be perfect for this. I was just wondering
>> whether the alsa-core already provides an optional timer for the
>> drivers but it seems I need to do it myself.
>> Thanks a lot for the feedback! Lets see how it works out.
>> David

More information about the Alsa-devel mailing list