1 /***************************************************** vim:set ts=4 sw=4 sts=4:
5 (C) 2005 by Gary Cramblitt <garycramblitt@comcast.net>
6 Portions based on aplay.c in alsa-utils
7 Copyright (c) by Jaroslav Kysela <perex@suse.cz>
8 Based on vplay program by Michael Beck
10 Original author: Gary Cramblitt <garycramblitt@comcast.net>
12 This program is free software; you can redistribute it and/or modify
13 it under the terms of the GNU General Public License as published by
14 the Free Software Foundation; either version 2 of the License, or
15 (at your option) any later version.
17 This program is distributed in the hope that it will be useful,
18 but WITHOUT ANY WARRANTY; without even the implied warranty of
19 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 GNU General Public License for more details.
22 You should have received a copy of the GNU General Public License
23 along with this program; if not, write to the Free Software
24 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
25 ******************************************************************************/
27 // AlsaPlayer includes.
28 #include "alsaplayer.h"
32 // #include <sys/wait.h>
34 #include <config-kttsd.h>
35 #if TIME_WITH_SYS_TIME
36 # include <sys/time.h>
40 # include <sys/time.h>
47 #include <QtCore/QDir>
48 #include <QtGui/QApplication>
49 #include <QtCore/QMutexLocker>
54 #include <kstandarddirs.h>
55 #include <kmessagebox.h>
58 #define DBG if (m_debugLevel >= 2) kDebug() << timestamp()
60 QString
AlsaPlayerThread::timestamp() const
66 tstr
= strdup(ctime(&t
));
67 tstr
[strlen(tstr
)-1] = 0;
68 gettimeofday(&tv
,NULL
);
70 ts
.sprintf(" %s [%d] ",tstr
, (int) tv
.tv_usec
);
75 static snd_pcm_sframes_t (*readi_func
)(snd_pcm_t
*handle
, void *buffer
, snd_pcm_uframes_t size
);
76 static snd_pcm_sframes_t (*writei_func
)(snd_pcm_t
*handle
, const void *buffer
, snd_pcm_uframes_t size
);
77 static snd_pcm_sframes_t (*readn_func
)(snd_pcm_t
*handle
, void **bufs
, snd_pcm_uframes_t size
);
78 static snd_pcm_sframes_t (*writen_func
)(snd_pcm_t
*handle
, void **bufs
, snd_pcm_uframes_t size
);
81 ////////////////////////////////////////////////////////////////////////////////
83 ////////////////////////////////////////////////////////////////////////////////
85 AlsaPlayerThread::AlsaPlayerThread(QObject
* parent
) :
92 m_simulatedPause(false)
97 AlsaPlayerThread::~AlsaPlayerThread()
105 //void AlsaPlayerThread::play(const FileHandle &file)
106 void AlsaPlayerThread::startPlay(const QString
&file
)
111 snd_pcm_pause(handle
, false);
113 m_simulatedPause
= false;
117 audiofile
.setFileName(file
);
118 audiofile
.open(QIODevice::ReadOnly
);
119 fd
= audiofile
.handle();
120 if (audiofile_name
) free(audiofile_name
);
121 audiofile_name
= qstrdup(file
.toAscii().constData());
122 // Start thread running.
126 /*virtual*/ void AlsaPlayerThread::run()
128 QString pName
= m_pcmName
.section(" ", 0, 0);
129 pcm_name
= qstrdup(pName
.toAscii().constData());
130 DBG
<< "pName = " << pcm_name
<< endl
;
132 snd_pcm_info_t
*info
;
134 m_simulatedPause
= false;
136 snd_pcm_info_alloca(&info
);
138 err
= snd_output_stdio_attach(&log
, stderr
, 0);
141 rhwdata
.format
= DEFAULT_FORMAT
;
142 rhwdata
.rate
= DEFAULT_SPEED
;
143 rhwdata
.channels
= 1;
145 err
= snd_pcm_open(&handle
, pcm_name
, stream
, open_mode
);
147 kError() << "audio open error on pcm device " << pcm_name
<< ": " << snd_strerror(err
)
152 if ((err
= snd_pcm_info(handle
, info
)) < 0) {
153 kError() << "info error: " << snd_strerror(err
) << endl
;
160 audioBuffer
.resize(1024);
161 // audiobuf = (char *)malloc(1024);
162 audiobuf
= audioBuffer
.data();
163 if (audiobuf
== NULL
)
167 writei_func
= snd_pcm_mmap_writei
;
168 readi_func
= snd_pcm_mmap_readi
;
169 writen_func
= snd_pcm_mmap_writen
;
170 readn_func
= snd_pcm_mmap_readn
;
172 writei_func
= snd_pcm_writei
;
173 readi_func
= snd_pcm_readi
;
174 writen_func
= snd_pcm_writen
;
175 readn_func
= snd_pcm_readn
;
184 void AlsaPlayerThread::pause()
187 kDebug() << "Pause requested";
188 QMutexLocker
locker(&m_mutex
);
190 // Some hardware can pause; some can't. canPause is set in set_params.
192 m_simulatedPause
= false;
193 snd_pcm_pause(handle
, true);
195 // Set a flag and cause wait_for_poll to sleep. When resumed, will get
197 m_simulatedPause
= true;
203 void AlsaPlayerThread::stop()
206 DBG
<< "STOP! Locking mutex" << endl
;
207 QMutexLocker
locker(&m_mutex
);
208 m_simulatedPause
= false;
210 /* This constant is arbitrary */
212 DBG
<< "Request for stop, device state is "
213 << snd_pcm_state_name(snd_pcm_state(handle
)) << endl
;
214 write(alsa_stop_pipe
[1], &buf
, 1);
216 DBG
<< "unlocking mutex" << endl
;
218 /* Wait for thread to exit */
219 DBG
<< "waiting for thread to exit" << endl
;
221 DBG
<< "cleaning up" << endl
;
222 // TODO: This seems like a bug. Why must I relock the locker
223 // since I'm about to destroy the locker?
230 * Stop playback, cleanup and exit thread.
232 void AlsaPlayerThread::stopAndExit()
234 // if (handle) snd_pcm_drop(handle);
239 void AlsaPlayerThread::setVolume(float volume
)
241 m_currentVolume
= volume
;
244 float AlsaPlayerThread::volume() const
246 return m_currentVolume
;
249 /////////////////////////////////////////////////////////////////////////////////
250 // player status functions
251 /////////////////////////////////////////////////////////////////////////////////
253 bool AlsaPlayerThread::playing() const
257 QMutexLocker
locker(&m_mutex
);
260 snd_pcm_status_t
*status
;
261 snd_pcm_status_alloca(&status
);
263 if ((res
= snd_pcm_status(handle
, status
)) < 0)
264 kError() << "status error: " << snd_strerror(res
) << endl
;
266 result
= (SND_PCM_STATE_RUNNING
== snd_pcm_status_get_state(status
))
267 || (SND_PCM_STATE_DRAINING
== snd_pcm_status_get_state(status
));
268 DBG
<< "state = " << snd_pcm_state_name(snd_pcm_status_get_state(status
)) <<
272 result
= !m_simulatedPause
;
278 bool AlsaPlayerThread::paused() const
282 QMutexLocker
locker(&m_mutex
);
285 snd_pcm_status_t
*status
;
286 snd_pcm_status_alloca(&status
);
288 if ((res
= snd_pcm_status(handle
, status
)) < 0)
289 kError() << "status error: " << snd_strerror(res
) << endl
;
291 result
= (SND_PCM_STATE_PAUSED
== snd_pcm_status_get_state(status
));
292 DBG
<< "state = " << snd_pcm_state_name(snd_pcm_status_get_state(status
)) <<
296 result
= m_simulatedPause
;
302 int AlsaPlayerThread::totalTime() const
305 int rate
= hwdata
.rate
;
306 int channels
= hwdata
.channels
;
307 if (rate
> 0 && channels
> 0) {
308 total
= int((double(pbrec_count
) / rate
) / channels
);
309 // DBG("pbrec_count = %i rate =%i channels = %i", pbrec_count, rate, channels);
310 // DBG("totalTime = %i", total);
315 int AlsaPlayerThread::currentTime() const
318 int rate
= hwdata
.rate
;
319 int channels
= hwdata
.channels
;
320 if (rate
> 0 && channels
> 0) {
321 current
= int((double(fdcount
) / rate
) / channels
);
322 // DBG("fdcount = %i rate = %i channels = %i", fdcount, rate, channels);
323 // DBG("currentTime = %i", current);
328 int AlsaPlayerThread::position() const
330 // TODO: Make this more accurate by adding frames that have been so-far
331 // played within the Alsa ring buffer.
332 return pbrec_count
> 0 ? int(double(fdcount
) * 1000 / pbrec_count
+ .5) : 0;
335 /////////////////////////////////////////////////////////////////////////////////
336 // player seek functions
337 /////////////////////////////////////////////////////////////////////////////////
339 void AlsaPlayerThread::seek(int /*seekTime*/)
344 void AlsaPlayerThread::seekPosition(int /*position*/)
350 * Returns a list of PCM devices.
351 * This function fills the specified list with ALSA hardware soundcards found on the system.
352 * It uses plughw:xx instead of hw:xx for specifiers, because hw:xx are not practical to
353 * use (e.g. they require a resampler/channel mixer in the application).
355 QStringList
AlsaPlayerThread::getPluginList( const QByteArray
& classname
)
360 int card
= -1, device
= -1;
362 snd_ctl_card_info_t
*info
;
363 snd_pcm_info_t
*pcminfo
;
364 snd_ctl_card_info_alloca(&info
);
365 snd_pcm_info_alloca(&pcminfo
);
368 result
.append("default");
370 err
= snd_card_next(&card
);
371 if (err
< 0 || card
< 0) break;
374 sprintf(name
, "hw:%i", card
);
375 if ((err
= snd_ctl_open(&handle
, name
, 0)) < 0) continue;
376 if ((err
= snd_ctl_card_info(handle
, info
)) < 0) {
377 snd_ctl_close(handle
);
380 for (int devCnt
=0;;++devCnt
) {
381 err
= snd_ctl_pcm_next_device(handle
, &device
);
382 if (err
< 0 || device
< 0) break;
384 snd_pcm_info_set_device(pcminfo
, device
);
385 snd_pcm_info_set_subdevice(pcminfo
, 0);
386 snd_pcm_info_set_stream(pcminfo
, SND_PCM_STREAM_PLAYBACK
);
387 if ((err
= snd_ctl_pcm_info(handle
, pcminfo
)) < 0) continue;
388 QString infoName
= " ";
389 infoName
+= snd_ctl_card_info_get_name(info
);
391 infoName
+= snd_pcm_info_get_name(pcminfo
);
394 QString pcmName
= QString("default:%1").arg(card
);
395 result
.append(pcmName
+ infoName
);
397 QString pcmName
= QString("plughw:%1,%2").arg(card
).arg(device
);
398 result
.append(pcmName
+ infoName
);
400 snd_ctl_close(handle
);
406 void AlsaPlayerThread::setSinkName(const QString
& sinkName
) { m_pcmName
= sinkName
; }
408 /////////////////////////////////////////////////////////////////////////////////
410 /////////////////////////////////////////////////////////////////////////////////
412 void AlsaPlayerThread::init()
419 file_type
= FORMAT_DEFAULT
;
422 open_mode
= SND_PCM_NONBLOCK
;
423 stream
= SND_PCM_STREAM_PLAYBACK
;
436 pbrec_count
= LLONG_MAX
;
437 alsa_stop_pipe
[0] = 0;
438 alsa_stop_pipe
[1] = 0;
440 m_simulatedPause
= false;
443 void AlsaPlayerThread::cleanup()
445 DBG
<< "cleaning up" << endl
;
446 QMutexLocker
locker(&m_mutex
);
447 if (pcm_name
) free(pcm_name
);
448 if (audiofile_name
) free(audiofile_name
);
449 if (fd
>= 0) audiofile
.close();
451 snd_pcm_drop(handle
);
452 snd_pcm_close(handle
);
454 if (alsa_stop_pipe
[0]) close(alsa_stop_pipe
[0]);
455 if (alsa_stop_pipe
[1]) close(alsa_stop_pipe
[1]);
456 if (audiobuf
) audioBuffer
.resize(0);
457 if (alsa_poll_fds
) alsa_poll_fds_barray
.resize(0);
458 if (log
) snd_output_close(log
);
459 snd_config_update_free_global();
464 * Safe read (for pipes)
467 ssize_t
AlsaPlayerThread::safe_read(int fd
, void *buf
, size_t count
)
473 if ((res
= read(fd
, buf
, count
)) == 0)
476 return result
> 0 ? result
: res
;
479 buf
= (char *)buf
+ res
;
485 * Test, if it is a .VOC file and return >=0 if ok (this is the length of rest)
488 int AlsaPlayerThread::test_vocfile(void *buffer
)
490 VocHeader
*vp
= (VocHeader
*)buffer
;
492 if (!memcmp(vp
->magic
, VOC_MAGIC_STRING
, 20)) {
493 vocminor
= LE_SHORT(vp
->version
) & 0xFF;
494 vocmajor
= LE_SHORT(vp
->version
) / 256;
495 if (LE_SHORT(vp
->version
) != (0x1233 - LE_SHORT(vp
->coded_ver
)))
496 return -2; /* coded version mismatch */
497 return LE_SHORT(vp
->headerlen
) - sizeof(VocHeader
); /* 0 mostly */
499 return -1; /* magic string fail */
503 * helper for test_wavefile
506 ssize_t
AlsaPlayerThread::test_wavefile_read(int fd
, char *buffer
, size_t *size
, size_t reqsize
, int line
)
508 if (*size
>= reqsize
)
510 if ((size_t)safe_read(fd
, buffer
+ *size
, reqsize
- *size
) != reqsize
- *size
) {
511 kError() << "read error (called from line " << line
<< endl
;
514 return *size
= reqsize
;
517 #define check_wavefile_space(buffer, len, blimit) \
518 if (len > blimit) { \
520 if ((buffer = (char*)realloc(buffer, blimit)) == NULL) { \
526 * test, if it's a .WAV file, > 0 if ok (and set the speed, stereo etc.)
528 * Value returned is bytes to be discarded.
530 ssize_t
AlsaPlayerThread::test_wavefile(int fd
, char *_buffer
, size_t size
)
532 WaveHeader
*h
= (WaveHeader
*)_buffer
;
540 if (size
< sizeof(WaveHeader
))
542 if (h
->magic
!= WAV_RIFF
|| h
->type
!= WAV_WAVE
)
544 if (size
> sizeof(WaveHeader
)) {
545 check_wavefile_space(buffer
, size
- sizeof(WaveHeader
), blimit
);
546 memcpy(buffer
, _buffer
+ sizeof(WaveHeader
), size
- sizeof(WaveHeader
));
548 size
-= sizeof(WaveHeader
);
550 check_wavefile_space(buffer
, sizeof(WaveChunkHeader
), blimit
);
551 test_wavefile_read(fd
, buffer
, &size
, sizeof(WaveChunkHeader
), __LINE__
);
552 c
= (WaveChunkHeader
*)buffer
;
554 len
= LE_INT(c
->length
);
556 if (size
> sizeof(WaveChunkHeader
))
557 memmove(buffer
, buffer
+ sizeof(WaveChunkHeader
), size
- sizeof(WaveChunkHeader
));
558 size
-= sizeof(WaveChunkHeader
);
561 check_wavefile_space(buffer
, len
, blimit
);
562 test_wavefile_read(fd
, buffer
, &size
, len
, __LINE__
);
564 memmove(buffer
, buffer
+ len
, size
- len
);
568 if (len
< sizeof(WaveFmtBody
)) {
569 kError() << "unknown length of 'fmt ' chunk (read " << len
<< ", should be "
570 << sizeof(WaveFmtBody
) << " at least" << endl
;
573 check_wavefile_space(buffer
, len
, blimit
);
574 test_wavefile_read(fd
, buffer
, &size
, len
, __LINE__
);
575 f
= (WaveFmtBody
*) buffer
;
576 if (LE_SHORT(f
->format
) != WAV_PCM_CODE
) {
577 kError() << "can't play not PCM-coded WAVE-files" << endl
;
580 if (LE_SHORT(f
->modus
) < 1) {
581 kError() << "can't play WAVE-files with " << LE_SHORT (f
->modus
) << " tracks" << endl
;
584 hwdata
.channels
= LE_SHORT(f
->modus
);
585 switch (LE_SHORT(f
->bit_p_spl
)) {
587 if (hwdata
.format
!= DEFAULT_FORMAT
&&
588 hwdata
.format
!= SND_PCM_FORMAT_U8
)
589 kDebug() << "Warning: format is changed to U8";
590 hwdata
.format
= SND_PCM_FORMAT_U8
;
593 if (hwdata
.format
!= DEFAULT_FORMAT
&&
594 hwdata
.format
!= SND_PCM_FORMAT_S16_LE
)
595 kDebug() << "Warning: format is changed to S16_LE";
596 hwdata
.format
= SND_PCM_FORMAT_S16_LE
;
599 switch (LE_SHORT(f
->byte_p_spl
) / hwdata
.channels
) {
601 if (hwdata
.format
!= DEFAULT_FORMAT
&&
602 hwdata
.format
!= SND_PCM_FORMAT_S24_3LE
)
603 kDebug() << "Warning: format is changed to S24_3LE";
604 hwdata
.format
= SND_PCM_FORMAT_S24_3LE
;
607 if (hwdata
.format
!= DEFAULT_FORMAT
&&
608 hwdata
.format
!= SND_PCM_FORMAT_S24_LE
)
609 kDebug() << "Warning: format is changed to S24_LE";
610 hwdata
.format
= SND_PCM_FORMAT_S24_LE
;
613 kError () << "can not play WAVE-files with sample " <<
614 LE_SHORT (f
->bit_p_spl
) << " bits in " << LE_SHORT (f
->byte_p_spl
)
615 << "(" << hwdata
.channels
<< " channels)" << endl
;
620 hwdata
.format
= SND_PCM_FORMAT_S32_LE
;
623 kError() << "can't play WAVE-files with sample "
624 << LE_SHORT (f
->bit_p_spl
) << endl
;
627 hwdata
.rate
= LE_INT(f
->sample_fq
);
630 memmove(buffer
, buffer
+ len
, size
- len
);
636 check_wavefile_space(buffer
, sizeof(WaveChunkHeader
), blimit
);
637 test_wavefile_read(fd
, buffer
, &size
, sizeof(WaveChunkHeader
), __LINE__
);
638 c
= (WaveChunkHeader
*)buffer
;
640 len
= LE_INT(c
->length
);
641 if (size
> sizeof(WaveChunkHeader
))
642 memmove(buffer
, buffer
+ sizeof(WaveChunkHeader
), size
- sizeof(WaveChunkHeader
));
643 size
-= sizeof(WaveChunkHeader
);
644 if (type
== WAV_DATA
) {
645 if (len
< pbrec_count
&& len
< 0x7ffffffe)
648 memcpy(_buffer
, buffer
, size
);
653 check_wavefile_space(buffer
, len
, blimit
);
654 test_wavefile_read(fd
, buffer
, &size
, len
, __LINE__
);
656 memmove(buffer
, buffer
+ len
, size
- len
);
660 /* shouldn't be reached */
668 int AlsaPlayerThread::test_au(int fd
, char *buffer
)
670 AuHeader
*ap
= (AuHeader
*)buffer
;
672 if (ap
->magic
!= AU_MAGIC
)
674 if (BE_INT(ap
->hdr_size
) > 128 || BE_INT(ap
->hdr_size
) < 24)
676 pbrec_count
= BE_INT(ap
->data_size
);
677 switch (BE_INT(ap
->encoding
)) {
679 if (hwdata
.format
!= DEFAULT_FORMAT
&&
680 hwdata
.format
!= SND_PCM_FORMAT_MU_LAW
)
681 kDebug() << "Warning: format is changed to MU_LAW";
682 hwdata
.format
= SND_PCM_FORMAT_MU_LAW
;
685 if (hwdata
.format
!= DEFAULT_FORMAT
&&
686 hwdata
.format
!= SND_PCM_FORMAT_U8
)
687 kDebug() << "Warning: format is changed to U8";
688 hwdata
.format
= SND_PCM_FORMAT_U8
;
691 if (hwdata
.format
!= DEFAULT_FORMAT
&&
692 hwdata
.format
!= SND_PCM_FORMAT_S16_BE
)
693 kDebug() << "Warning: format is changed to S16_BE";
694 hwdata
.format
= SND_PCM_FORMAT_S16_BE
;
699 hwdata
.rate
= BE_INT(ap
->sample_rate
);
700 if (hwdata
.rate
< 2000 || hwdata
.rate
> 256000)
702 hwdata
.channels
= BE_INT(ap
->channels
);
703 if (hwdata
.channels
< 1 || hwdata
.channels
> 128)
705 if ((size_t)safe_read(fd
, buffer
+ sizeof(AuHeader
), BE_INT(ap
->hdr_size
) - sizeof(AuHeader
)) != BE_INT(ap
->hdr_size
) - sizeof(AuHeader
)) {
706 kError() << "read error" << endl
;
712 void AlsaPlayerThread::set_params(void)
714 snd_pcm_hw_params_t
*hwparams
;
715 snd_pcm_uframes_t period_size
;
719 unsigned int periods
;
721 snd_pcm_hw_params_alloca(&hwparams
);
722 err
= snd_pcm_hw_params_any(handle
, hwparams
);
724 kError() << "Broken configuration for this PCM: no configurations available" << endl
;
728 /* Create the pipe for communication about stop requests. */
729 if (pipe(alsa_stop_pipe
)) {
730 kError() << "Stop pipe creation failed (" << strerror(errno
) << ")" << endl
;
734 /* Find how many descriptors we will get for poll(). */
735 alsa_fd_count
= snd_pcm_poll_descriptors_count(handle
);
736 if (alsa_fd_count
<= 0) {
737 kError() << "Invalid poll descriptors count returned from ALSA." << endl
;
741 /* Create and fill in struct pollfd *alsa_poll_fds with ALSA descriptors. */
742 // alsa_poll_fds = (pollfd *)malloc ((alsa_fd_count + 1) * sizeof(struct pollfd));
743 alsa_poll_fds_barray
.resize((alsa_fd_count
+ 1) * sizeof(struct pollfd
));
744 alsa_poll_fds
= (pollfd
*)alsa_poll_fds_barray
.data();
745 assert(alsa_poll_fds
);
746 if ((err
= snd_pcm_poll_descriptors(handle
, alsa_poll_fds
, alsa_fd_count
)) < 0) {
747 kError() << "Unable to obtain poll descriptors for playback: " << snd_strerror(err
) << endl
;
751 /* Create a new pollfd structure for requests by alsa_stop(). */
752 struct pollfd alsa_stop_pipe_pfd
;
753 alsa_stop_pipe_pfd
.fd
= alsa_stop_pipe
[0];
754 alsa_stop_pipe_pfd
.events
= POLLIN
;
755 alsa_stop_pipe_pfd
.revents
= 0;
757 /* Join this our own pollfd to the ALSAs ones. */
758 alsa_poll_fds
[alsa_fd_count
] = alsa_stop_pipe_pfd
;
762 snd_pcm_access_mask_t
*mask
= (snd_pcm_access_mask_t
*)alloca(snd_pcm_access_mask_sizeof());
763 snd_pcm_access_mask_none(mask
);
764 snd_pcm_access_mask_set(mask
, SND_PCM_ACCESS_MMAP_INTERLEAVED
);
765 snd_pcm_access_mask_set(mask
, SND_PCM_ACCESS_MMAP_NONINTERLEAVED
);
766 snd_pcm_access_mask_set(mask
, SND_PCM_ACCESS_MMAP_COMPLEX
);
767 err
= snd_pcm_hw_params_set_access_mask(handle
, hwparams
, mask
);
768 } else if (interleaved
)
769 err
= snd_pcm_hw_params_set_access(handle
, hwparams
,
770 SND_PCM_ACCESS_RW_INTERLEAVED
);
772 err
= snd_pcm_hw_params_set_access(handle
, hwparams
,
773 SND_PCM_ACCESS_RW_NONINTERLEAVED
);
775 kError() << "Error setting access type: " << snd_strerror(err
) << endl
;
778 err
= snd_pcm_hw_params_set_format(handle
, hwparams
, hwdata
.format
);
780 kError() << "Error setting sample format to " <<
781 hwdata
.format
<< ": " << snd_strerror(err
) << endl
;
784 err
= snd_pcm_hw_params_set_channels(handle
, hwparams
, hwdata
.channels
);
786 kError() << "Error setting channel count to "
787 << hwdata
.channels
<< ": " << snd_strerror(err
) << endl
;
792 err
= snd_pcm_hw_params_set_periods_min(handle
, hwparams
, 2);
796 err
= snd_pcm_hw_params_set_rate_near(handle
, hwparams
, &hwdata
.rate
, 0);
798 if ((float)rate
* 1.05 < hwdata
.rate
|| (float)rate
* 0.95 > hwdata
.rate
) {
799 kDebug() << "Warning: rate is not accurate (requested = " << rate
<< "Hz, got = " <<
800 hwdata
.rate
<< ")" << endl
;
801 kDebug() << " please, try the plug plugin (-Dplug:" << snd_pcm_name
<< ")";
804 period_size
= m_defPeriodSize
;
806 err
= snd_pcm_hw_params_set_period_size_near(handle
, hwparams
, &period_size
, &dir
);
808 kDebug() << "Setting period_size to " << period_size
<< " failed, but continuing: "
809 << snd_strerror(err
);
812 periods
= m_defPeriods
;
814 err
= snd_pcm_hw_params_set_periods_near(handle
, hwparams
, &periods
, &dir
);
816 kDebug() << "Unable to set number of periods to " << periods
<< ", but continuing: "
817 << snd_strerror(err
);
819 /* Install hw parameters. */
820 err
= snd_pcm_hw_params(handle
, hwparams
);
822 kDebug() << "Unable to install hw params: " << snd_strerror(err
);
823 snd_pcm_hw_params_dump(hwparams
, log
);
827 /* Determine if device can pause. */
828 canPause
= (1 == snd_pcm_hw_params_can_pause(hwparams
));
830 /* Get final buffer size and calculate the chunk size we will pass to device. */
831 snd_pcm_hw_params_get_buffer_size(hwparams
, &buffer_size
);
832 chunk_size
= periods
* period_size
;
834 if (0 == chunk_size
) {
835 kError() << "Invalid periods or period_size. Cannot continue.";
839 if (chunk_size
== buffer_size
)
840 kDebug() << "WARNING: Should not use chunk_size equal to buffer_size (" << chunk_size
841 << "). Continuing anyway." ;
843 DBG
<< "Final buffer_size = " <<
844 buffer_size
<< " chunk_size = " << chunk_size
<< " periods = " << periods
845 << " period_size = " << period_size
<< " canPause = " << canPause
<< endl
;
847 if (m_debugLevel
>= 2)
848 snd_pcm_dump(handle
, log
);
850 bits_per_sample
= snd_pcm_format_physical_width(hwdata
.format
);
851 bits_per_frame
= bits_per_sample
* hwdata
.channels
;
852 chunk_bytes
= chunk_size
* bits_per_frame
/ 8;
853 audioBuffer
.resize(chunk_bytes
);
854 audiobuf
= audioBuffer
.data();
855 if (audiobuf
== NULL
) {
861 #define timersub(a, b, result) \
863 (result)->tv_sec = (a)->tv_sec - (b)->tv_sec; \
864 (result)->tv_usec = (a)->tv_usec - (b)->tv_usec; \
865 if ((result)->tv_usec < 0) { \
866 --(result)->tv_sec; \
867 (result)->tv_usec += 1000000; \
872 /* I/O error handler */
873 void AlsaPlayerThread::xrun()
875 snd_pcm_status_t
*status
;
878 snd_pcm_status_alloca(&status
);
879 if ((res
= snd_pcm_status(handle
, status
))<0) {
880 kError() << "status error: " << snd_strerror(res
) << endl
;
883 if (SND_PCM_STATE_XRUN
== snd_pcm_status_get_state(status
)) {
884 struct timeval now
, diff
, tstamp
;
885 gettimeofday(&now
, 0);
886 snd_pcm_status_get_trigger_tstamp(status
, &tstamp
);
887 timersub(&now
, &tstamp
, &diff
);
888 kDebug() << (stream
== SND_PCM_STREAM_PLAYBACK
? "underrun" : "overrun")
889 << "!!! at least " << diff
.tv_sec
* 1000 + diff
.tv_usec
/ 1000.0 << " ms long)" << endl
;
890 if (m_debugLevel
>= 2) {
891 DBG
<< "Status:" << snd_pcm_status_dump(status
, log
) << endl
;
893 if ((res
= snd_pcm_prepare(handle
))<0) {
894 kError() << "xrun: prepare error: " << snd_strerror(res
) << endl
;
897 return; /* ok, data should be accepted again */
898 } if (SND_PCM_STATE_DRAINING
== snd_pcm_status_get_state(status
)) {
899 if (m_debugLevel
>= 2) {
900 DBG
<< "Status(DRAINING):" << endl
;
901 snd_pcm_status_dump(status
, log
);
903 if (stream
== SND_PCM_STREAM_CAPTURE
) {
904 kDebug() << "capture stream format change? attempting recover...";
905 if ((res
= snd_pcm_prepare(handle
))<0) {
906 kError() << "xrun(DRAINING): prepare error: " << snd_strerror(res
) << endl
;
912 if (m_debugLevel
>= 2) {
913 DBG
<< "Status(R/W):" << endl
;
914 snd_pcm_status_dump(status
, log
);
916 kError() << "read/write error, state = " << snd_pcm_state_name(snd_pcm_status_get_state(status
))
921 /* I/O suspend handler */
922 void AlsaPlayerThread::suspend(void)
926 kDebug() << "Suspended. Trying resume. ";
927 while ((res
= snd_pcm_resume(handle
)) == -EAGAIN
)
928 sleep(1); /* wait until suspend flag is released */
930 kDebug() << "Failed. Restarting stream. ";
931 if ((res
= snd_pcm_prepare(handle
)) < 0) {
932 kError() << "suspend: prepare error: " << snd_strerror(res
) << endl
;
936 kDebug() << "Suspend done.";
940 void AlsaPlayerThread::compute_max_peak(char *data
, size_t count
)
942 signed int val
, max
, max_peak
= 0, perc
;
943 size_t ocount
= count
;
945 switch (bits_per_sample
) {
947 signed char *valp
= (signed char *)data
;
948 signed char mask
= snd_pcm_format_silence(hwdata
.format
);
949 while (count
-- > 0) {
950 val
= *valp
++ ^ mask
;
958 signed short *valp
= (signed short *)data
;
959 signed short mask
= snd_pcm_format_silence_16(hwdata
.format
);
961 while (count
-- > 0) {
962 val
= *valp
++ ^ mask
;
970 signed int *valp
= (signed int *)data
;
971 signed int mask
= snd_pcm_format_silence_32(hwdata
.format
);
973 while (count
-- > 0) {
974 val
= *valp
++ ^ mask
;
984 max
= 1 << (bits_per_sample
-1);
987 DBG
<< "Max peak (" << ocount
<< " samples): " << max_peak
<< " (0x"
988 << max_peak
<< ")" << endl
;
989 if (bits_per_sample
> 16)
990 perc
= max_peak
/ (max
/ 100);
992 perc
= max_peak
* 100 / max
;
993 for (val
= 0; val
< 20; ++val
)
998 DBG
<< perc
<< "%" << endl
;
1005 ssize_t
AlsaPlayerThread::pcm_write(char *data
, size_t count
)
1010 if (sleep_min
== 0 && count
< chunk_size
) {
1011 DBG
<< "calling snd_pcm_format_set_silence" << endl
;
1012 snd_pcm_format_set_silence(hwdata
.format
, data
+ count
* bits_per_frame
/ 8, (chunk_size
- count
) * hwdata
.channels
);
1016 DBG
<< "calling writei_func, count = " << count
<< endl
;
1017 r
= writei_func(handle
, data
, count
);
1018 DBG
<< "writei_func returned " << r
<< endl
;
1019 if (-EAGAIN
== r
|| (r
>= 0 && (size_t)r
< count
)) {
1020 DBG
<< "r = " << r
<< " calling snd_pcm_wait" << endl
;
1021 snd_pcm_wait(handle
, 100);
1022 } else if (-EPIPE
== r
) {
1024 } else if (-ESTRPIPE
== r
) {
1026 } else if (-EBUSY
== r
){
1027 kDebug() << "WARNING: sleeping while PCM BUSY";
1031 kError() << "write error: " << snd_strerror(r
);
1035 if (m_debugLevel
>= 2)
1036 compute_max_peak(data
, r
* hwdata
.channels
);
1039 data
+= r
* bits_per_frame
/ 8;
1041 /* Report current state */
1042 DBG
<< "PCM state before polling: " << snd_pcm_state_name(snd_pcm_state(handle
)) << endl
;
1044 int err
= wait_for_poll(0);
1046 kError() << "Wait for poll() failed" << endl
;
1050 kDebug () << "Playback stopped";
1051 /* Drop the playback on the sound device (probably
1052 still in progress up till now) */
1053 err
= snd_pcm_drop(handle
);
1055 kError() << "snd_pcm_drop() failed: " << snd_strerror(err
) << endl
;
1065 * ok, let's play a .voc file
1068 ssize_t
AlsaPlayerThread::voc_pcm_write(u_char
*data
, size_t count
)
1070 ssize_t result
= count
, r
;
1075 if (size
> chunk_bytes
- buffer_pos
)
1076 size
= chunk_bytes
- buffer_pos
;
1077 memcpy(audiobuf
+ buffer_pos
, data
, size
);
1081 if ((size_t)buffer_pos
== chunk_bytes
) {
1082 if ((size_t)(r
= pcm_write(audiobuf
, chunk_size
)) != chunk_size
)
1090 void AlsaPlayerThread::voc_write_silence(unsigned x
)
1095 QByteArray
buffer(chunk_bytes
, '\0');
1096 // buf = (char *) malloc(chunk_bytes);
1097 buf
= buffer
.data();
1099 kError() << "can't allocate buffer for silence";
1100 return; /* not fatal error */
1102 snd_pcm_format_set_silence(hwdata
.format
, buf
, chunk_size
* hwdata
.channels
);
1107 if (voc_pcm_write((u_char
*)buf
, l
) != (ssize_t
)l
) {
1108 kError() << "write error" << endl
;
1116 void AlsaPlayerThread::voc_pcm_flush(void)
1118 if (buffer_pos
> 0) {
1120 if (sleep_min
== 0) {
1121 if (snd_pcm_format_set_silence(hwdata
.format
, audiobuf
+ buffer_pos
, chunk_bytes
- buffer_pos
* 8 / bits_per_sample
) < 0)
1122 kDebug() << "voc_pcm_flush - silence error";
1125 b
= buffer_pos
* 8 / bits_per_frame
;
1127 if (pcm_write(audiobuf
, b
) != (ssize_t
)b
)
1128 kError() << "voc_pcm_flush error" << endl
;
1130 snd_pcm_drain(handle
);
1133 void AlsaPlayerThread::voc_play(int fd
, int ofs
, const char* name
)
1139 size_t nextblock
, in_buffer
;
1141 char was_extended
= 0, output
= 0;
1142 u_short
*sp
, repeat
= 0;
1144 off64_t filepos
= 0;
1146 #define COUNT(x) nextblock -= x; in_buffer -= x; data += x
1147 #define COUNT1(x) in_buffer -= x; data += x
1149 QByteArray
buffer(64 * 1024, '\0');
1150 // data = buf = (u_char *)malloc(64 * 1024);
1151 data
= buf
= (u_char
*)buffer
.data();
1156 kDebug() << "Playing Creative Labs Channel file '" << name
<< "'...";
1157 /* first we waste the rest of header, ugly but we don't need seek */
1158 while (ofs
> (ssize_t
)chunk_bytes
) {
1159 if ((size_t)safe_read(fd
, buf
, chunk_bytes
) != chunk_bytes
) {
1160 kError() << "read error" << endl
;
1166 if (safe_read(fd
, buf
, ofs
) != ofs
) {
1167 kError() << "read error" << endl
;
1171 hwdata
.format
= DEFAULT_FORMAT
;
1172 hwdata
.channels
= 1;
1173 hwdata
.rate
= DEFAULT_SPEED
;
1176 in_buffer
= nextblock
= 0;
1178 Fill_the_buffer
: /* need this for repeat */
1179 if (in_buffer
< 32) {
1180 /* move the rest of buffer to pos 0 and fill the buf up */
1182 memcpy(buf
, data
, in_buffer
);
1184 if ((l
= safe_read(fd
, buf
+ in_buffer
, chunk_bytes
- in_buffer
)) > 0)
1186 else if (!in_buffer
) {
1187 /* the file is truncated, so simulate 'Terminator'
1188 and reduce the datablock for safe landing */
1189 nextblock
= buf
[0] = 0;
1196 while (!nextblock
) { /* this is a new block */
1197 if (in_buffer
< sizeof(VocBlockType
))
1199 bp
= (VocBlockType
*) data
;
1200 COUNT1(sizeof(VocBlockType
));
1201 nextblock
= VOC_DATALEN(bp
);
1208 return; /* VOC-file stop */
1210 vd
= (VocVoiceData
*) data
;
1211 COUNT1(sizeof(VocVoiceData
));
1212 /* we need a SYNC, before we can set new SPEED, STEREO ... */
1214 if (!was_extended
) {
1215 hwdata
.rate
= (int) (vd
->tc
);
1216 hwdata
.rate
= 1000000 / (256 - hwdata
.rate
);
1218 MSG("Channel data %d Hz", dsp_speed
);
1220 if (vd
->pack
) { /* /dev/dsp can't it */
1221 kError() << "can't play packed .voc files" << endl
;
1224 if (hwdata
.channels
== 2) /* if we are in Stereo-Mode, switch back */
1225 hwdata
.channels
= 1;
1226 } else { /* there was extended block */
1227 hwdata
.channels
= 2;
1232 case 2: /* nothing to do, pure data */
1234 MSG("Channel continuation");
1237 case 3: /* a silence block, no data, only a count */
1238 sp
= (u_short
*) data
;
1239 COUNT1(sizeof(u_short
));
1240 hwdata
.rate
= (int) (*data
);
1242 hwdata
.rate
= 1000000 / (256 - hwdata
.rate
);
1244 silence
= (((size_t) * sp
) * 1000) / hwdata
.rate
;
1246 MSG("Silence for %d ms", (int) silence
);
1248 voc_write_silence(*sp
);
1250 case 4: /* a marker for syncronisation, no effect */
1251 sp
= (u_short
*) data
;
1252 COUNT1(sizeof(u_short
));
1254 MSG("Marker %d", *sp
);
1257 case 5: /* ASCII text, we copy to stderr */
1260 MSG("ASCII - text :");
1263 case 6: /* repeat marker, says repeatcount */
1264 /* my specs don't say it: maybe this can be recursive, but
1265 I don't think somebody use it */
1266 repeat
= *(u_short
*) data
;
1267 COUNT1(sizeof(u_short
));
1269 MSG("Repeat loop %d times", repeat
);
1271 if (filepos
>= 0) { /* if < 0, one seek fails, why test another */
1272 if ((filepos
= lseek64(fd
, 0, 1)) < 0) {
1273 kError() << "can't play loops; " << name
<< "isn't seekable";
1276 filepos
-= in_buffer
; /* set filepos after repeat */
1282 case 7: /* ok, lets repeat that be rewinding tape */
1284 if (repeat
!= 0xFFFF) {
1286 MSG("Repeat loop %d", repeat
);
1292 MSG("Neverending loop");
1294 lseek64(fd
, filepos
, 0);
1295 in_buffer
= 0; /* clear the buffer */
1296 goto Fill_the_buffer
;
1300 MSG("End repeat loop");
1303 case 8: /* the extension to play Stereo, I have SB 1.0 :-( */
1305 eb
= (VocExtBlock
*) data
;
1306 COUNT1(sizeof(VocExtBlock
));
1307 hwdata
.rate
= (int) (eb
->tc
);
1308 hwdata
.rate
= 256000000L / (65536 - hwdata
.rate
);
1309 hwdata
.channels
= eb
->mode
== VOC_MODE_STEREO
? 2 : 1;
1310 if (hwdata
.channels
== 2)
1311 hwdata
.rate
= hwdata
.rate
>> 1;
1312 if (eb
->pack
) { /* /dev/dsp can't it */
1313 kError() << "can't play packed .voc files" << endl
;
1317 MSG("Extended block %s %d Hz",
1318 (eb
->mode
? "Stereo" : "Mono"), dsp_speed
);
1322 kError() << "unknown blocktype " << bp
->type
<< ". terminate." << endl
;
1324 } /* switch (bp->type) */
1325 } /* while (! nextblock) */
1326 /* put nextblock data bytes to dsp */
1328 if (nextblock
< (size_t)l
)
1332 if (write(2, data
, l
) != l
) { /* to stderr */
1333 kError() << "write error";
1337 if (voc_pcm_write(data
, l
) != l
) {
1338 kError() << "write error" << endl
;
1349 /* that was a big one, perhaps somebody split it :-) */
1351 /* setting the globals for playing raw data */
1352 void AlsaPlayerThread::init_raw_data(void)
1357 /* calculate the data count to read from/to dsp */
1358 off64_t
AlsaPlayerThread::calc_count(void)
1362 if (timelimit
== 0) {
1363 count
= pbrec_count
;
1365 count
= snd_pcm_format_size(hwdata
.format
, hwdata
.rate
* hwdata
.channels
);
1366 count
*= (off64_t
)timelimit
;
1368 return count
< pbrec_count
? count
: pbrec_count
;
1371 void AlsaPlayerThread::header(int /*rtype*/, const char* /*name*/)
1373 // fprintf(stderr, "%s %s '%s' : ",
1374 // (stream == SND_PCM_STREAM_PLAYBACK) ? "Playing" : "Recording",
1375 // fmt_rec_table[rtype].what,
1378 if (hwdata
.channels
== 1)
1380 else if (hwdata
.channels
== 2)
1381 channels
= "Stereo";
1383 channels
= QString("Channels %1").arg(hwdata
.channels
);
1384 QByteArray asciiChannels
= channels
.toAscii();
1386 << snd_pcm_format_description(hwdata
.format
)
1390 << asciiChannels
.constData() << endl
;
1393 /* playing raw data */
1395 void AlsaPlayerThread::playback_go(int fd
, size_t loaded
, off64_t count
, int rtype
, const char *name
)
1398 off64_t written
= 0;
1401 if (m_debugLevel
>= 1) header(rtype
, name
);
1404 while (loaded
> chunk_bytes
&& written
< count
) {
1405 if (pcm_write(audiobuf
+ written
, chunk_size
) <= 0)
1407 written
+= chunk_bytes
;
1408 loaded
-= chunk_bytes
;
1410 if (written
> 0 && loaded
> 0)
1411 memmove(audiobuf
, audiobuf
+ written
, loaded
);
1414 while (written
< count
) {
1416 c
= count
- written
;
1417 if (c
> chunk_bytes
)
1423 r
= safe_read(fd
, audiobuf
+ l
, c
);
1432 } while (sleep_min
== 0 && (size_t)l
< chunk_bytes
);
1433 l
= l
* 8 / bits_per_frame
;
1434 DBG
<< "calling pcm_write with " << l
<< " frames." << endl
;
1435 r
= pcm_write(audiobuf
, l
);
1436 DBG
<< "pcm_write returned r = " << r
<< endl
;
1440 r
= r
* bits_per_frame
/ 8;
1445 DBG
<< "Draining..." << endl
;
1447 /* We want the next "device ready" notification only when the buffer is completely empty. */
1448 /* Do this by setting the avail_min to the buffer size. */
1450 DBG
<< "Getting swparams" << endl
;
1451 snd_pcm_sw_params_t
*swparams
;
1452 snd_pcm_sw_params_alloca(&swparams
);
1453 err
= snd_pcm_sw_params_current(handle
, swparams
);
1455 kError() << "Unable to get current swparams: " << snd_strerror(err
) << endl
;
1458 DBG
<< "Setting avail min to " << buffer_size
<< endl
;
1459 err
= snd_pcm_sw_params_set_avail_min(handle
, swparams
, buffer_size
);
1461 kError() << "Unable to set avail min for playback: " << snd_strerror(err
) << endl
;
1464 /* write the parameters to the playback device */
1465 DBG
<< "Writing swparams" << endl
;
1466 err
= snd_pcm_sw_params(handle
, swparams
);
1468 kError() << "Unable to set sw params for playback: " << snd_strerror(err
) << endl
;
1472 DBG
<< "Waiting for poll" << endl
;
1473 err
= wait_for_poll(1);
1475 kError() << "Wait for poll() failed" << endl
;
1477 } else if (err
== 1){
1478 kError() << "Playback stopped while draining" << endl
;
1480 /* Drop the playback on the sound device (probably
1481 still in progress up till now) */
1482 err
= snd_pcm_drop(handle
);
1484 kError() << "snd_pcm_drop() failed: " << snd_strerror(err
) << endl
;
1488 DBG
<< "Draining completed" << endl
;
1492 * let's play or capture it (capture_type says VOC/WAVE/raw)
1495 void AlsaPlayerThread::playback(int fd
)
1501 pbrec_count
= LLONG_MAX
;
1504 /* read the file header */
1505 dta
= sizeof(AuHeader
);
1506 if ((size_t)safe_read(fd
, audiobuf
, dta
) != dta
) {
1507 kError() << "read error" << endl
;
1510 if (test_au(fd
, audiobuf
) >= 0) {
1511 rhwdata
.format
= hwdata
.format
;
1512 pbrec_count
= calc_count();
1513 playback_go(fd
, 0, pbrec_count
, FORMAT_AU
, audiofile_name
);
1516 dta
= sizeof(VocHeader
);
1517 if ((size_t)safe_read(fd
, audiobuf
+ sizeof(AuHeader
),
1518 dta
- sizeof(AuHeader
)) != dta
- sizeof(AuHeader
)) {
1519 kError() << "read error" << endl
;
1522 if ((ofs
= test_vocfile(audiobuf
)) >= 0) {
1523 pbrec_count
= calc_count();
1524 voc_play(fd
, ofs
, audiofile_name
);
1527 /* read bytes for WAVE-header */
1528 if ((dtawave
= test_wavefile(fd
, audiobuf
, dta
)) >= 0) {
1529 pbrec_count
= calc_count();
1530 playback_go(fd
, dtawave
, pbrec_count
, FORMAT_WAVE
, audiofile_name
);
1532 /* should be raw data */
1534 pbrec_count
= calc_count();
1535 playback_go(fd
, dta
, pbrec_count
, FORMAT_RAW
, audiofile_name
);
1541 /* Wait until ALSA is ready for more samples or stop() was called.
1542 @return 0 if ALSA is ready for more input, +1 if a request to stop
1543 the sound output was received and a negative value on error. */
1544 int AlsaPlayerThread::wait_for_poll(int draining
)
1546 unsigned short revents
;
1547 snd_pcm_state_t state
;
1550 DBG
<< "Waiting for poll" << endl
;
1552 /* Wait for certain events */
1554 /* Simulated pause by not writing to alsa device, which will lead to an XRUN
1556 if (m_simulatedPause
)
1560 ret
= poll(alsa_poll_fds
, alsa_fd_count
, -1);
1561 DBG
<< "activity on " << ret
<< " descriptors" << endl
;
1563 /* Check for stop request from alsa_stop on the last file descriptors. */
1564 if ((revents
= alsa_poll_fds
[alsa_fd_count
-1].revents
)) {
1565 if (revents
& POLLIN
){
1566 DBG
<< "stop requested" << endl
;
1571 /* Check the first count-1 descriptors for ALSA events */
1572 snd_pcm_poll_descriptors_revents(handle
, alsa_poll_fds
, alsa_fd_count
-1, &revents
);
1574 /* Ensure we are in the right state */
1575 state
= snd_pcm_state(handle
);
1576 DBG
<< "State after poll returned is " << snd_pcm_state_name(state
);
1578 if (SND_PCM_STATE_XRUN
== state
){
1580 kDebug() << "WARNING: Buffer underrun detected!";
1584 DBG
<< "Playback terminated" << endl
;
1589 if (SND_PCM_STATE_SUSPENDED
== state
){
1590 DBG
<< "WARNING: Suspend detected!" << endl
;
1595 /* Check for errors */
1596 if (revents
& POLLERR
) {
1597 DBG
<< "poll revents says POLLERR" << endl
;
1601 /* Is ALSA ready for more input? */
1602 if ((revents
& POLLOUT
)){
1603 DBG
<< "Ready for more input" << endl
;
1610 // ====================================================================
1613 // AlsaPlayer is nothing more than a container for AlsaPlayerThread
1614 // in order to avoid ambiguous QObject, since both Player and QThread
1615 // derive from QObject. Is there a better solution?
1617 AlsaPlayer::AlsaPlayer(QObject
* parent
, const QStringList
& args
):
1618 Player(parent
, "alsaplayer", args
)
1620 m_AlsaPlayerThread
= new AlsaPlayerThread(this);
1623 AlsaPlayer::~AlsaPlayer()
1625 delete m_AlsaPlayerThread
;
1628 /*virtual*/ void AlsaPlayer::startPlay(const QString
& file
) { m_AlsaPlayerThread
->startPlay(file
); }
1629 /*virtual*/ void AlsaPlayer::pause() { m_AlsaPlayerThread
->pause(); }
1630 /*virtual*/ void AlsaPlayer::stop() { m_AlsaPlayerThread
->stop(); }
1632 /*virtual*/ void AlsaPlayer::setVolume(float volume
) { m_AlsaPlayerThread
->setVolume(volume
); }
1633 /*virtual*/ float AlsaPlayer::volume() const { return m_AlsaPlayerThread
->volume(); }
1635 /*virtual*/ bool AlsaPlayer::playing() const { return m_AlsaPlayerThread
->playing(); }
1636 /*virtual*/ bool AlsaPlayer::paused() const { return m_AlsaPlayerThread
->paused(); }
1638 /*virtual*/ int AlsaPlayer::totalTime() const { return m_AlsaPlayerThread
->totalTime(); }
1639 /*virtual*/ int AlsaPlayer::currentTime() const { return m_AlsaPlayerThread
->currentTime(); }
1640 /*virtual*/ int AlsaPlayer::position() const { return m_AlsaPlayerThread
->position(); } // in this case not really the percent
1642 /*virtual*/ void AlsaPlayer::seek(int seekTime
) { m_AlsaPlayerThread
->seek(seekTime
); }
1643 /*virtual*/ void AlsaPlayer::seekPosition(int position
) { m_AlsaPlayerThread
->seekPosition(position
); }
1645 /*virtual*/ QStringList
AlsaPlayer::getPluginList( const QByteArray
& classname
)
1646 { return m_AlsaPlayerThread
->getPluginList(classname
); }
1647 /*virtual*/ void AlsaPlayer::setSinkName(const QString
&sinkName
)
1648 { m_AlsaPlayerThread
->setSinkName(sinkName
); }
1650 #include "alsaplayer.moc"
1654 // vim: sw=4 ts=8 et