1 /*
   2  * Copyright (c) 2003, 2013, 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     if (channels <= 0) {
 186         ERROR1(" DAUDIO_Open: Invalid number of channels=%d!\n", channels);
 187         return NULL;
 188     }
 189 
 190     info = (SolPcmInfo*) malloc(sizeof(SolPcmInfo));
 191     if (!info) {
 192         ERROR0("Out of memory\n");
 193         return NULL;
 194     }
 195     memset(info, 0, sizeof(SolPcmInfo));
 196     info->frameSize = frameSize;
 197     info->fd = -1;
 198 
 199     if (isSource) {
 200         openMode = O_WRONLY;
 201     } else {
 202         openMode = O_RDONLY;
 203     }
 204 
 205 #ifndef __linux__
 206     /* blackdown does not use NONBLOCK */
 207     openMode |= O_NONBLOCK;
 208 #endif
 209 
 210     if (getAudioDeviceDescriptionByIndex(mixerIndex, &desc, FALSE)) {
 211         info->fd = open(desc.path, openMode);
 212     }
 213     if (info->fd < 0) {
 214         ERROR1("Couldn't open audio device for mixer %d!\n", mixerIndex);
 215         free(info);
 216         return NULL;
 217     }
 218     /* set to multiple open */
 219     if (ioctl(info->fd, AUDIO_MIXER_MULTIPLE_OPEN, NULL) >= 0) {
 220         TRACE1("DAUDIO_Open: %s set to multiple open\n", desc.path);
 221     } else {
 222         ERROR1("DAUDIO_Open: ioctl AUDIO_MIXER_MULTIPLE_OPEN failed on %s!\n", desc.path);
 223     }
 224 
 225     AUDIO_INITINFO(&(info->info));
 226     /* need AUDIO_GETINFO ioctl to get this to work on solaris x86  */
 227     err = ioctl(info->fd, AUDIO_GETINFO, &(info->info));
 228 
 229     /* not valid to call AUDIO_SETINFO ioctl with all the fields from AUDIO_GETINFO. */
 230     AUDIO_INITINFO(&(info->info));
 231 
 232     if (isSource) {
 233         info->info.play.sample_rate = sampleRate;
 234         info->info.play.precision = sampleSizeInBits;
 235         info->info.play.channels = channels;
 236         info->info.play.encoding = AUDIO_ENCODING_LINEAR;
 237         info->info.play.buffer_size = bufferSizeInBytes;
 238         info->info.play.pause = 1;
 239     } else {
 240         info->info.record.sample_rate = sampleRate;
 241         info->info.record.precision = sampleSizeInBits;
 242         info->info.record.channels = channels;
 243         info->info.record.encoding = AUDIO_ENCODING_LINEAR;
 244         info->info.record.buffer_size = bufferSizeInBytes;
 245         info->info.record.pause = 1;
 246     }
 247     err = ioctl(info->fd, AUDIO_SETINFO,  &(info->info));
 248     if (err < 0) {
 249         ERROR0("DAUDIO_Open: could not set info!\n");
 250         DAUDIO_Close((void*) info, isSource);
 251         return NULL;
 252     }
 253     DAUDIO_Flush((void*) info, isSource);
 254 
 255     err = ioctl(info->fd, AUDIO_GETINFO, &(info->info));
 256     if (err >= 0) {
 257         if (isSource) {
 258             info->bufferSizeInBytes = info->info.play.buffer_size;
 259         } else {
 260             info->bufferSizeInBytes = info->info.record.buffer_size;
 261         }
 262         TRACE2("DAUDIO: buffersize in bytes: requested=%d, got %d\n",
 263                (int) bufferSizeInBytes,
 264                (int) info->bufferSizeInBytes);
 265     } else {
 266         ERROR0("DAUDIO_Open: cannot get info!\n");
 267         DAUDIO_Close((void*) info, isSource);
 268         return NULL;
 269     }
 270     TRACE0("< DAUDIO_Open: Opened device successfully.\n");
 271     return (void*) info;
 272 }
 273 
 274 
 275 int DAUDIO_Start(void* id, int isSource) {
 276     SolPcmInfo* info = (SolPcmInfo*) id;
 277     int err, modified;
 278     audio_info_t audioInfo;
 279 
 280     TRACE0("> DAUDIO_Start\n");
 281 
 282     AUDIO_INITINFO(&audioInfo);
 283     err = ioctl(info->fd, AUDIO_GETINFO, &audioInfo);
 284     if (err >= 0) {
 285         // unpause
 286         modified = FALSE;
 287         if (isSource && audioInfo.play.pause) {
 288             audioInfo.play.pause = 0;
 289             modified = TRUE;
 290         }
 291         if (!isSource && audioInfo.record.pause) {
 292             audioInfo.record.pause = 0;
 293             modified = TRUE;
 294         }
 295         if (modified) {
 296             err = ioctl(info->fd, AUDIO_SETINFO, &audioInfo);
 297         }
 298     }
 299 
 300     TRACE1("< DAUDIO_Start %s\n", (err>=0)?"success":"error");
 301     return (err >= 0)?TRUE:FALSE;
 302 }
 303 
 304 int DAUDIO_Stop(void* id, int isSource) {
 305     SolPcmInfo* info = (SolPcmInfo*) id;
 306     int err, modified;
 307     audio_info_t audioInfo;
 308 
 309     TRACE0("> DAUDIO_Stop\n");
 310 
 311     AUDIO_INITINFO(&audioInfo);
 312     err = ioctl(info->fd, AUDIO_GETINFO, &audioInfo);
 313     if (err >= 0) {
 314         // pause
 315         modified = FALSE;
 316         if (isSource && !audioInfo.play.pause) {
 317             audioInfo.play.pause = 1;
 318             modified = TRUE;
 319         }
 320         if (!isSource && !audioInfo.record.pause) {
 321             audioInfo.record.pause = 1;
 322             modified = TRUE;
 323         }
 324         if (modified) {
 325             err = ioctl(info->fd, AUDIO_SETINFO, &audioInfo);
 326         }
 327     }
 328 
 329     TRACE1("< DAUDIO_Stop %s\n", (err>=0)?"success":"error");
 330     return (err >= 0)?TRUE:FALSE;
 331 }
 332 
 333 void DAUDIO_Close(void* id, int isSource) {
 334     SolPcmInfo* info = (SolPcmInfo*) id;
 335 
 336     TRACE0("DAUDIO_Close\n");
 337     if (info != NULL) {
 338         if (info->fd >= 0) {
 339             DAUDIO_Flush(id, isSource);
 340             close(info->fd);
 341         }
 342         free(info);
 343     }
 344 }
 345 
 346 #ifndef USE_TRACE
 347 /* close to 2^31 */
 348 #define POSITION_MAX 2000000000
 349 #else
 350 /* for testing */
 351 #define POSITION_MAX 1000000
 352 #endif
 353 
 354 void resetErrorFlagAndAdjustPosition(SolPcmInfo* info, int isSource, int count) {
 355     audio_info_t audioInfo;
 356     audio_prinfo_t* prinfo;
 357     int err;
 358     int offset = -1;
 359     int underrun = FALSE;
 360     int devBytes = 0;
 361 
 362     if (count > 0) {
 363         info->transferedBytes += count;
 364 
 365         if (isSource) {
 366             prinfo = &(audioInfo.play);
 367         } else {
 368             prinfo = &(audioInfo.record);
 369         }
 370         AUDIO_INITINFO(&audioInfo);
 371         err = ioctl(info->fd, AUDIO_GETINFO, &audioInfo);
 372         if (err >= 0) {
 373             underrun = prinfo->error;
 374             devBytes = prinfo->samples * info->frameSize;
 375         }
 376         AUDIO_INITINFO(&audioInfo);
 377         if (underrun) {
 378             /* if an underrun occurred, reset */
 379             ERROR1("DAUDIO_Write/Read: Underrun/overflow: adjusting positionOffset by %d:\n",
 380                    (devBytes - info->transferedBytes));
 381             ERROR1("    devBytes from %d to 0, ", devBytes);
 382             ERROR2(" positionOffset from %d to %d ",
 383                    (int) info->positionOffset,
 384                    (int) (info->positionOffset + info->transferedBytes));
 385             ERROR1(" transferedBytes from %d to 0\n",
 386                    (int) info->transferedBytes);
 387             prinfo->samples = 0;
 388             info->positionOffset += info->transferedBytes;
 389             info->transferedBytes = 0;
 390         }
 391         else if (info->transferedBytes > POSITION_MAX) {
 392             /* we will reset transferedBytes and
 393              * the samples field in prinfo
 394              */
 395             offset = devBytes;
 396             prinfo->samples = 0;
 397         }
 398         /* reset error flag */
 399         prinfo->error = 0;
 400 
 401         err = ioctl(info->fd, AUDIO_SETINFO, &audioInfo);
 402         if (err >= 0) {
 403             if (offset > 0) {
 404                 /* upon exit of AUDIO_SETINFO, the samples parameter
 405                  * was set to the previous value. This is our
 406                  * offset.
 407                  */
 408                 TRACE1("Adjust samplePos: offset=%d, ", (int) offset);
 409                 TRACE2("transferedBytes=%d -> %d, ",
 410                        (int) info->transferedBytes,
 411                        (int) (info->transferedBytes - offset));
 412                 TRACE2("positionOffset=%d -> %d\n",
 413                        (int) (info->positionOffset),
 414                        (int) (((int) info->positionOffset) + offset));
 415                 info->transferedBytes -= offset;
 416                 info->positionOffset += offset;
 417             }
 418         } else {
 419             ERROR0("DAUDIO: resetErrorFlagAndAdjustPosition ioctl failed!\n");
 420         }
 421     }
 422 }
 423 
 424 // returns -1 on error
 425 int DAUDIO_Write(void* id, char* data, int byteSize) {
 426     SolPcmInfo* info = (SolPcmInfo*) id;
 427     int ret = -1;
 428 
 429     TRACE1("> DAUDIO_Write %d bytes\n", byteSize);
 430     if (info!=NULL) {
 431         ret = write(info->fd, data, byteSize);
 432         resetErrorFlagAndAdjustPosition(info, TRUE, ret);
 433         /* sets ret to -1 if buffer full, no error! */
 434         if (ret < 0) {
 435             ret = 0;
 436         }
 437     }
 438     TRACE1("< DAUDIO_Write: returning %d bytes.\n", ret);
 439     return ret;
 440 }
 441 
 442 // returns -1 on error
 443 int DAUDIO_Read(void* id, char* data, int byteSize) {
 444     SolPcmInfo* info = (SolPcmInfo*) id;
 445     int ret = -1;
 446 
 447     TRACE1("> DAUDIO_Read %d bytes\n", byteSize);
 448     if (info != NULL) {
 449         ret = read(info->fd, data, byteSize);
 450         resetErrorFlagAndAdjustPosition(info, TRUE, ret);
 451         /* sets ret to -1 if buffer full, no error! */
 452         if (ret < 0) {
 453             ret = 0;
 454         }
 455     }
 456     TRACE1("< DAUDIO_Read: returning %d bytes.\n", ret);
 457     return ret;
 458 }
 459 
 460 
 461 int DAUDIO_GetBufferSize(void* id, int isSource) {
 462     SolPcmInfo* info = (SolPcmInfo*) id;
 463     if (info) {
 464         return info->bufferSizeInBytes;
 465     }
 466     return 0;
 467 }
 468 
 469 int DAUDIO_StillDraining(void* id, int isSource) {
 470     SolPcmInfo* info = (SolPcmInfo*) id;
 471     audio_info_t audioInfo;
 472     audio_prinfo_t* prinfo;
 473     int ret = FALSE;
 474 
 475     if (info!=NULL) {
 476         if (isSource) {
 477             prinfo = &(audioInfo.play);
 478         } else {
 479             prinfo = &(audioInfo.record);
 480         }
 481         /* check error flag */
 482         AUDIO_INITINFO(&audioInfo);
 483         ioctl(info->fd, AUDIO_GETINFO, &audioInfo);
 484         ret = (prinfo->error != 0)?FALSE:TRUE;
 485     }
 486     return ret;
 487 }
 488 
 489 
 490 int getDevicePosition(SolPcmInfo* info, int isSource) {
 491     audio_info_t audioInfo;
 492     audio_prinfo_t* prinfo;
 493     int err;
 494 
 495     if (isSource) {
 496         prinfo = &(audioInfo.play);
 497     } else {
 498         prinfo = &(audioInfo.record);
 499     }
 500     AUDIO_INITINFO(&audioInfo);
 501     err = ioctl(info->fd, AUDIO_GETINFO, &audioInfo);
 502     if (err >= 0) {
 503         /*TRACE2("---> device paused: %d  eof=%d\n",
 504                prinfo->pause, prinfo->eof);
 505         */
 506         return (int) (prinfo->samples * info->frameSize);
 507     }
 508     ERROR0("DAUDIO: getDevicePosition: ioctl failed!\n");
 509     return -1;
 510 }
 511 
 512 int DAUDIO_Flush(void* id, int isSource) {
 513     SolPcmInfo* info = (SolPcmInfo*) id;
 514     int err = -1;
 515     int pos;
 516 
 517     TRACE0("DAUDIO_Flush\n");
 518     if (info) {
 519         if (isSource) {
 520             err = ioctl(info->fd, I_FLUSH, FLUSHW);
 521         } else {
 522             err = ioctl(info->fd, I_FLUSH, FLUSHR);
 523         }
 524         if (err >= 0) {
 525             /* resets the transferedBytes parameter to
 526              * the current samples count of the device
 527              */
 528             pos = getDevicePosition(info, isSource);
 529             if (pos >= 0) {
 530                 info->transferedBytes = pos;
 531             }
 532         }
 533     }
 534     if (err < 0) {
 535         ERROR0("ERROR in DAUDIO_Flush\n");
 536     }
 537     return (err < 0)?FALSE:TRUE;
 538 }
 539 
 540 int DAUDIO_GetAvailable(void* id, int isSource) {
 541     SolPcmInfo* info = (SolPcmInfo*) id;
 542     int ret = 0;
 543     int pos;
 544 
 545     if (info) {
 546         /* unfortunately, the STREAMS architecture
 547          * seems to not have a method for querying
 548          * the available bytes to read/write!
 549          * estimate it...
 550          */
 551         pos = getDevicePosition(info, isSource);
 552         if (pos >= 0) {
 553             if (isSource) {
 554                 /* we usually have written more bytes
 555                  * to the queue than the device position should be
 556                  */
 557                 ret = (info->bufferSizeInBytes) - (info->transferedBytes - pos);
 558             } else {
 559                 /* for record, the device stream should
 560                  * be usually ahead of our read actions
 561                  */
 562                 ret = pos - info->transferedBytes;
 563             }
 564             if (ret > info->bufferSizeInBytes) {
 565                 ERROR2("DAUDIO_GetAvailable: available=%d, too big at bufferSize=%d!\n",
 566                        (int) ret, (int) info->bufferSizeInBytes);
 567                 ERROR2("                     devicePos=%d, transferedBytes=%d\n",
 568                        (int) pos, (int) info->transferedBytes);
 569                 ret = info->bufferSizeInBytes;
 570             }
 571             else if (ret < 0) {
 572                 ERROR1("DAUDIO_GetAvailable: available=%d, in theory not possible!\n",
 573                        (int) ret);
 574                 ERROR2("                     devicePos=%d, transferedBytes=%d\n",
 575                        (int) pos, (int) info->transferedBytes);
 576                 ret = 0;
 577             }
 578         }
 579     }
 580 
 581     TRACE1("DAUDIO_GetAvailable returns %d bytes\n", ret);
 582     return ret;
 583 }
 584 
 585 INT64 DAUDIO_GetBytePosition(void* id, int isSource, INT64 javaBytePos) {
 586     SolPcmInfo* info = (SolPcmInfo*) id;
 587     int ret;
 588     int pos;
 589     INT64 result = javaBytePos;
 590 
 591     if (info) {
 592         pos = getDevicePosition(info, isSource);
 593         if (pos >= 0) {
 594             result = info->positionOffset + pos;
 595         }
 596     }
 597 
 598     //printf("getbyteposition: javaBytePos=%d , return=%d\n", (int) javaBytePos, (int) result);
 599     return result;
 600 }
 601 
 602 
 603 void DAUDIO_SetBytePosition(void* id, int isSource, INT64 javaBytePos) {
 604     SolPcmInfo* info = (SolPcmInfo*) id;
 605     int ret;
 606     int pos;
 607 
 608     if (info) {
 609         pos = getDevicePosition(info, isSource);
 610         if (pos >= 0) {
 611             info->positionOffset = javaBytePos - pos;
 612         }
 613     }
 614 }
 615 
 616 int DAUDIO_RequiresServicing(void* id, int isSource) {
 617     // never need servicing on Solaris
 618     return FALSE;
 619 }
 620 
 621 void DAUDIO_Service(void* id, int isSource) {
 622     // never need servicing on Solaris
 623 }
 624 
 625 
 626 #endif // USE_DAUDIO