1 /*
   2  * Copyright (c) 2003, 2007, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 #define USE_ERROR
  27 #define USE_TRACE
  28 
  29 #include "PLATFORM_API_SolarisOS_Utils.h"
  30 #include "DirectAudio.h"
  31 
  32 #if USE_DAUDIO == TRUE
  33 
  34 
  35 // The default buffer time
  36 #define DEFAULT_PERIOD_TIME_MILLIS 50
  37 
  38 ///// implemented functions of DirectAudio.h
  39 
  40 INT32 DAUDIO_GetDirectAudioDeviceCount() {
  41     return (INT32) getAudioDeviceCount();
  42 }
  43 
  44 
  45 INT32 DAUDIO_GetDirectAudioDeviceDescription(INT32 mixerIndex,
  46                                              DirectAudioDeviceDescription* description) {
  47     AudioDeviceDescription desc;
  48 
  49     if (getAudioDeviceDescriptionByIndex(mixerIndex, &desc, TRUE)) {
  50         description->maxSimulLines = desc.maxSimulLines;
  51         strncpy(description->name, desc.name, DAUDIO_STRING_LENGTH-1);
  52         description->name[DAUDIO_STRING_LENGTH-1] = 0;
  53         strncpy(description->vendor, desc.vendor, DAUDIO_STRING_LENGTH-1);
  54         description->vendor[DAUDIO_STRING_LENGTH-1] = 0;
  55         strncpy(description->version, desc.version, DAUDIO_STRING_LENGTH-1);
  56         description->version[DAUDIO_STRING_LENGTH-1] = 0;
  57         /*strncpy(description->description, desc.description, DAUDIO_STRING_LENGTH-1);*/
  58         strncpy(description->description, "Solaris Mixer", DAUDIO_STRING_LENGTH-1);
  59         description->description[DAUDIO_STRING_LENGTH-1] = 0;
  60         return TRUE;
  61     }
  62     return FALSE;
  63 
  64 }
  65 
  66 #define MAX_SAMPLE_RATES   20
  67 
  68 void DAUDIO_GetFormats(INT32 mixerIndex, INT32 deviceID, int isSource, void* creator) {
  69     int fd = -1;
  70     AudioDeviceDescription desc;
  71     am_sample_rates_t      *sr;
  72     /* hardcoded bits and channels */
  73     int bits[] = {8, 16};
  74     int bitsCount = 2;
  75     int channels[] = {1, 2};
  76     int channelsCount = 2;
  77     /* for querying sample rates */
  78     int err;
  79     int ch, b, s;
  80 
  81     TRACE2("DAUDIO_GetFormats, mixer %d, isSource=%d\n", mixerIndex, isSource);
  82     if (getAudioDeviceDescriptionByIndex(mixerIndex, &desc, FALSE)) {
  83         fd = open(desc.pathctl, O_RDONLY);
  84     }
  85     if (fd < 0) {
  86         ERROR1("Couldn't open audio device ctl for device %d!\n", mixerIndex);
  87         return;
  88     }
  89 
  90     /* get sample rates */
  91     sr = (am_sample_rates_t*) malloc(AUDIO_MIXER_SAMP_RATES_STRUCT_SIZE(MAX_SAMPLE_RATES));
  92     if (sr == NULL) {
  93         ERROR1("DAUDIO_GetFormats: out of memory for mixer %d\n", (int) mixerIndex);
  94         close(fd);
  95         return;
  96     }
  97 
  98     sr->num_samp_rates = MAX_SAMPLE_RATES;
  99     sr->type = isSource?AUDIO_PLAY:AUDIO_RECORD;
 100     sr->samp_rates[0] = -2;
 101     err = ioctl(fd, AUDIO_MIXER_GET_SAMPLE_RATES, sr);
 102     if (err < 0) {
 103         ERROR1("  DAUDIO_GetFormats: AUDIO_MIXER_GET_SAMPLE_RATES failed for mixer %d!\n",
 104                (int)mixerIndex);
 105         ERROR2(" -> num_sample_rates=%d sample_rates[0] = %d\n",
 106                (int) sr->num_samp_rates,
 107                (int) sr->samp_rates[0]);
 108         /* Some Solaris 8 drivers fail for get sample rates!
 109          * Do as if we support all sample rates
 110          */
 111         sr->flags = MIXER_SR_LIMITS;
 112     }
 113     if ((sr->flags & MIXER_SR_LIMITS)
 114         || (sr->num_samp_rates > MAX_SAMPLE_RATES)) {
 115 #ifdef USE_TRACE
 116         if ((sr->flags & MIXER_SR_LIMITS)) {
 117             TRACE1("  DAUDIO_GetFormats: floating sample rate allowed by mixer %d\n",
 118                    (int)mixerIndex);
 119         }
 120         if (sr->num_samp_rates > MAX_SAMPLE_RATES) {
 121             TRACE2("  DAUDIO_GetFormats: more than %d formats. Use -1 for sample rates mixer %d\n",
 122                    MAX_SAMPLE_RATES, (int)mixerIndex);
 123         }
 124 #endif
 125         /*
 126          * Fake it to have only one sample rate: -1
 127          */
 128         sr->num_samp_rates = 1;
 129         sr->samp_rates[0] = -1;
 130     }
 131     close(fd);
 132 
 133     for (ch = 0; ch < channelsCount; ch++) {
 134         for (b = 0; b < bitsCount; b++) {
 135             for (s = 0; s < sr->num_samp_rates; s++) {
 136                 DAUDIO_AddAudioFormat(creator,
 137                                       bits[b], /* significant bits */
 138                                       0, /* frameSize: let it be calculated */
 139                                       channels[ch],
 140                                       (float) ((int) sr->samp_rates[s]),
 141                                       DAUDIO_PCM, /* encoding - let's only do PCM */
 142                                       (bits[b] > 8)?TRUE:TRUE, /* isSigned */
 143 #ifdef _LITTLE_ENDIAN
 144                                       FALSE /* little endian */
 145 #else
 146                                       (bits[b] > 8)?TRUE:FALSE  /* big endian */
 147 #endif
 148                                       );
 149             }
 150         }
 151     }
 152     free(sr);
 153 }
 154 
 155 
 156 typedef struct {
 157     int fd;
 158     audio_info_t info;
 159     int bufferSizeInBytes;
 160     int frameSize; /* storage size in Bytes */
 161     /* how many bytes were written or read */
 162     INT32 transferedBytes;
 163     /* if transferedBytes exceed 32-bit boundary,
 164      * it will be reset and positionOffset will receive
 165      * the offset
 166      */
 167     INT64 positionOffset;
 168 } SolPcmInfo;
 169 
 170 
 171 void* DAUDIO_Open(INT32 mixerIndex, INT32 deviceID, int isSource,
 172                   int encoding, float sampleRate, int sampleSizeInBits,
 173                   int frameSize, int channels,
 174                   int isSigned, int isBigEndian, int bufferSizeInBytes) {
 175     int err = 0;
 176     int openMode;
 177     AudioDeviceDescription desc;
 178     SolPcmInfo* info;
 179 
 180     TRACE0("> DAUDIO_Open\n");
 181     if (encoding != DAUDIO_PCM) {
 182         ERROR1(" DAUDIO_Open: invalid encoding %d\n", (int) encoding);
 183         return NULL;
 184     }
 185 
 186     info = (SolPcmInfo*) malloc(sizeof(SolPcmInfo));
 187     if (!info) {
 188         ERROR0("Out of memory\n");
 189         return NULL;
 190     }
 191     memset(info, 0, sizeof(SolPcmInfo));
 192     info->frameSize = frameSize;
 193     info->fd = -1;
 194 
 195     if (isSource) {
 196         openMode = O_WRONLY;
 197     } else {
 198         openMode = O_RDONLY;
 199     }
 200 
 201 #ifndef __linux__
 202     /* blackdown does not use NONBLOCK */
 203     openMode |= O_NONBLOCK;
 204 #endif
 205 
 206     if (getAudioDeviceDescriptionByIndex(mixerIndex, &desc, FALSE)) {
 207         info->fd = open(desc.path, openMode);
 208     }
 209     if (info->fd < 0) {
 210         ERROR1("Couldn't open audio device for mixer %d!\n", mixerIndex);
 211         free(info);
 212         return NULL;
 213     }
 214     /* set to multiple open */
 215     if (ioctl(info->fd, AUDIO_MIXER_MULTIPLE_OPEN, NULL) >= 0) {
 216         TRACE1("DAUDIO_Open: %s set to multiple open\n", desc.path);
 217     } else {
 218         ERROR1("DAUDIO_Open: ioctl AUDIO_MIXER_MULTIPLE_OPEN failed on %s!\n", desc.path);
 219     }
 220 
 221     AUDIO_INITINFO(&(info->info));
 222     /* need AUDIO_GETINFO ioctl to get this to work on solaris x86  */
 223     err = ioctl(info->fd, AUDIO_GETINFO, &(info->info));
 224 
 225     /* not valid to call AUDIO_SETINFO ioctl with all the fields from AUDIO_GETINFO. */
 226     AUDIO_INITINFO(&(info->info));
 227 
 228     if (isSource) {
 229         info->info.play.sample_rate = sampleRate;
 230         info->info.play.precision = sampleSizeInBits;
 231         info->info.play.channels = channels;
 232         info->info.play.encoding = AUDIO_ENCODING_LINEAR;
 233         info->info.play.buffer_size = bufferSizeInBytes;
 234         info->info.play.pause = 1;
 235     } else {
 236         info->info.record.sample_rate = sampleRate;
 237         info->info.record.precision = sampleSizeInBits;
 238         info->info.record.channels = channels;
 239         info->info.record.encoding = AUDIO_ENCODING_LINEAR;
 240         info->info.record.buffer_size = bufferSizeInBytes;
 241         info->info.record.pause = 1;
 242     }
 243     err = ioctl(info->fd, AUDIO_SETINFO,  &(info->info));
 244     if (err < 0) {
 245         ERROR0("DAUDIO_Open: could not set info!\n");
 246         DAUDIO_Close((void*) info, isSource);
 247         return NULL;
 248     }
 249     DAUDIO_Flush((void*) info, isSource);
 250 
 251     err = ioctl(info->fd, AUDIO_GETINFO, &(info->info));
 252     if (err >= 0) {
 253         if (isSource) {
 254             info->bufferSizeInBytes = info->info.play.buffer_size;
 255         } else {
 256             info->bufferSizeInBytes = info->info.record.buffer_size;
 257         }
 258         TRACE2("DAUDIO: buffersize in bytes: requested=%d, got %d\n",
 259                (int) bufferSizeInBytes,
 260                (int) info->bufferSizeInBytes);
 261     } else {
 262         ERROR0("DAUDIO_Open: cannot get info!\n");
 263         DAUDIO_Close((void*) info, isSource);
 264         return NULL;
 265     }
 266     TRACE0("< DAUDIO_Open: Opened device successfully.\n");
 267     return (void*) info;
 268 }
 269 
 270 
 271 int DAUDIO_Start(void* id, int isSource) {
 272     SolPcmInfo* info = (SolPcmInfo*) id;
 273     int err, modified;
 274     audio_info_t audioInfo;
 275 
 276     TRACE0("> DAUDIO_Start\n");
 277 
 278     AUDIO_INITINFO(&audioInfo);
 279     err = ioctl(info->fd, AUDIO_GETINFO, &audioInfo);
 280     if (err >= 0) {
 281         // unpause
 282         modified = FALSE;
 283         if (isSource && audioInfo.play.pause) {
 284             audioInfo.play.pause = 0;
 285             modified = TRUE;
 286         }
 287         if (!isSource && audioInfo.record.pause) {
 288             audioInfo.record.pause = 0;
 289             modified = TRUE;
 290         }
 291         if (modified) {
 292             err = ioctl(info->fd, AUDIO_SETINFO, &audioInfo);
 293         }
 294     }
 295 
 296     TRACE1("< DAUDIO_Start %s\n", (err>=0)?"success":"error");
 297     return (err >= 0)?TRUE:FALSE;
 298 }
 299 
 300 int DAUDIO_Stop(void* id, int isSource) {
 301     SolPcmInfo* info = (SolPcmInfo*) id;
 302     int err, modified;
 303     audio_info_t audioInfo;
 304 
 305     TRACE0("> DAUDIO_Stop\n");
 306 
 307     AUDIO_INITINFO(&audioInfo);
 308     err = ioctl(info->fd, AUDIO_GETINFO, &audioInfo);
 309     if (err >= 0) {
 310         // pause
 311         modified = FALSE;
 312         if (isSource && !audioInfo.play.pause) {
 313             audioInfo.play.pause = 1;
 314             modified = TRUE;
 315         }
 316         if (!isSource && !audioInfo.record.pause) {
 317             audioInfo.record.pause = 1;
 318             modified = TRUE;
 319         }
 320         if (modified) {
 321             err = ioctl(info->fd, AUDIO_SETINFO, &audioInfo);
 322         }
 323     }
 324 
 325     TRACE1("< DAUDIO_Stop %s\n", (err>=0)?"success":"error");
 326     return (err >= 0)?TRUE:FALSE;
 327 }
 328 
 329 void DAUDIO_Close(void* id, int isSource) {
 330     SolPcmInfo* info = (SolPcmInfo*) id;
 331 
 332     TRACE0("DAUDIO_Close\n");
 333     if (info != NULL) {
 334         if (info->fd >= 0) {
 335             DAUDIO_Flush(id, isSource);
 336             close(info->fd);
 337         }
 338         free(info);
 339     }
 340 }
 341 
 342 #ifndef USE_TRACE
 343 /* close to 2^31 */
 344 #define POSITION_MAX 2000000000
 345 #else
 346 /* for testing */
 347 #define POSITION_MAX 1000000
 348 #endif
 349 
 350 void resetErrorFlagAndAdjustPosition(SolPcmInfo* info, int isSource, int count) {
 351     audio_info_t audioInfo;
 352     audio_prinfo_t* prinfo;
 353     int err;
 354     int offset = -1;
 355     int underrun = FALSE;
 356     int devBytes = 0;
 357 
 358     if (count > 0) {
 359         info->transferedBytes += count;
 360 
 361         if (isSource) {
 362             prinfo = &(audioInfo.play);
 363         } else {
 364             prinfo = &(audioInfo.record);
 365         }
 366         AUDIO_INITINFO(&audioInfo);
 367         err = ioctl(info->fd, AUDIO_GETINFO, &audioInfo);
 368         if (err >= 0) {
 369             underrun = prinfo->error;
 370             devBytes = prinfo->samples * info->frameSize;
 371         }
 372         AUDIO_INITINFO(&audioInfo);
 373         if (underrun) {
 374             /* if an underrun occured, reset */
 375             ERROR1("DAUDIO_Write/Read: Underrun/overflow: adjusting positionOffset by %d:\n",
 376                    (devBytes - info->transferedBytes));
 377             ERROR1("    devBytes from %d to 0, ", devBytes);
 378             ERROR2(" positionOffset from %d to %d ",
 379                    (int) info->positionOffset,
 380                    (int) (info->positionOffset + info->transferedBytes));
 381             ERROR1(" transferedBytes from %d to 0\n",
 382                    (int) info->transferedBytes);
 383             prinfo->samples = 0;
 384             info->positionOffset += info->transferedBytes;
 385             info->transferedBytes = 0;
 386         }
 387         else if (info->transferedBytes > POSITION_MAX) {
 388             /* we will reset transferedBytes and
 389              * the samples field in prinfo
 390              */
 391             offset = devBytes;
 392             prinfo->samples = 0;
 393         }
 394         /* reset error flag */
 395         prinfo->error = 0;
 396 
 397         err = ioctl(info->fd, AUDIO_SETINFO, &audioInfo);
 398         if (err >= 0) {
 399             if (offset > 0) {
 400                 /* upon exit of AUDIO_SETINFO, the samples parameter
 401                  * was set to the previous value. This is our
 402                  * offset.
 403                  */
 404                 TRACE1("Adjust samplePos: offset=%d, ", (int) offset);
 405                 TRACE2("transferedBytes=%d -> %d, ",
 406                        (int) info->transferedBytes,
 407                        (int) (info->transferedBytes - offset));
 408                 TRACE2("positionOffset=%d -> %d\n",
 409                        (int) (info->positionOffset),
 410                        (int) (((int) info->positionOffset) + offset));
 411                 info->transferedBytes -= offset;
 412                 info->positionOffset += offset;
 413             }
 414         } else {
 415             ERROR0("DAUDIO: resetErrorFlagAndAdjustPosition ioctl failed!\n");
 416         }
 417     }
 418 }
 419 
 420 // returns -1 on error
 421 int DAUDIO_Write(void* id, char* data, int byteSize) {
 422     SolPcmInfo* info = (SolPcmInfo*) id;
 423     int ret = -1;
 424 
 425     TRACE1("> DAUDIO_Write %d bytes\n", byteSize);
 426     if (info!=NULL) {
 427         ret = write(info->fd, data, byteSize);
 428         resetErrorFlagAndAdjustPosition(info, TRUE, ret);
 429         /* sets ret to -1 if buffer full, no error! */
 430         if (ret < 0) {
 431             ret = 0;
 432         }
 433     }
 434     TRACE1("< DAUDIO_Write: returning %d bytes.\n", ret);
 435     return ret;
 436 }
 437 
 438 // returns -1 on error
 439 int DAUDIO_Read(void* id, char* data, int byteSize) {
 440     SolPcmInfo* info = (SolPcmInfo*) id;
 441     int ret = -1;
 442 
 443     TRACE1("> DAUDIO_Read %d bytes\n", byteSize);
 444     if (info != NULL) {
 445         ret = read(info->fd, data, byteSize);
 446         resetErrorFlagAndAdjustPosition(info, TRUE, ret);
 447         /* sets ret to -1 if buffer full, no error! */
 448         if (ret < 0) {
 449             ret = 0;
 450         }
 451     }
 452     TRACE1("< DAUDIO_Read: returning %d bytes.\n", ret);
 453     return ret;
 454 }
 455 
 456 
 457 int DAUDIO_GetBufferSize(void* id, int isSource) {
 458     SolPcmInfo* info = (SolPcmInfo*) id;
 459     if (info) {
 460         return info->bufferSizeInBytes;
 461     }
 462     return 0;
 463 }
 464 
 465 int DAUDIO_StillDraining(void* id, int isSource) {
 466     SolPcmInfo* info = (SolPcmInfo*) id;
 467     audio_info_t audioInfo;
 468     audio_prinfo_t* prinfo;
 469     int ret = FALSE;
 470 
 471     if (info!=NULL) {
 472         if (isSource) {
 473             prinfo = &(audioInfo.play);
 474         } else {
 475             prinfo = &(audioInfo.record);
 476         }
 477         /* check error flag */
 478         AUDIO_INITINFO(&audioInfo);
 479         ioctl(info->fd, AUDIO_GETINFO, &audioInfo);
 480         ret = (prinfo->error != 0)?FALSE:TRUE;
 481     }
 482     return ret;
 483 }
 484 
 485 
 486 int getDevicePosition(SolPcmInfo* info, int isSource) {
 487     audio_info_t audioInfo;
 488     audio_prinfo_t* prinfo;
 489     int err;
 490 
 491     if (isSource) {
 492         prinfo = &(audioInfo.play);
 493     } else {
 494         prinfo = &(audioInfo.record);
 495     }
 496     AUDIO_INITINFO(&audioInfo);
 497     err = ioctl(info->fd, AUDIO_GETINFO, &audioInfo);
 498     if (err >= 0) {
 499         /*TRACE2("---> device paused: %d  eof=%d\n",
 500                prinfo->pause, prinfo->eof);
 501         */
 502         return (int) (prinfo->samples * info->frameSize);
 503     }
 504     ERROR0("DAUDIO: getDevicePosition: ioctl failed!\n");
 505     return -1;
 506 }
 507 
 508 int DAUDIO_Flush(void* id, int isSource) {
 509     SolPcmInfo* info = (SolPcmInfo*) id;
 510     int err = -1;
 511     int pos;
 512 
 513     TRACE0("DAUDIO_Flush\n");
 514     if (info) {
 515         if (isSource) {
 516             err = ioctl(info->fd, I_FLUSH, FLUSHW);
 517         } else {
 518             err = ioctl(info->fd, I_FLUSH, FLUSHR);
 519         }
 520         if (err >= 0) {
 521             /* resets the transferedBytes parameter to
 522              * the current samples count of the device
 523              */
 524             pos = getDevicePosition(info, isSource);
 525             if (pos >= 0) {
 526                 info->transferedBytes = pos;
 527             }
 528         }
 529     }
 530     if (err < 0) {
 531         ERROR0("ERROR in DAUDIO_Flush\n");
 532     }
 533     return (err < 0)?FALSE:TRUE;
 534 }
 535 
 536 int DAUDIO_GetAvailable(void* id, int isSource) {
 537     SolPcmInfo* info = (SolPcmInfo*) id;
 538     int ret = 0;
 539     int pos;
 540 
 541     if (info) {
 542         /* unfortunately, the STREAMS architecture
 543          * seems to not have a method for querying
 544          * the available bytes to read/write!
 545          * estimate it...
 546          */
 547         pos = getDevicePosition(info, isSource);
 548         if (pos >= 0) {
 549             if (isSource) {
 550                 /* we usually have written more bytes
 551                  * to the queue than the device position should be
 552                  */
 553                 ret = (info->bufferSizeInBytes) - (info->transferedBytes - pos);
 554             } else {
 555                 /* for record, the device stream should
 556                  * be usually ahead of our read actions
 557                  */
 558                 ret = pos - info->transferedBytes;
 559             }
 560             if (ret > info->bufferSizeInBytes) {
 561                 ERROR2("DAUDIO_GetAvailable: available=%d, too big at bufferSize=%d!\n",
 562                        (int) ret, (int) info->bufferSizeInBytes);
 563                 ERROR2("                     devicePos=%d, transferedBytes=%d\n",
 564                        (int) pos, (int) info->transferedBytes);
 565                 ret = info->bufferSizeInBytes;
 566             }
 567             else if (ret < 0) {
 568                 ERROR1("DAUDIO_GetAvailable: available=%d, in theory not possible!\n",
 569                        (int) ret);
 570                 ERROR2("                     devicePos=%d, transferedBytes=%d\n",
 571                        (int) pos, (int) info->transferedBytes);
 572                 ret = 0;
 573             }
 574         }
 575     }
 576 
 577     TRACE1("DAUDIO_GetAvailable returns %d bytes\n", ret);
 578     return ret;
 579 }
 580 
 581 INT64 DAUDIO_GetBytePosition(void* id, int isSource, INT64 javaBytePos) {
 582     SolPcmInfo* info = (SolPcmInfo*) id;
 583     int ret;
 584     int pos;
 585     INT64 result = javaBytePos;
 586 
 587     if (info) {
 588         pos = getDevicePosition(info, isSource);
 589         if (pos >= 0) {
 590             result = info->positionOffset + pos;
 591         }
 592     }
 593 
 594     //printf("getbyteposition: javaBytePos=%d , return=%d\n", (int) javaBytePos, (int) result);
 595     return result;
 596 }
 597 
 598 
 599 void DAUDIO_SetBytePosition(void* id, int isSource, INT64 javaBytePos) {
 600     SolPcmInfo* info = (SolPcmInfo*) id;
 601     int ret;
 602     int pos;
 603 
 604     if (info) {
 605         pos = getDevicePosition(info, isSource);
 606         if (pos >= 0) {
 607             info->positionOffset = javaBytePos - pos;
 608         }
 609     }
 610 }
 611 
 612 int DAUDIO_RequiresServicing(void* id, int isSource) {
 613     // never need servicing on Solaris
 614     return FALSE;
 615 }
 616 
 617 void DAUDIO_Service(void* id, int isSource) {
 618     // never need servicing on Solaris
 619 }
 620 
 621 
 622 #endif // USE_DAUDIO