4 * QNX Sound Architecture
7 #pragma implementation "sound.h"
11 #include <sys/asoundlib.h>
13 typedef struct _SoundHandleEntry
{
14 struct _SoundHandleEntry
*next
;
21 unsigned bitsPerSample
;
22 unsigned fragmentValue
;
25 snd_pcm_t
*pcm_handle
;
28 snd_mixer_t
*mixer_handle
;
29 snd_mixer_group_t group
;
32 static SoundHandleEntry
*SoundHandleList
;
33 static pthread_rwlock_t SoundHandleLock
= PTHREAD_RWLOCK_INITIALIZER
;
35 static int snd_openmode
[2] = {SND_PCM_OPEN_CAPTURE
, SND_PCM_OPEN_PLAYBACK
};
36 static int snd_chnmode
[2] = {SND_PCM_CHANNEL_CAPTURE
, SND_PCM_CHANNEL_PLAYBACK
};
38 PSound::PSound(unsigned channels
,
39 unsigned samplesPerSecond
,
40 unsigned bitsPerSample
,
45 numChannels
= channels
;
46 sampleRate
= samplesPerSecond
;
47 sampleSize
= bitsPerSample
;
50 memcpy(GetPointer(), buffer
, bufferSize
);
54 PSound::PSound(const PFilePath
& filename
)
64 PSound
& PSound::operator=(const PBYTEArray
& data
)
66 PBYTEArray::operator=(data
);
71 void PSound::SetFormat(unsigned channels
,
72 unsigned samplesPerSecond
,
73 unsigned bitsPerSample
)
76 numChannels
= channels
;
77 sampleRate
= samplesPerSecond
;
78 sampleSize
= bitsPerSample
;
79 formatInfo
.SetSize(0);
83 BOOL
PSound::Load(const PFilePath
& /*filename*/)
89 BOOL
PSound::Save(const PFilePath
& /*filename*/)
97 PSoundChannel
channel(PSoundChannel::GetDefaultDevice(PSoundChannel::Player
),
98 PSoundChannel::Player
);
99 if (!channel
.IsOpen())
102 return channel
.PlaySound(*this, TRUE
);
106 BOOL
PSound::PlayFile(const PFilePath
& file
, BOOL wait
)
108 PSoundChannel
channel(PSoundChannel::GetDefaultDevice(PSoundChannel::Player
),
109 PSoundChannel::Player
);
110 if (!channel
.IsOpen())
113 return channel
.PlayFile(file
, wait
);
117 PSoundChannel::PSoundChannel()
123 PSoundChannel::PSoundChannel(const PString
& device
,
125 unsigned numChannels
,
127 unsigned bitsPerSample
)
130 Open(device
, dir
, numChannels
, sampleRate
, bitsPerSample
);
134 void PSoundChannel::Construct()
140 PSoundChannel::~PSoundChannel()
146 PStringArray
PSoundChannel::GetDeviceNames(Directions dir
)
149 PDirectory devdir
= "/dev/snd";
155 PString filename
= devdir
.GetEntryName();
156 PString devname
= devdir
+ filename
;
158 if ((filename
.GetLength() > 3) && (filename
.Left(3) == "pcm") &&
159 (filename
.Right(1) == (dir
== Recorder
? "r" : "p")))
161 int fd
= ::open(filename
, O_RDONLY
);
163 devices
.AppendString(filename
);
167 } while (devdir
.Next());
172 PString
PSoundChannel::GetDefaultDevice(Directions dir
)
177 filename
= "/dev/snd/pcmPreferredp";
179 filename
= "/dev/snd/pcmPreferredc";
181 int fd
= ::open(filename
, O_RDONLY
);
182 // PTRACE(1, "GetDefaultDevice; fd = " << fd << ", filename ='" << filename <<"'\n");
187 return (filename
= "/dev/null");
192 BOOL
PSoundChannel::Open(const PString
& _device
,
194 unsigned _numChannels
,
195 unsigned _sampleRate
,
196 unsigned _bitsPerSample
)
200 // make the direction value 1 or 2
203 SoundHandleEntry
*entry
;
205 pthread_rwlock_wrlock(&SoundHandleLock
);
206 for (entry
= SoundHandleList
; entry
&& entry
->handle
!= os_handle
; entry
= entry
->next
);
210 PTRACE(6, "OSS\tOpen occured for existing entry");
212 // see if the sound channel is already open in this direction
213 if ((entry
->direction
& dir
) != 0) {
214 pthread_rwlock_unlock(&SoundHandleLock
);
218 // flag this entry as open in this direction
219 entry
->direction
|= dir
;
220 os_handle
= entry
->handle
;
223 PTRACE(6, "OSS\tOpen occured for new entry");
225 SoundHandleEntry
*entry
= (SoundHandleEntry
*)::malloc(sizeof(*entry
));
228 pthread_rwlock_unlock(&SoundHandleLock
);
232 // this is the first time this device has been used
233 // open the device in read/write mode always
234 if (((_dir
== Player
) && _device
== "/dev/snd/pcmPreferredp") ||
235 ((_dir
== Recorder
) && _device
== "/dev/snd/pcmPreferredc"))
237 os_handle
= snd_pcm_open_preferred(&entry
->pcm_handle
, &entry
->card
, &entry
->dev
, snd_openmode
[_dir
]);
239 if (sscanf(_device
, "/dev/snd/pcmC%iD%i", &entry
->card
, &entry
->dev
) != 2) {
241 return ConvertOSError(os_handle
);
243 os_handle
= snd_pcm_open(&entry
->pcm_handle
, entry
->card
, entry
->dev
, snd_openmode
[_dir
]);
249 return ConvertOSError(os_handle
);
252 if (snd_pcm_plugin_set_disable(entry
->pcm_handle
, PLUGIN_DISABLE_MMAP
) < 0)
255 // save the information into the dictionary entry
256 os_handle
= snd_pcm_file_descriptor(entry
->pcm_handle
, snd_chnmode
[_dir
]);
257 entry
->handle
= os_handle
;
258 entry
->direction
= dir
;
259 entry
->numChannels
= mNumChannels
= _numChannels
;
260 entry
->sampleRate
= actualSampleRate
= mSampleRate
= _sampleRate
;
261 entry
->bitsPerSample
= mBitsPerSample
= _bitsPerSample
;
262 entry
->isInitialised
= FALSE
;
263 entry
->fragmentValue
= 0x7fff0008;
264 entry
->mixer_handle
= 0;
266 entry
->next
= SoundHandleList
;
267 SoundHandleList
= entry
;
269 pthread_rwlock_unlock(&SoundHandleLock
);
271 // save the direction and device
274 isInitialised
= FALSE
;
279 BOOL
PSoundChannel::Setup()
282 PTRACE(6, "OSS\tSkipping setup of " << device
<< " as not open");
287 PTRACE(6, "OSS\tSkipping setup of " << device
<< " as instance already initialised");
291 SoundHandleEntry
*entry
;
292 pthread_rwlock_rdlock(&SoundHandleLock
);
293 for (entry
= SoundHandleList
; entry
&& entry
->handle
!= os_handle
;
294 entry
= entry
->next
);
296 // set default return status
299 // do not re-initialise initialised devices
300 if (entry
->isInitialised
) {
301 pthread_rwlock_unlock(&SoundHandleLock
);
302 PTRACE(6, "OSS\tSkipping setup for " << device
<< " as already initialised");
306 PTRACE(6, "OSS\tInitialising " << device
<< "(" << (void *)(&entry
) << ")");
310 mBitsPerSample
= entry
->bitsPerSample
;
311 mNumChannels
= entry
->numChannels
;
312 mSampleRate
= entry
->sampleRate
;
314 snd_pcm_channel_params_t pp
;
316 memset(&pp
, 0, sizeof(pp
));
317 pp
.channel
= snd_chnmode
[direction
];
318 pp
.mode
= SND_PCM_MODE_BLOCK
;
319 pp
.start_mode
= (direction
== Player
) ? SND_PCM_START_FULL
: SND_PCM_START_DATA
;
320 pp
.stop_mode
= SND_PCM_STOP_STOP
;
321 pp
.buf
.block
.frags_min
= 1;
322 pp
.buf
.block
.frag_size
= 1 << (entry
->fragmentValue
& 0xffff);
323 pp
.buf
.block
.frags_max
= ((unsigned)entry
->fragmentValue
>> 16) & 0x7fff;
324 if (pp
.buf
.block
.frags_max
== 0)
325 pp
.buf
.block
.frags_max
= 65536;
327 pp
.format
.interleave
= 1;
328 pp
.format
.rate
= entry
->sampleRate
;
329 pp
.format
.voices
= entry
->numChannels
;
331 #if PBYTE_ORDER == PLITTLE_ENDIAN
332 pp
.format
.format
= (entry
->bitsPerSample
== 16) ? SND_PCM_SFMT_S16_LE
: SND_PCM_SFMT_U8
;
334 pp
.format
.format
= (entry
->bitsPerSample
== 16) ? SND_PCM_SFMT_S16_BE
: SND_PCM_SFMT_U8
;
337 if (snd_pcm_plugin_params(entry
->pcm_handle
, &pp
) < 0) {
338 pthread_rwlock_unlock(&SoundHandleLock
);
342 if (snd_pcm_plugin_prepare(entry
->pcm_handle
, snd_chnmode
[direction
]) < 0) {
343 pthread_rwlock_unlock(&SoundHandleLock
);
347 /* also open the mixer */
348 snd_pcm_channel_setup_t setup
;
350 memset(&setup
, 0, sizeof(setup
));
351 memset(&entry
->group
, 0, sizeof(entry
->group
));
352 setup
.channel
= snd_chnmode
[direction
];
353 setup
.mixer_gid
= &entry
->group
.gid
;
355 if (snd_pcm_plugin_setup(entry
->pcm_handle
, &setup
) < 0) {
356 pthread_rwlock_unlock(&SoundHandleLock
);
360 if (snd_mixer_open(&entry
->mixer_handle
, entry
->card
, setup
.mixer_device
) < 0) {
361 pthread_rwlock_unlock(&SoundHandleLock
);
365 actualSampleRate
= setup
.format
.rate
;
369 PTRACE(4, "QSA: Frag Size = " << setup
.buf
.block
.frag_size
370 << ", Rate = " << setup
.format
.rate
371 << ", Mixer Pcm Group [" << entry
->group
.gid
.name
<< "]\n");
374 pthread_rwlock_unlock(&SoundHandleLock
);
375 // ensure device is marked as initialised
376 isInitialised
= TRUE
;
377 entry
->isInitialised
= TRUE
;
382 BOOL
PSoundChannel::Close()
384 // if the channel isn't open, do nothing
388 SoundHandleEntry
*entry
, **entryp
;
390 pthread_rwlock_wrlock(&SoundHandleLock
);
391 for (entryp
= &SoundHandleList
, entry
= *entryp
; entry
&& entry
->handle
!= os_handle
;
392 entryp
= &entry
->next
, entry
= *entryp
);
395 pthread_rwlock_unlock(&SoundHandleLock
);
399 // modify the directions bit mask in the dictionary
400 entry
->direction
^= (direction
+1);
402 // if this is the last usage of this entry, then remove it
403 if (entry
->direction
== 0) {
404 snd_mixer_close(entry
->mixer_handle
);
405 snd_pcm_plugin_flush(entry
->pcm_handle
, snd_chnmode
[direction
]);
406 snd_pcm_close(entry
->pcm_handle
);
407 *entryp
= entry
->next
;
410 pthread_rwlock_unlock(&SoundHandleLock
);
411 return PChannel::Close();
414 // flag this channel as closed
415 pthread_rwlock_unlock(&SoundHandleLock
);
419 BOOL
PSoundChannel::Write(const void * buf
, PINDEX len
)
427 SoundHandleEntry
* entry
;
428 pthread_rwlock_rdlock(&SoundHandleLock
);
429 for (entry
= SoundHandleList
; entry
&& entry
->handle
!= os_handle
;
430 entry
= entry
->next
);
432 snd_pcm_channel_status_t status
;
435 while ((written
+= snd_pcm_plugin_write(entry
->pcm_handle
, buf
, len
)) < len
)
437 memset(&status
, 0, sizeof(status
));
438 status
.channel
= SND_PCM_CHANNEL_PLAYBACK
;
439 if (snd_pcm_plugin_status(entry
->pcm_handle
, &status
) < 0) {
440 pthread_rwlock_unlock(&SoundHandleLock
);
443 if (status
.status
== SND_PCM_STATUS_READY
||
444 status
.status
== SND_PCM_STATUS_UNDERRUN
)
446 if (snd_pcm_plugin_prepare(entry
->pcm_handle
, snd_chnmode
[direction
]) < 0) {
447 pthread_rwlock_unlock(&SoundHandleLock
);
454 pthread_rwlock_unlock(&SoundHandleLock
);
458 BOOL
PSoundChannel::Read(void * buf
, PINDEX len
)
468 SoundHandleEntry
* entry
;
469 pthread_rwlock_rdlock(&SoundHandleLock
);
470 for (entry
= SoundHandleList
; entry
&& entry
->handle
!= os_handle
;
471 entry
= entry
->next
);
473 PTRACE(6, "QSA\tRead start");
475 lastReadCount
= snd_pcm_plugin_read(entry
->pcm_handle
, buf
, len
);
477 if (lastReadCount
< len
) {
478 snd_pcm_channel_status_t status
;
480 memset(&status
, 0, sizeof(status
));
481 status
.channel
= SND_PCM_CHANNEL_CAPTURE
;
482 if (snd_pcm_plugin_status(entry
->pcm_handle
, &status
) < 0) {
483 pthread_rwlock_unlock(&SoundHandleLock
);
484 PTRACE(6, "QSA\tRead failed");
488 if (status
.status
== SND_PCM_STATUS_READY
||
489 status
.status
== SND_PCM_STATUS_OVERRUN
) {
490 if (snd_pcm_plugin_prepare(entry
->pcm_handle
, SND_PCM_CHANNEL_CAPTURE
) < 0) {
491 pthread_rwlock_unlock(&SoundHandleLock
);
492 PTRACE(6, "QSA\tRead failed");
496 PTRACE(6, "QSA\tRead completed short - " << lastReadCount
<< " vs " << len
);
498 PTRACE(6, "QSA\tRead completed");
500 pthread_rwlock_unlock(&SoundHandleLock
);
504 BOOL
PSoundChannel::SetFormat(unsigned numChannels
,
506 unsigned bitsPerSample
)
509 return SetErrorValues(NotOpen
, EBADF
);
512 PAssert((bitsPerSample
== 8) || (bitsPerSample
== 16), PInvalidParameter
);
513 PAssert(numChannels
>= 1 && numChannels
<= 2, PInvalidParameter
);
515 SoundHandleEntry
* entry
;
516 pthread_rwlock_rdlock(&SoundHandleLock
);
517 for (entry
= SoundHandleList
; entry
&& entry
->handle
!= os_handle
;
518 entry
= entry
->next
);
520 if (entry
->isInitialised
) {
521 if ((numChannels
!= entry
->numChannels
) ||
522 (sampleRate
!= entry
->sampleRate
) ||
523 (bitsPerSample
!= entry
->bitsPerSample
)) {
524 pthread_rwlock_unlock(&SoundHandleLock
);
525 PTRACE(6, "OSS\tTried to change read/write format without stopping");
528 pthread_rwlock_unlock(&SoundHandleLock
);
532 if (direction
== Player
) {
533 snd_pcm_plugin_playback_drain(entry
->pcm_handle
);
536 entry
->numChannels
= numChannels
;
537 entry
->sampleRate
= sampleRate
;
538 entry
->bitsPerSample
= bitsPerSample
;
539 entry
->isInitialised
= FALSE
;
540 pthread_rwlock_unlock(&SoundHandleLock
);
542 // mark this channel as uninitialised
543 isInitialised
= FALSE
;
548 // Get the number of channels (mono/stereo) in the sound.
549 unsigned PSoundChannel::GetChannels() const
554 // Get the sample rate in samples per second.
555 unsigned PSoundChannel::GetSampleRate() const
557 return actualSampleRate
;
560 // Get the sample size in bits per sample.
561 unsigned PSoundChannel::GetSampleSize() const
563 return mBitsPerSample
;
567 BOOL
PSoundChannel::SetBuffers(PINDEX size
, PINDEX count
)
570 return SetErrorValues(NotOpen
, EBADF
);
572 //PINDEX totalSize = size * count;
575 //count = (totalSize + 15) / 16;
577 PAssert(size
> 0 && count
> 0 && count
< 65536, PInvalidParameter
);
579 while (size
> (PINDEX
)(1 << arg
))
584 SoundHandleEntry
* entry
;
585 pthread_rwlock_rdlock(&SoundHandleLock
);
586 for (entry
= SoundHandleList
; entry
&& entry
->handle
!= os_handle
;
587 entry
= entry
->next
);
589 if (entry
->isInitialised
) {
590 if (entry
->fragmentValue
!= (unsigned)arg
) {
591 pthread_rwlock_unlock(&SoundHandleLock
);
592 PTRACE(6, "OSS\tTried to change buffers without stopping");
595 pthread_rwlock_unlock(&SoundHandleLock
);
599 if (direction
== Player
) {
600 snd_pcm_plugin_playback_drain(entry
->pcm_handle
);
603 // set information in the common record
604 entry
->fragmentValue
= arg
;
605 entry
->isInitialised
= FALSE
;
606 pthread_rwlock_unlock(&SoundHandleLock
);
608 // flag this channel as not initialised
609 isInitialised
= FALSE
;
615 BOOL
PSoundChannel::GetBuffers(PINDEX
& size
, PINDEX
& count
)
618 return SetErrorValues(NotOpen
, EBADF
);
620 SoundHandleEntry
* entry
;
621 pthread_rwlock_rdlock(&SoundHandleLock
);
622 for (entry
= SoundHandleList
; entry
&& entry
->handle
!= os_handle
;
623 entry
= entry
->next
);
625 int arg
= entry
->fragmentValue
;
628 size
= 1 << (arg
&0xffff);
629 pthread_rwlock_unlock(&SoundHandleLock
);
635 BOOL
PSoundChannel::PlaySound(const PSound
& sound
, BOOL wait
)
638 return SetErrorValues(NotOpen
, EBADF
);
642 if (!Write((const BYTE
*)sound
, sound
.GetSize()))
646 return WaitForPlayCompletion();
652 BOOL
PSoundChannel::PlayFile(const PFilePath
& filename
, BOOL wait
)
655 return SetErrorValues(NotOpen
, EBADF
);
657 PFile
file(filename
, PFile::ReadOnly
);
663 if (!file
.Read(buffer
, 256))
665 PINDEX len
= file
.GetLastReadCount();
668 if (!Write(buffer
, len
))
675 return WaitForPlayCompletion();
681 BOOL
PSoundChannel::HasPlayCompleted()
684 return SetErrorValues(NotOpen
, EBADF
);
686 SoundHandleEntry
* entry
;
687 pthread_rwlock_rdlock(&SoundHandleLock
);
688 for (entry
= SoundHandleList
; entry
&& entry
->handle
!= os_handle
;
689 entry
= entry
->next
);
691 snd_pcm_channel_status_t status
;
692 memset(&status
, 0, sizeof(status
));
693 status
.channel
= snd_chnmode
[direction
];
695 if (snd_pcm_plugin_status(entry
->pcm_handle
, &status
) < 0) {
696 pthread_rwlock_unlock(&SoundHandleLock
);
700 int ret
= (abs(status
.free
) / (entry
->bitsPerSample
/ 8));
701 pthread_rwlock_unlock(&SoundHandleLock
);
705 BOOL
PSoundChannel::WaitForPlayCompletion()
708 return SetErrorValues(NotOpen
, EBADF
);
710 SoundHandleEntry
* entry
;
711 pthread_rwlock_rdlock(&SoundHandleLock
);
712 for (entry
= SoundHandleList
; entry
&& entry
->handle
!= os_handle
;
713 entry
= entry
->next
);
715 if (snd_pcm_playback_flush(entry
->pcm_handle
) < 0) {
716 pthread_rwlock_unlock(&SoundHandleLock
);
720 pthread_rwlock_unlock(&SoundHandleLock
);
725 BOOL
PSoundChannel::RecordSound(PSound
& sound
)
728 return SetErrorValues(NotOpen
, EBADF
);
734 BOOL
PSoundChannel::RecordFile(const PFilePath
& filename
)
737 return SetErrorValues(NotOpen
, EBADF
);
743 BOOL
PSoundChannel::StartRecording()
746 return SetErrorValues(NotOpen
, EBADF
);
752 struct timeval instant
= {0, 0};
753 return ConvertOSError(::select(fd
+ 1, &fds
, NULL
, NULL
, &instant
));
757 BOOL
PSoundChannel::IsRecordBufferFull()
760 return SetErrorValues(NotOpen
, EBADF
);
762 PTRACE(1, "IsRecordBufferFull()\n");
763 /* do I suppose to get the status, and check sth ? */
768 BOOL
PSoundChannel::AreAllRecordBuffersFull()
771 return SetErrorValues(NotOpen
, EBADF
);
773 PTRACE(1, "AreAllRecordBuffersFull()\n");
774 /* do I suppose to get the status, and check sth ? */
779 BOOL
PSoundChannel::WaitForRecordBufferFull()
782 return SetErrorValues(NotOpen
, EBADF
);
784 PTRACE(1, "WaitForRecordBufferFull()\n");
785 return PXSetIOBlock(PXReadBlock
, readTimeout
);
789 BOOL
PSoundChannel::WaitForAllRecordBuffersFull()
791 PTRACE(1, "WaitForAllRecordBuffersFull()\n");
796 BOOL
PSoundChannel::Abort()
798 if (direction
== Player
&& os_handle
!= -1) {
799 SoundHandleEntry
* entry
;
800 pthread_rwlock_rdlock(&SoundHandleLock
);
801 for (entry
= SoundHandleList
; entry
&& entry
->handle
!= os_handle
;
802 entry
= entry
->next
);
804 snd_pcm_plugin_playback_drain(entry
->pcm_handle
);
805 pthread_rwlock_unlock(&SoundHandleLock
);
811 BOOL
PSoundChannel::SetVolume(unsigned newVal
)
818 SoundHandleEntry
*entry
;
819 pthread_rwlock_rdlock(&SoundHandleLock
);
820 for (entry
= SoundHandleList
; entry
&& entry
->handle
!= os_handle
;
821 entry
= entry
->next
);
825 if ((ret
= snd_mixer_group_read(entry
->mixer_handle
, &entry
->group
)) < 0)
827 pthread_rwlock_unlock(&SoundHandleLock
);
828 cerr
<< "snd_mixer_group_read: " << strerror(-ret
) << endl
;
832 /* QSA treat the newVal as a percentage */
833 newVal
= (newVal
* (entry
->group
.max
- entry
->group
.min
) / 100) + entry
->group
.min
;
835 entry
->group
.volume
.names
.front_left
= newVal
;
836 entry
->group
.volume
.names
.front_right
= newVal
;
838 if ((ret
= snd_mixer_group_write(entry
->mixer_handle
, &entry
->group
)) < 0)
840 pthread_rwlock_unlock(&SoundHandleLock
);
841 cerr
<< "snd_mixer_group_write: " << strerror(-ret
) << endl
;
844 pthread_rwlock_unlock(&SoundHandleLock
);
848 BOOL
PSoundChannel::GetVolume(unsigned &devVol
)
855 SoundHandleEntry
*entry
;
856 pthread_rwlock_rdlock(&SoundHandleLock
);
857 for (entry
= SoundHandleList
; entry
&& entry
->handle
!= os_handle
;
858 entry
= entry
->next
);
862 if ((ret
= snd_mixer_group_read(entry
->mixer_handle
, &entry
->group
)) < 0)
864 pthread_rwlock_unlock(&SoundHandleLock
);
867 pthread_rwlock_unlock(&SoundHandleLock
);
869 /* return the percentage */
870 devVol
= (unsigned)(entry
->group
.volume
.names
.front_left
- entry
->group
.min
) * 100
871 / (entry
->group
.max
- entry
->group
.min
);