Sunday, 23 November 2008

Progress with MusicPal Sound Programming

After a couple of hours of using strace to try to understand what Nashville was doing, and looking at the "base_audio" directory in the MusicPal kernel source directory, I have finally got it to make a noise.

Basically you have to use /dev/audio and send some ioctl's to initialise the hardware, and set up the graphic equaliser. You can then write raw data to /dev/audio just like you would with OSS. In fact, I used the OSS sinegen.c example program as the basis and just altered the ioctls. http://www.musicpal.webhop.net/audiotest.c is my sample program (it needs audio.h from the MusicPal kernel source code to compile) - it makes a continuous tone come out of the MusicPal speaker, which is progress! There are some comments in the code to show what it is doing (as far as I can work out anyway!).

15 comments:

Anonymous said...

Where can I download the audio.h needed to compile audiotest.c?

NT said...

Ooops, sorry - it came with the MusicPal linux kernel source code. It is now at http://musicpal.webhop.net/audio.h".
I am working on making MusicPlayerDaemon work on the musicpal so it can do something more useful, but that is likely to be a few days off!

Anonymous said...

ah, ok I did look in the kernel source but it seems I was looking at the vanilla EDLK kernel source not the MusicPal kernel source which has to be downloaded separately. Anyway audio.h also references audiotypes.h, so After I found both these files the audiotest.c file compiles successfully, however I was unable to get a sound out of my Music Pal perhaps you could provide some usage instructions?

Anonymous said...

More progress, did some fiddling and got audiotest to produce a nice test tone.

Then I did the following. On a Linux box run

mpg123 -s music.mp3 | nc -l -p 1234

Telnet to MusicPal and do

./audiotest
ctrl+c

nc <LinuxBoxIP> 1234 > /dev/audio

Woah music playing! unfortunately it only plays on the MusicPals builtin speaker and not the HIFI that I have attached to my MusicPal

NT said...

That is real progress - the sine wave is not good to listen to!
Does audiotest play through your hifi, or just through the MusicPal speakers? I'll have a dig through the Kernel source code for how to switch the external outputs on and off - I think the audiotest.c program has a line that I said I did not understand - the nashville MusicPal program does an extra ioctl that does not seem to be necessary - it might be enabling the audio outputs. I'm having trouble with my internet connection so can't check, but the sound mixer in the MusicPal is an obscure string of letters and numbers, and it has a driver in the kernel source tree if you want to have a look.

NT

NT said...

I have just had another go at it, and I think you might need the main nashville application to play something before audiotest works? Is that what you found?
If that is the case we are not initialising the audio output correctly - presumably the AUDIO_SET_WOLFSON_REG call in audiotest.c is incorrect. The comment above it points to the datasheet for the mixer chip if you want to try to work out what we should do - I've just got to make a bit of progress on a little Christmas present before I get back to it!

Anonymous said...

I compiled and ran strace and noticed, that nashville opens /dev/audio twice, once in read/write mode, and once in write only mode. The fd for the rw was 6 so you can see the respective ioctl's against this fd:

ioctl(6, 0x40047015, 0xbec16ca8) = 0
ioctl(6, RTC_PLL_SET, 0xbec16ca8) = 0
ioctl(6, 0x40047013, 0xbec16c2c) = 0
ioctl(6, 0x40047013, 0xbec16c2c) = 0

these numbers didn't make alot of sense to me until I translated them into the numbers defined in audio.h

so you can see these translated here:


ioctl(6, AUDIO_SWITCH_OUTPUT, 0xbec16ca8) = 0
ioctl(6, AUDIO_GET_WOLFSON_REG, 0xbec16ca8) = 0
ioctl(6, AUDIO_SET_VOLUME, 0xbec16c2c) = 0

I used the following program to get these:

giles@korma:~/MusicPal$ cat iow.c
#include <linux/ioctl.h>

main()
{
int i;
for(i = 0;i<255; i++)
printf("I=%d IOW=%x\n",i,_IOW('p',i,unsigned long));
}

Anonymous said...

Well try as I might I cannot get the lineout to work. Here is my modified version of audiotest.c

giles@korma:~/MusicPal$ cat audiotest.c
/***************************************************************
* NAME: Audiotest.c
* DESC: A simple test of the MusicPal audio device.
* It is an adaptation of the OSS sinegen.c example - it should
* just play a simple sine wave tone.
* The ioctrl calls were identified by disecting the base_audio directory
* in the MusicPal kernel source directory.
* HIST: 23nov2008 GJ ORIGIAL VERSION
*/

#include <stdio.h>
#include <fcntl.h>
#include "audio.h"

int fd_out;
int sample_rate = 48000;

/***************************************************************
* write_sinewave is the same code as used in the OSS sinegen.c example
***************************************************************/
static void
write_sinewave (void)
{
static unsigned int phase = 0; /* Phase of the sine wave */
unsigned int p;
int i;
short buf[1024]; /* 1024 samples/write is a safe choice */

int outsz = sizeof (buf) / 2;

static int sinebuf[48] = {

0, 4276, 8480, 12539, 16383, 19947, 23169, 25995,
28377, 30272, 31650, 32486, 32767, 32486, 31650, 30272,
28377, 25995, 23169, 19947, 16383, 12539, 8480, 4276,
0, -4276, -8480, -12539, -16383, -19947, -23169, -25995,
-28377, -30272, -31650, -32486, -32767, -32486, -31650, -30272,
-28377, -25995, -23169, -19947, -16383, -12539, -8480, -4276
};

for (i = 0; i < outsz; i++)
{
p = (phase * sample_rate) / 48000;
phase = (phase + 1) % 4800;
buf[i] = sinebuf[p % 48];
}


if (write (fd_out, buf, sizeof (buf)) != sizeof (buf))
{
perror ("Audio write short");
/*exit (-1);*/
}
}

/**********************************************************
* open_audio_device is re-written for the musicpal
***********************************************************/
static int
open_audio_device (char *name)
{
int tmp, fd;
wm8971_reg reg;
equalizer_t eq;
volume_t vol;

fprintf(stderr,"opening device %s\n",name);
if ((fd = open (name, O_RDWR, 0)) == -1)
{
perror (name);
exit (-1);
}


if (ioctl (fd, AUDIO_IOC_HWRESET, 0x1) == -1)
{
perror ("AUDIO_IOC_HWRESET");
exit (-1);
}
else
fprintf(stderr,"AUDIO_IOC_HWRESET called successfully\n");

eq.bass = 0;
eq.treble = 0;
eq.dac = 255;
eq.flags = 0;
if (ioctl (fd, AUDIO_SET_EQUALIZER, &eq) == -1)
{
perror ("AUDIO_SET_EQUALIZER");
exit (-1);
}
else
fprintf(stderr,"AUDIO_SET_EQUALIZER called successfully\n");

/* See http://www.wolfsonmicro.jp/uploads/documents/jp/WM8971.pdf for
* details Not sure what we need to do here - the AUDIO_SET_EQUALIZER
* seems to do quite a bit, but nashville does this too - I just don't know
* which register it sets - this is a dummy - it is setting the audio input
* gain, which is not necessary for us here.
*/
reg.addr=1;
reg.value=255;
if (ioctl (fd, AUDIO_SET_WOLFSON_REG, &reg) == -1)
{
perror ("AUDIO_SET_WOLFSON_REG");
exit (-1);
}
else
fprintf(stderr,"AUDIO_SET_WOLFSON_REG called successfully\n");

unsigned long val = SWITCH_LO_ON|SWITCH_SP_ON;
fprintf(stderr,"val=%d\n",val);
if (ioctl (fd, AUDIO_SWITCH_OUTPUT, &val) == -1)
{
perror ("AUDIO_SWITCH_OUTPUT");
exit (-1);
}
else
fprintf(stderr,"AUDIO_SWITCH_OUTPUT called successfully\n");

if (ioctl (fd, AUDIO_SET_EQUALIZER, &eq) == -1)
{
perror ("AUDIO_SET_EQUALIZER");
exit (-1);
}
else
fprintf(stderr,"AUDIO_SET_EQUALIZER called successfully\n");


vol.outputs=VOLUME_OUTPUT_LO;
vol.volume=76;
if (ioctl (fd, AUDIO_SET_VOLUME, &vol) == -1)
{
perror ("AUDIO_SET_VOLUME");
exit (-1);
}
else
fprintf(stderr,"AUDIO_SET_VOLUME called successfully\n");

vol.outputs=VOLUME_OUTPUT_LO|VOLUME_OUTPUT_SP;
vol.volume=76;
if (ioctl (fd, AUDIO_SET_VOLUME, &vol) == -1)
{
perror ("AUDIO_SET_VOLUME");
exit (-1);
}
else
fprintf(stderr,"AUDIO_SET_VOLUME called successfully\n");



return fd;
}

/****************************************************
* Only change in main is to use /dev/audio rather than
* /dev/dsp in the OSS example.
******************************************************/
int
main (int argc, char *argv[])
{
char *name_out = "/dev/audio";

if (argc > 1)
name_out = argv[1];
fd_out = open_audio_device (name_out);

fprintf(stderr,"fd_out=%d\n",fd_out);
while (1)
write_sinewave ();

exit (0);
}

Jazid said...

Your work with the MusicPal is very interesting ... keep up the good work!

It will be very interesting to see what the outcome of all this research bears.

maz said...

Hi,

I like your researches. Very good, thanks! They helped me a lot in the beginning. Here are my researches with the /dev/audio device:

As NT mentioned, /dev/audio blocks until nashville plays something.
I run strace and followed the forks with -F parameter. Here is my part of code from the audio functions (I'm currently working on a console application).

The initialisation in audio_init() should open the device without blocking:

/*
###############
audio_arm.c:
###############
*/
#ifndef WIN32

#include < unistd.h>
#include < stdlib.h>
#include < stdio.h>
#include < sys/ioctl.h>
#include < fcntl.h>
#include < linux/soundcard.h>
#include "audio.h"
#include "audio_funcs.h"

static int audiodev = -1;
static int audiodev_wr = -1;

int audio_init(int with_defaults)
{
wm8971_reg reg;
equalizer_t eq;

if(audiodev != -1) return -2;

// Open device(s)
audiodev_wr = open(AUDIO_DEV_FILE, O_WRONLY, 0);
if(audiodev_wr == -1) return -1;

// http://www.wolfsonmicro.jp/uploads/documents/jp/WM8971.pdf

audiodev = open(AUDIO_DEV_FILE, O_RDWR, 0);
if(audiodev == -1) return -1;

//audio_hwreset();

// ???
ioctl(audiodev_wr, 0x7000, 0x01);

// other things, that strace -F showed:
reg.addr = 10;
reg.value = 8;
ioctl(audiodev_wr, AUDIO_SET_WOLFSON_REG, &reg);
reg.addr = 16;
reg.value = 32;
ioctl(audiodev_wr, AUDIO_SET_WOLFSON_REG, &reg);
reg.addr = 1;
reg.value = 23;
ioctl(audiodev_wr, AUDIO_SET_WOLFSON_REG, &reg);
reg.addr = 3;
reg.value = 23;

// >= 46 .. interesting...
ioctl(audiodev_wr, AUDIO_SET_WOLFSON_REG, &reg);
reg.addr = 46;
reg.value = 0;
ioctl(audiodev_wr, AUDIO_SET_WOLFSON_REG, &reg);
reg.addr = 62;
reg.value = 0;
ioctl(audiodev_wr, AUDIO_SET_WOLFSON_REG, &reg);
reg.addr = 64;
reg.value = 0;
ioctl(audiodev_wr, AUDIO_SET_WOLFSON_REG, &reg);
reg.addr = 66;
reg.value = 0;
ioctl(audiodev_wr, AUDIO_SET_WOLFSON_REG, &reg);
reg.addr = 69;
reg.value = 0;
ioctl(audiodev_wr, AUDIO_SET_WOLFSON_REG, &reg);
reg.addr = 70;
reg.value = 0;
ioctl(audiodev_wr, AUDIO_SET_WOLFSON_REG, &reg);
reg.addr = 72;
reg.value = 0;
ioctl(audiodev_wr, AUDIO_SET_WOLFSON_REG, &reg);
reg.addr = 75;
reg.value = 0;
ioctl(audiodev_wr, AUDIO_SET_WOLFSON_REG, &reg);
reg.addr = 53;
reg.value = 152;
ioctl(audiodev_wr, AUDIO_SET_WOLFSON_REG, &reg);

reg.addr = 16;
reg.value = 32;
ioctl(audiodev_wr, AUDIO_SET_WOLFSON_REG, &reg);
reg.addr = 10;
reg.value = 0;
ioctl(audiodev_wr, AUDIO_SET_WOLFSON_REG, &reg);

ioctl(audiodev_wr, AUDIO_RECORDPAUSE, 0x01);
ioctl(audiodev_wr, AUDIO_SET_STEREO, 0x01);
ioctl(audiodev_wr, AUDIO_WOLFSON_SET_CHANNEL, 0x02);
ioctl(audiodev_wr, AUDIO_SETRXBITPERSAMPLE, 0x10);
ioctl(audiodev_wr, AUDIO_SETTXBITPERSAMPLE, 0x10);
ioctl(audiodev_wr, AUDIO_WOLFSON_SET_FMT, 0x10);

if(with_defaults)
{
// Set samplingrate to 44100hz
audio_set_bitpersample(16);
audio_set_samplingrate(44100);

// Set equalizer
eq.bass = 0;
eq.treble = 0;
eq.dac = 255;
eq.flags = 0;
audio_set_equalizer(&eq);

// Switch all speakers "on"
audio_set_output(SWITCH_LO_ON | SWITCH_SP_ON);

// Set volume to 50% for both outputs
audio_set_volume_linear(VOLUME_OUTPUT_LO, 64);
audio_set_volume_linear(VOLUME_OUTPUT_SP, 64);
}

ioctl(audiodev_wr, AUDIO_PLAYBACKENABLEALL, 0x01);

return 0;
}

int audio_hwreset()
{
unsigned long val = 1;
if(audiodev == -1) return -1;
return ioctl(audiodev, AUDIO_IOC_HWRESET, &val);
}

int audio_set_equalizer(equalizer_t *eq)
{
if(audiodev == -1) return -1;
return ioctl(audiodev, AUDIO_SET_EQUALIZER, eq);
}

int audio_set_wolfson_reg(wm8971_reg *reg)
{
if(audiodev == -1) return -1;
return ioctl(audiodev, AUDIO_SET_WOLFSON_REG, reg);
}

int audio_get_wolfson_reg(wm8971_reg *reg)
{
if(audiodev == -1) return -1;
return ioctl(audiodev, AUDIO_GET_WOLFSON_REG, reg);
}

int audio_set_volume(volume_t *vol)
{
if(audiodev == -1) return -1;
return ioctl(audiodev, AUDIO_SET_VOLUME, vol);
}

int audio_set_volume_linear(unsigned char outputs, unsigned int volume)
{
volume_t vol;
int linear_volume = 0;

if(volume > 127) volume = 127;
if(volume < 1) volume = 1;

linear_volume = (int)(log((float)(volume))*(128.0/log(128.0)));

vol.outputs = outputs;
vol.volume = linear_volume;

return audio_set_volume(&vol);
}

int audio_set_all_on_off(int enable)
{
if(audiodev == -1) return -1;
if(enable)
return ioctl(audiodev, AUDIO_ALL_ON, NULL);
else
return ioctl(audiodev, AUDIO_ALL_OFF, NULL);
}

int audio_set_samplingrate(unsigned long rate)
{
if(audiodev == -1) return -1;
return ioctl(audiodev, AUDIO_SETSAMPLINGRATE, rate);
}

int audio_set_bitpersample(unsigned long bps)
{
if(audiodev == -1) return -1;
return ioctl(audiodev, AUDIO_SETTXBITPERSAMPLE, bps);
}

int audio_set_playback_on_off(int enable)
{
if(audiodev == -1) return -1;
return ioctl(audiodev, AUDIO_PLAYBACKENABLEALL, &enable);
}

int audio_set_output(int output)
{
if(audiodev == -1) return -1;
return ioctl(audiodev, AUDIO_SWITCH_OUTPUT, &output);
}

int audio_close()
{
if(audiodev != -1)
close(audiodev);

if(audiodev_wr != -1)
close(audiodev_wr);

audiodev = -1;
audiodev_wr = -1;

return 0;
}

#endif

/*
###############
audio_funcs.h:
###############
*/
#ifndef WIN32
#include "audio.h"

#define AUDIO_DEV_FILE "/dev/audio"

int audio_init(int with_defaults);
int audio_close();
int audio_hwreset();
int audio_get_wolfson_reg(wm8971_reg *reg);
int audio_set_wolfson_reg(wm8971_reg *reg);
int audio_set_equalizer(equalizer_t *eq);
int audio_set_volume_linear(unsigned char outputs, unsigned int volume);
int audio_set_volume(volume_t *vol);
int audio_set_all_on_off(int enable);
int audio_set_samplingrate(unsigned long rate);
int audio_set_bitpersample(unsigned long bps);
int audio_set_playback_on_off(int enable);
int audio_set_output(int output);

#else

// Output bits for volume
#define VOLUME_OUTPUT_HP 0x01
#define VOLUME_OUTPUT_SP 0x02
#define VOLUME_OUTPUT_LO 0x04

// Outputs
#define SWITCH_SP_ON 0x01
#define SWITCH_SP_OFF 0x00
#define SWITCH_LO_ON 0x02
#define SWITCH_LO_OFF 0x00
#define SWITCH_HP_ON 0x04
#define SWITCH_HP_OFF 0x00

int audio_init(int with_defaults) { return 0; }
int audio_close() { return 0; }
int audio_get_wolfson_reg(void *reg) { return 0; }
int audio_set_wolfson_reg(void *reg) { return 0; }
int audio_set_equalizer(void *eq) { return 0; }
int audio_hwreset() { return 0; }
int audio_set_volume_linear(unsigned char outputs, unsigned int volume) { return 0; }
int audio_set_volume(void *vol) { return 0; }
int audio_set_all_on_off(int enable) { return 0; }
int audio_set_samplingrate(unsigned long rate) { return 0; }
int audio_set_bitpersample(unsigned long bps) { return 0; }
int audio_set_playback_on_off(int enable) { return 0; }
int audio_set_output(int output) { return 0; }

#endif

#################

greets
Marco

NT said...

Wow - you have made much more progress than me - I saw all of that extra stuff in the strace output, but haven't had chance to try to decode it - it looks like you have it cracked, well done!

maz said...

Hi,

to decode the struct, you can write a preloaded library that overrides ioctl():

/*
##############
# preload.c
##############
*/
#include < stdio.h>
#include < stdlib.h>
#include < stdarg.h>
#include < dlfcn.h>
#include < time.h>
#include "linux/ioctl.h"
#include "audio.h"

static char * libc = "/lib/libc.so.6";

static void * dl;
static int (* org_ioctl)(int, unsigned long int, void*data);

int ioctl(int fd, int request, void *data)
{
int retval = -1;

if(org_ioctl == NULL)
{
dl = dlopen(libc, RTLD_NOW);
org_ioctl = dlsym(dl, "ioctl");
}

if(request == _IOW(AUDIO_IOC_MAGIC,11, unsigned long))
{
wm8971_reg *reg = (wm8971_reg*)data;
printf("fd: %d, req: %x, data: %x, addr: %d, val: %d\n", fd, request, data, reg->addr, reg->value);
}

return org_ioctl(fd, request, data);
}

#########################

Compile:
arm-linux-gcc -ldl -fPIC -shared -o preload.so preload.c

Run:
LD_PRELOAD=/tmp/preload.so /bin/nashville

NT said...

Now that is impressive - I didn't know you could do that - This looks like a method of overriding any function in a shared library isn't it? This means that we can work out what the nashville application is doing to set-up the hardware, which will be handy.

I will get back to thinking about the MusicPal soon - just got a couple of other jobs to do first......

maz said...

Hi NT,

it's me again ;-)

I think I wouldn't get so far, without your researches.

I've created a Mod for the MusicPal, that can be downloaded here: PalMod
OpenSource of course ...

MP3 ripping is the only thing supported at the moment. But it's possible to run custom applications and nashville at the same time.

Modifications to the PalMod menu can be done in /home/palmod.cfg.

cheers!
Marco

NT said...

Well done - I'll have a play with that.
I put links to your work and that of giles above in a separate post so they do not get lost.