Ticket #3101: patch-audio_macosx.diff

File patch-audio_macosx.diff, 9.2 KB (added by sailgreg@…, 19 years ago)

mpg123 OS X audio driver

  • audio_macosx.

    old new  
     1/*- This is a 80 chars line, to have pretty formatted comments ---------------*/
     2
     3/* audio_macosx.c, originally written by Guillaume Outters
     4 * to contact the author, please mail to: guillaume.outters@free.fr
     5 *
     6 * This file is some quick pre-alpha patch to allow me to have some music for my
     7 * long working days, and it does it well. But it surely isn't a final version;
     8 * as Mac OS X requires at least a G3, I'm not sure it will be useful to
     9 * implement downsampling.
     10 *
     11 * Mac OS X audio works by asking you to fill its buffer, and, to complicate a
     12 * bit, you must provide it with floats. In order not to patch too much mpg123,
     13 * we'll accept signed short (mpg123 "approved" format) and transform them into
     14 * floats as soon as received. Let's say this way calculations are faster.
     15 *
     16 * As we don't have some /dev/audio device with blocking write, we'll have to
     17 * stop mpg123 before it does too much work, while we are waiting our dump proc
     18 * to be called. I wanted to use semaphores, but they still need an
     19 * implementation from Apple before I can do anything. So we'll block using a
     20 * sleep and wake up on SIGUSR2.
     21 * Version 0.2: now I use named semaphores (which are implemented AND work).
     22 * Preprocessor flag MOSX_USES_SEM (defined at the beginning of this file)
     23 * enables this behaviour.
     24 *
     25 * In order always to have a ready buffer to be dumped when the routine gets
     26 * called, we have a "buffer loop" of NUMBER_BUFFERS buffers. mpg123 fills it
     27 * on one extremity ('to'), playProc reads it on another point ('from'). 'to'
     28 * blocks when it arrives on 'from' (having filled the whole circle of buffers)
     29 * and 'from' blocks when no data is available. As soon as it has emptied a
     30 * buffer, if mpg123 is sleeping, it awakes it quite brutaly (SIGUSR2) to tell
     31 * it to fill the buffer. */
     32
     33#ifndef MOSX_USES_SEM
     34#define MOSX_USES_SEM 1 /* Semaphores or sleep()/kill()? I would say semaphores, but this is just my advice, after all */
     35#endif
     36#ifndef MOSX_SEM_V2
     37#define MOSX_SEM_V2 1
     38#endif
     39
     40#include "mpg123.h"
     41#include <CoreAudio/AudioHardware.h>
     42#include <stdio.h>
     43#include <stdlib.h>
     44#include <errno.h>
     45#if MOSX_USES_SEM
     46#include <semaphore.h>
     47#endif
     48
     49struct aBuffer
     50{
     51        float * buffer;
     52        long size;
     53       
     54        float * ptr;    /* Where in the buffer are we? */
     55        long remaining;
     56       
     57        struct aBuffer * next;
     58};
     59typedef struct aBuffer aBuffer;
     60
     61struct anEnv
     62{
     63        long size;
     64        short * debut;
     65        short * ptr;
     66        AudioDeviceID device;
     67        char play;
     68       
     69        /* Intermediate buffers */
     70       
     71        #if MOSX_USES_SEM
     72        sem_t * semaphore;
     73        #else
     74        char wait;      /* mpg123 is waiting (due to the semaphore) to show the world its power; let's free him! */
     75        pid_t pid;
     76        #endif
     77        aBuffer * from; /* Current buffers */
     78        aBuffer * to;
     79};
     80
     81static struct anEnv env;
     82
     83#define ENV ((struct anEnv *)inClientData)
     84#define NUMBER_BUFFERS 16       /* Tried with 3 buffers, but then any little window move is sufficient to stop the sound. Here we have 1.5 seconds music buffered */
     85
     86void destroyBuffers()
     87{
     88        aBuffer * ptr;
     89        aBuffer * ptr2;
     90       
     91        ptr = env.to->next;
     92        env.to->next = NULL;
     93        while(ptr)
     94        {
     95                ptr2 = ptr->next;
     96                if(ptr->buffer) free(ptr->buffer);
     97                free(ptr);
     98                ptr = ptr2;
     99        }
     100}
     101
     102void initBuffers()
     103{
     104        long m;
     105        aBuffer ** ptrptr;
     106       
     107        ptrptr = &env.to;
     108        for(m = 0; m < NUMBER_BUFFERS; m++)
     109        {
     110                *ptrptr = malloc(sizeof(aBuffer));
     111                (*ptrptr)->size = 0;
     112                (*ptrptr)->remaining = 0;
     113                (*ptrptr)->buffer = NULL;
     114                ptrptr = &(*ptrptr)->next;
     115                #if MOSX_USES_SEM
     116                sem_post(env.semaphore);        /* This buffer is ready for filling (of course, it is empty!) */
     117                #endif
     118        }
     119        *ptrptr = env.from = env.to;
     120}
     121
     122int fillBuffer(aBuffer * b, short * source, long size)
     123{
     124        float * dest;
     125       
     126        if(b->remaining)        /* Non empty buffer, must still be playing */
     127                return(-1);
     128        if(b->size != size)     /* Hey! What's that? Coudn't this buffer size be fixed once (well, perhaps we just didn't allocate it yet) */
     129        {
     130                if(b->buffer) free(b->buffer);
     131                b->buffer = malloc(size * sizeof(float));
     132                b->size = size;
     133        }
     134       
     135        dest = b->buffer;
     136        while(size--)
     137                //*dest++ = ((*source++) + 32768) / 65536.0;
     138                *dest++ = (*source++) / 32768.0;
     139       
     140        b->ptr = b->buffer;
     141        b->remaining = b->size; /* Do this at last; we shouldn't show the buffer is full before it is effectively */
     142       
     143        #ifdef DEBUG_MOSX
     144        printf("."); fflush(stdout);
     145        #endif
     146       
     147        return(0);
     148}
     149
     150OSStatus playProc(AudioDeviceID inDevice, const AudioTimeStamp * inNow, const AudioBufferList * inInputData, const AudioTimeStamp * inInputTime, AudioBufferList * outOutputData, const AudioTimeStamp * inOutputTime, void * inClientData)
     151{
     152        long m, n, o;
     153        float * dest;
     154       
     155        for(o = 0; o < outOutputData->mNumberBuffers; o++)
     156        {
     157                m = outOutputData->mBuffers[o].mDataByteSize / sizeof(float);   /* What we have to fill */
     158                dest = outOutputData->mBuffers[o].mData;
     159               
     160                while(m > 0)
     161                {
     162                        if( (n = ENV->from->remaining) <= 0 )   /* No more bytes in the current read buffer! */
     163                        {
     164                                while( (n = ENV->from->remaining) <= 0)
     165                                        usleep(2000);   /* Let's wait a bit for the results... */
     166                        }
     167                                               
     168                        /* We dump what we can */
     169                       
     170                        if(n > m) n = m;        /* In fact, just the necessary should be sufficient (I think) */
     171                       
     172                        memcpy(dest, ENV->from->ptr, n * sizeof(float));
     173                       
     174                        /* Let's remember all done work */
     175                       
     176                        m -= n;
     177                        ENV->from->ptr += n;
     178                        if( (ENV->from->remaining -= n) <= 0)   /* ... and tell mpg123 there's a buffer to fill */
     179                        {
     180                                #if MOSX_USES_SEM
     181                                sem_post(ENV->semaphore);
     182                                #else
     183                                if(ENV->wait)
     184                                {
     185                                        kill(ENV->pid, SIGUSR2);
     186                                        ENV->wait = 0;
     187                                }
     188                                #endif
     189                                ENV->from = ENV->from->next;
     190                        }
     191                }
     192        }
     193       
     194        return (0);
     195}
     196
     197#if ! MOSX_USES_SEM
     198void start(int n)
     199{
     200        signal(SIGUSR2, start);
     201}
     202#endif
     203
     204int audio_open(struct audio_info_struct *ai)
     205{
     206        long size;
     207        AudioStreamBasicDescription format;
     208        #if MOSX_USES_SEM
     209        char s[10];
     210        long m;
     211        #endif
     212        /*float vol;
     213        OSStatus e;*/
     214       
     215        /* Where did that default audio output go? */
     216       
     217        size = sizeof(env.device);
     218        if(AudioHardwareGetProperty(kAudioHardwarePropertyDefaultOutputDevice, &size, &env.device)) return(-1);
     219               
     220        /* Hmmm, let's choose PCM format */
     221       
     222        size = sizeof(format);
     223        if(AudioDeviceGetProperty(env.device, 0, 0, kAudioDevicePropertyStreamFormat, &size, &format)) return(-1);
     224        if(format.mFormatID != kAudioFormatLinearPCM) return(-1);
     225       
     226        /* Let's test volume, which doesn't seem to work (but this is only an alpha, remember?) */
     227       
     228        /*vol = 0.5;
     229        size = sizeof(vol);
     230        if(e = AudioDeviceSetProperty(env.device, NULL, 0, 0, 'volm', size, &vol)) { printf("Didn't your mother ever tell you not to hear music so loudly?\n"); return(-1); } */
     231       
     232        /* Let's init our environment */
     233       
     234        env.size = 0;
     235        env.debut = NULL;
     236        env.ptr = NULL;
     237        env.play = 0;
     238       
     239        #if MOSX_USES_SEM
     240        strcpy(s, "/mpg123-0000");
     241        do
     242        {
     243                for(m = 10;; m--)
     244                        if( (s[m]++) <= '9')
     245                                break;
     246                        else
     247                                s[m] = '0';
     248        } while( (env.semaphore = sem_open(s, O_CREAT | O_EXCL, 0644, 0)) == (sem_t *)SEM_FAILED);
     249        #else
     250        env.pid = getpid();
     251        env.wait = 0;
     252        signal(SIGUSR2, start);
     253        #endif
     254       
     255        initBuffers();
     256               
     257        /* And prepare audio launching */
     258       
     259        if(AudioDeviceAddIOProc(env.device, playProc, &env)) return(-1);
     260       
     261        return(0);
     262}
     263
     264int audio_reset_parameters(struct audio_info_struct *ai)
     265{
     266        return 0;
     267}
     268
     269int audio_rate_best_match(struct audio_info_struct *ai)
     270{
     271        return 0;
     272}
     273
     274int audio_set_rate(struct audio_info_struct *ai)
     275{
     276        return 0;
     277}
     278
     279int audio_set_channels(struct audio_info_struct *ai)
     280{
     281        return 0;
     282}
     283
     284int audio_set_format(struct audio_info_struct *ai)
     285{
     286        return 0;
     287}
     288
     289int audio_get_formats(struct audio_info_struct *ai)
     290{
     291        return AUDIO_FORMAT_SIGNED_16;
     292}
     293
     294int audio_play_samples(struct audio_info_struct *ai,unsigned char *buf,int len)
     295{
     296        /* We have to calm down mpg123, else he wouldn't hesitate to drop us another buffer (which would be the same, in fact) */
     297       
     298        #if MOSX_USES_SEM && MOSX_SEM_V2        /* Suddenly, I have some kind of doubt: HOW COULD IT WORK??? */
     299        while(sem_wait(env.semaphore)){}        /* We just have to wait a buffer fill request */
     300        fillBuffer(env.to, (short *)buf, len / sizeof(short));
     301        #else
     302        while(fillBuffer(env.to, (short *)buf, len / sizeof(short)) < 0)        /* While the next buffer to write is not empty, we wait a bit... */
     303        {
     304                #ifdef DEBUG_MOSX
     305                printf("|"); fflush(stdout);
     306                #endif
     307                #if MOSX_USES_SEM
     308                sem_wait(env.semaphore);        /* This is a bug. I should wait for the semaphore once per empty buffer (and not each time fillBuffers() returns -1). See MOSX_SEM_V2; this was a too quickly modified part when I implemented MOSX_USES_SEM. */
     309                #else
     310                env.wait = 1;
     311                sleep(3600);    /* This should be sufficient, shouldn't it? */
     312                #endif
     313        }
     314        #endif
     315        env.to = env.to->next;
     316       
     317        /* And we lauch action if not already done */
     318       
     319        if(!env.play)
     320        {
     321                if(AudioDeviceStart(env.device, playProc)) return(-1);
     322                env.play = 1;
     323        }
     324       
     325        return len;
     326}
     327
     328int audio_close(struct audio_info_struct *ai)
     329{
     330        AudioDeviceStop(env.device, playProc);  /* No matter the error code, we want to close it (by brute force if necessary) */
     331        AudioDeviceRemoveIOProc(env.device, playProc);
     332        destroyBuffers();
     333        #if MOSX_USES_SEM
     334        sem_close(env.semaphore);
     335        #endif
     336        return 0;
     337}