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 // #include <sys/wait.h>
30 #if TIME_WITH_SYS_TIME
31 # include <sys/time.h>
35 # include <sys/time.h>
43 #include <QApplication>
44 #include <QMutexLocker>
49 #include <kstandarddirs.h>
50 #include <kmessagebox.h>
53 // AlsaPlayer includes.
54 #include "alsaplayer.h"
56 #if !defined(__GNUC__) || __GNUC__ >= 3
57 #define ERR(...) do {\
59 QString s = dbgStr.sprintf( "%s:%d: ERROR ", __FUNCTION__, __LINE__); \
60 s += dbgStr.sprintf( __VA_ARGS__); \
61 kDebug() << timestamp() << "AlsaPlayer::" << s << endl; \
64 #define ERR(args...) do {\
66 QString s = dbgStr.sprintf( "%s:%d: ERROR ", __FUNCTION__, __LINE__); \
67 s += dbgStr.sprintf( ##args ); \
68 kDebug() << timestamp() << "AlsaPlayer::" << s << endl; \
71 #if !defined(__GNUC__) || __GNUC__ >= 3
72 #define MSG(...) do {\
73 if (m_debugLevel >= 1) {\
75 QString s = dbgStr.sprintf( "%s:%d: ", __FUNCTION__, __LINE__); \
76 s += dbgStr.sprintf( __VA_ARGS__); \
77 kDebug() << timestamp() << "AlsaPlayer::" << s << endl; \
81 #define MSG(args...) do {\
82 if (m_debugLevel >= 1) {\
84 QString s = dbgStr.sprintf( "%s:%d: ", __FUNCTION__, __LINE__); \
85 s += dbgStr.sprintf( ##args ); \
86 kDebug() << timestamp() << "AlsaPlayer::" << s << endl; \
91 #if !defined(__GNUC__) || __GNUC__ >= 3
92 #define DBG(...) do {\
93 if (m_debugLevel >= 2) {\
95 QString s = dbgStr.sprintf( "%s:%d: ", __FUNCTION__, __LINE__); \
96 s += dbgStr.sprintf( __VA_ARGS__); \
97 kDebug() << timestamp() << "AlsaPlayer::" << s << endl; \
101 #define DBG(args...) do {\
102 if (m_debugLevel >= 2) {\
104 QString s = dbgStr.sprintf( "%s:%d: ", __FUNCTION__, __LINE__); \
105 s += dbgStr.sprintf( ##args ); \
106 kDebug() << timestamp() << "AlsaPlayer::" << s << endl; \
111 QString
AlsaPlayerThread::timestamp() const
117 tstr
= strdup(ctime(&t
));
118 tstr
[strlen(tstr
)-1] = 0;
119 gettimeofday(&tv
,NULL
);
121 ts
.sprintf(" %s [%d] ",tstr
, (int) tv
.tv_usec
);
126 ////////////////////////////////////////////////////////////////////////////////
128 ////////////////////////////////////////////////////////////////////////////////
130 AlsaPlayerThread::AlsaPlayerThread(QObject
* parent
) :
132 m_currentVolume(1.0),
133 m_pcmName("default"),
134 m_defPeriodSize(128),
137 m_simulatedPause(false)
142 AlsaPlayerThread::~AlsaPlayerThread()
150 //void AlsaPlayerThread::play(const FileHandle &file)
151 void AlsaPlayerThread::startPlay(const QString
&file
)
156 snd_pcm_pause(handle
, false);
158 m_simulatedPause
= false;
162 audiofile
.setName(file
);
163 audiofile
.open(QIODevice::ReadOnly
);
164 fd
= audiofile
.handle();
165 // Start thread running.
169 /*virtual*/ void AlsaPlayerThread::run()
171 QString pName
= m_pcmName
.section(" ", 0, 0);
172 DBG("pName = %s", pName
.ascii());
173 pcm_name
= qstrdup(pName
.ascii());
175 snd_pcm_info_t
*info
;
177 m_simulatedPause
= false;
179 snd_pcm_info_alloca(&info
);
181 err
= snd_output_stdio_attach(&log
, stderr
, 0);
184 rhwdata
.format
= DEFAULT_FORMAT
;
185 rhwdata
.rate
= DEFAULT_SPEED
;
186 rhwdata
.channels
= 1;
188 err
= snd_pcm_open(&handle
, pcm_name
, stream
, open_mode
);
190 ERR("audio open error on pcm device %s: %s", pcm_name
, snd_strerror(err
));
194 if ((err
= snd_pcm_info(handle
, info
)) < 0) {
195 ERR("info error: %s", snd_strerror(err
));
202 audioBuffer
.resize(1024);
203 // audiobuf = (char *)malloc(1024);
204 audiobuf
= audioBuffer
.data();
205 if (audiobuf
== NULL
) {
206 ERR("not enough memory");
211 writei_func
= snd_pcm_mmap_writei
;
212 readi_func
= snd_pcm_mmap_readi
;
213 writen_func
= snd_pcm_mmap_writen
;
214 readn_func
= snd_pcm_mmap_readn
;
216 writei_func
= snd_pcm_writei
;
217 readi_func
= snd_pcm_readi
;
218 writen_func
= snd_pcm_writen
;
219 readn_func
= snd_pcm_readn
;
228 void AlsaPlayerThread::pause()
231 DBG("Pause requested");
232 QMutexLocker
locker(&m_mutex
);
234 // Some hardware can pause; some can't. canPause is set in set_params.
236 m_simulatedPause
= false;
237 snd_pcm_pause(handle
, true);
239 // Set a flag and cause wait_for_poll to sleep. When resumed, will get
241 m_simulatedPause
= true;
248 void AlsaPlayerThread::stop()
251 DBG("STOP! Locking mutex");
252 QMutexLocker
locker(&m_mutex
);
253 m_simulatedPause
= false;
255 /* This constant is arbitrary */
257 DBG("Request for stop, device state is %s",
258 snd_pcm_state_name(snd_pcm_state(handle
)));
259 write(alsa_stop_pipe
[1], &buf
, 1);
261 DBG("unlocking mutex");
263 /* Wait for thread to exit */
264 DBG("waiting for thread to exit");
272 * Stop playback, cleanup and exit thread.
274 void AlsaPlayerThread::stopAndExit()
276 // if (handle) snd_pcm_drop(handle);
281 void AlsaPlayerThread::setVolume(float volume
)
283 m_currentVolume
= volume
;
286 float AlsaPlayerThread::volume() const
288 return m_currentVolume
;
291 /////////////////////////////////////////////////////////////////////////////////
292 // player status functions
293 /////////////////////////////////////////////////////////////////////////////////
295 bool AlsaPlayerThread::playing() const
299 QMutexLocker
locker(&m_mutex
);
302 snd_pcm_status_t
*status
;
303 snd_pcm_status_alloca(&status
);
305 if ((res
= snd_pcm_status(handle
, status
)) < 0)
306 ERR("status error: %s", snd_strerror(res
));
308 result
= (SND_PCM_STATE_RUNNING
== snd_pcm_status_get_state(status
))
309 || (SND_PCM_STATE_DRAINING
== snd_pcm_status_get_state(status
));
310 DBG("state = %s", snd_pcm_state_name(snd_pcm_status_get_state(status
)));
313 result
= !m_simulatedPause
;
319 bool AlsaPlayerThread::paused() const
323 QMutexLocker
locker(&m_mutex
);
326 snd_pcm_status_t
*status
;
327 snd_pcm_status_alloca(&status
);
329 if ((res
= snd_pcm_status(handle
, status
)) < 0)
330 ERR("status error: %s", snd_strerror(res
));
332 result
= (SND_PCM_STATE_PAUSED
== snd_pcm_status_get_state(status
));
333 DBG("state = %s", snd_pcm_state_name(snd_pcm_status_get_state(status
)));
336 result
= m_simulatedPause
;
342 int AlsaPlayerThread::totalTime() const
345 int rate
= hwdata
.rate
;
346 int channels
= hwdata
.channels
;
347 if (rate
> 0 && channels
> 0) {
348 total
= int((double(pbrec_count
) / rate
) / channels
);
349 // DBG("pbrec_count = %i rate =%i channels = %i", pbrec_count, rate, channels);
350 // DBG("totalTime = %i", total);
355 int AlsaPlayerThread::currentTime() const
358 int rate
= hwdata
.rate
;
359 int channels
= hwdata
.channels
;
360 if (rate
> 0 && channels
> 0) {
361 current
= int((double(fdcount
) / rate
) / channels
);
362 // DBG("fdcount = %i rate = %i channels = %i", fdcount, rate, channels);
363 // DBG("currentTime = %i", current);
368 int AlsaPlayerThread::position() const
370 // TODO: Make this more accurate by adding frames that have been so-far
371 // played within the Alsa ring buffer.
372 return pbrec_count
> 0 ? int(double(fdcount
) * 1000 / pbrec_count
+ .5) : 0;
375 /////////////////////////////////////////////////////////////////////////////////
376 // player seek functions
377 /////////////////////////////////////////////////////////////////////////////////
379 void AlsaPlayerThread::seek(int /*seekTime*/)
384 void AlsaPlayerThread::seekPosition(int /*position*/)
390 * Returns a list of PCM devices.
391 * This function fills the specified list with ALSA hardware soundcards found on the system.
392 * It uses plughw:xx instead of hw:xx for specifiers, because hw:xx are not practical to
393 * use (e.g. they require a resampler/channel mixer in the application).
395 QStringList
AlsaPlayerThread::getPluginList( const QByteArray
& classname
)
400 int card
= -1, device
= -1;
402 snd_ctl_card_info_t
*info
;
403 snd_pcm_info_t
*pcminfo
;
404 snd_ctl_card_info_alloca(&info
);
405 snd_pcm_info_alloca(&pcminfo
);
408 result
.append("default");
410 err
= snd_card_next(&card
);
411 if (err
< 0 || card
< 0) break;
414 sprintf(name
, "hw:%i", card
);
415 if ((err
= snd_ctl_open(&handle
, name
, 0)) < 0) continue;
416 if ((err
= snd_ctl_card_info(handle
, info
)) < 0) {
417 snd_ctl_close(handle
);
420 for (int devCnt
=0;;++devCnt
) {
421 err
= snd_ctl_pcm_next_device(handle
, &device
);
422 if (err
< 0 || device
< 0) break;
424 snd_pcm_info_set_device(pcminfo
, device
);
425 snd_pcm_info_set_subdevice(pcminfo
, 0);
426 snd_pcm_info_set_stream(pcminfo
, SND_PCM_STREAM_PLAYBACK
);
427 if ((err
= snd_ctl_pcm_info(handle
, pcminfo
)) < 0) continue;
428 QString infoName
= " ";
429 infoName
+= snd_ctl_card_info_get_name(info
);
431 infoName
+= snd_pcm_info_get_name(pcminfo
);
434 QString pcmName
= QString("default:%1").arg(card
);
435 result
.append(pcmName
+ infoName
);
437 QString pcmName
= QString("plughw:%1,%2").arg(card
).arg(device
);
438 result
.append(pcmName
+ infoName
);
440 snd_ctl_close(handle
);
446 void AlsaPlayerThread::setSinkName(const QString
& sinkName
) { m_pcmName
= sinkName
; }
448 /////////////////////////////////////////////////////////////////////////////////
450 /////////////////////////////////////////////////////////////////////////////////
452 void AlsaPlayerThread::init()
458 file_type
= FORMAT_DEFAULT
;
461 open_mode
= SND_PCM_NONBLOCK
;
462 stream
= SND_PCM_STREAM_PLAYBACK
;
475 pbrec_count
= LLONG_MAX
;
476 alsa_stop_pipe
[0] = 0;
477 alsa_stop_pipe
[1] = 0;
479 m_simulatedPause
= false;
482 void AlsaPlayerThread::cleanup()
485 QMutexLocker
locker(&m_mutex
);
486 if (pcm_name
) free(pcm_name
);
487 if (fd
>= 0) audiofile
.close();
489 snd_pcm_drop(handle
);
490 snd_pcm_close(handle
);
492 if (alsa_stop_pipe
[0]) close(alsa_stop_pipe
[0]);
493 if (alsa_stop_pipe
[1]) close(alsa_stop_pipe
[1]);
494 if (audiobuf
) audioBuffer
.resize(0);
495 if (alsa_poll_fds
) alsa_poll_fds_barray
.resize(0);
496 if (log
) snd_output_close(log
);
497 snd_config_update_free_global();
502 * Safe read (for pipes)
505 ssize_t
AlsaPlayerThread::safe_read(int fd
, void *buf
, size_t count
)
511 if ((res
= read(fd
, buf
, count
)) == 0)
514 return result
> 0 ? result
: res
;
517 buf
= (char *)buf
+ res
;
523 * Test, if it is a .VOC file and return >=0 if ok (this is the length of rest)
526 int AlsaPlayerThread::test_vocfile(void *buffer
)
528 VocHeader
*vp
= (VocHeader
*)buffer
;
530 if (!memcmp(vp
->magic
, VOC_MAGIC_STRING
, 20)) {
531 vocminor
= LE_SHORT(vp
->version
) & 0xFF;
532 vocmajor
= LE_SHORT(vp
->version
) / 256;
533 if (LE_SHORT(vp
->version
) != (0x1233 - LE_SHORT(vp
->coded_ver
)))
534 return -2; /* coded version mismatch */
535 return LE_SHORT(vp
->headerlen
) - sizeof(VocHeader
); /* 0 mostly */
537 return -1; /* magic string fail */
541 * helper for test_wavefile
544 ssize_t
AlsaPlayerThread::test_wavefile_read(int fd
, char *buffer
, size_t *size
, size_t reqsize
, int line
)
546 if (*size
>= reqsize
)
548 if ((size_t)safe_read(fd
, buffer
+ *size
, reqsize
- *size
) != reqsize
- *size
) {
549 ERR("read error (called from line %i)", line
);
552 return *size
= reqsize
;
555 #define check_wavefile_space(buffer, len, blimit) \
556 if (len > blimit) { \
558 if ((buffer = (char*)realloc(buffer, blimit)) == NULL) { \
559 ERR("not enough memory"); \
565 * test, if it's a .WAV file, > 0 if ok (and set the speed, stereo etc.)
567 * Value returned is bytes to be discarded.
569 ssize_t
AlsaPlayerThread::test_wavefile(int fd
, char *_buffer
, size_t size
)
571 WaveHeader
*h
= (WaveHeader
*)_buffer
;
579 if (size
< sizeof(WaveHeader
))
581 if (h
->magic
!= WAV_RIFF
|| h
->type
!= WAV_WAVE
)
583 if (size
> sizeof(WaveHeader
)) {
584 check_wavefile_space(buffer
, size
- sizeof(WaveHeader
), blimit
);
585 memcpy(buffer
, _buffer
+ sizeof(WaveHeader
), size
- sizeof(WaveHeader
));
587 size
-= sizeof(WaveHeader
);
589 check_wavefile_space(buffer
, sizeof(WaveChunkHeader
), blimit
);
590 test_wavefile_read(fd
, buffer
, &size
, sizeof(WaveChunkHeader
), __LINE__
);
591 c
= (WaveChunkHeader
*)buffer
;
593 len
= LE_INT(c
->length
);
595 if (size
> sizeof(WaveChunkHeader
))
596 memmove(buffer
, buffer
+ sizeof(WaveChunkHeader
), size
- sizeof(WaveChunkHeader
));
597 size
-= sizeof(WaveChunkHeader
);
600 check_wavefile_space(buffer
, len
, blimit
);
601 test_wavefile_read(fd
, buffer
, &size
, len
, __LINE__
);
603 memmove(buffer
, buffer
+ len
, size
- len
);
607 if (len
< sizeof(WaveFmtBody
)) {
608 ERR("unknown length of 'fmt ' chunk (read %u, should be %u at least)", len
, (u_int
)sizeof(WaveFmtBody
));
611 check_wavefile_space(buffer
, len
, blimit
);
612 test_wavefile_read(fd
, buffer
, &size
, len
, __LINE__
);
613 f
= (WaveFmtBody
*) buffer
;
614 if (LE_SHORT(f
->format
) != WAV_PCM_CODE
) {
615 ERR("can't play not PCM-coded WAVE-files");
618 if (LE_SHORT(f
->modus
) < 1) {
619 ERR("can't play WAVE-files with %d tracks", LE_SHORT(f
->modus
));
622 hwdata
.channels
= LE_SHORT(f
->modus
);
623 switch (LE_SHORT(f
->bit_p_spl
)) {
625 if (hwdata
.format
!= DEFAULT_FORMAT
&&
626 hwdata
.format
!= SND_PCM_FORMAT_U8
)
627 MSG("Warning: format is changed to U8");
628 hwdata
.format
= SND_PCM_FORMAT_U8
;
631 if (hwdata
.format
!= DEFAULT_FORMAT
&&
632 hwdata
.format
!= SND_PCM_FORMAT_S16_LE
)
633 MSG("Warning: format is changed to S16_LE");
634 hwdata
.format
= SND_PCM_FORMAT_S16_LE
;
637 switch (LE_SHORT(f
->byte_p_spl
) / hwdata
.channels
) {
639 if (hwdata
.format
!= DEFAULT_FORMAT
&&
640 hwdata
.format
!= SND_PCM_FORMAT_S24_3LE
)
641 MSG("Warning: format is changed to S24_3LE");
642 hwdata
.format
= SND_PCM_FORMAT_S24_3LE
;
645 if (hwdata
.format
!= DEFAULT_FORMAT
&&
646 hwdata
.format
!= SND_PCM_FORMAT_S24_LE
)
647 MSG("Warning: format is changed to S24_LE");
648 hwdata
.format
= SND_PCM_FORMAT_S24_LE
;
651 ERR("can't play WAVE-files with sample %d bits in %d bytes wide (%d channels)", LE_SHORT(f
->bit_p_spl
), LE_SHORT(f
->byte_p_spl
), hwdata
.channels
);
656 hwdata
.format
= SND_PCM_FORMAT_S32_LE
;
659 ERR("can't play WAVE-files with sample %d bits wide", LE_SHORT(f
->bit_p_spl
));
662 hwdata
.rate
= LE_INT(f
->sample_fq
);
665 memmove(buffer
, buffer
+ len
, size
- len
);
671 check_wavefile_space(buffer
, sizeof(WaveChunkHeader
), blimit
);
672 test_wavefile_read(fd
, buffer
, &size
, sizeof(WaveChunkHeader
), __LINE__
);
673 c
= (WaveChunkHeader
*)buffer
;
675 len
= LE_INT(c
->length
);
676 if (size
> sizeof(WaveChunkHeader
))
677 memmove(buffer
, buffer
+ sizeof(WaveChunkHeader
), size
- sizeof(WaveChunkHeader
));
678 size
-= sizeof(WaveChunkHeader
);
679 if (type
== WAV_DATA
) {
680 if (len
< pbrec_count
&& len
< 0x7ffffffe)
683 memcpy(_buffer
, buffer
, size
);
688 check_wavefile_space(buffer
, len
, blimit
);
689 test_wavefile_read(fd
, buffer
, &size
, len
, __LINE__
);
691 memmove(buffer
, buffer
+ len
, size
- len
);
695 /* shouldn't be reached */
703 int AlsaPlayerThread::test_au(int fd
, char *buffer
)
705 AuHeader
*ap
= (AuHeader
*)buffer
;
707 if (ap
->magic
!= AU_MAGIC
)
709 if (BE_INT(ap
->hdr_size
) > 128 || BE_INT(ap
->hdr_size
) < 24)
711 pbrec_count
= BE_INT(ap
->data_size
);
712 switch (BE_INT(ap
->encoding
)) {
714 if (hwdata
.format
!= DEFAULT_FORMAT
&&
715 hwdata
.format
!= SND_PCM_FORMAT_MU_LAW
)
716 MSG("Warning: format is changed to MU_LAW");
717 hwdata
.format
= SND_PCM_FORMAT_MU_LAW
;
720 if (hwdata
.format
!= DEFAULT_FORMAT
&&
721 hwdata
.format
!= SND_PCM_FORMAT_U8
)
722 MSG("Warning: format is changed to U8");
723 hwdata
.format
= SND_PCM_FORMAT_U8
;
726 if (hwdata
.format
!= DEFAULT_FORMAT
&&
727 hwdata
.format
!= SND_PCM_FORMAT_S16_BE
)
728 MSG("Warning: format is changed to S16_BE");
729 hwdata
.format
= SND_PCM_FORMAT_S16_BE
;
734 hwdata
.rate
= BE_INT(ap
->sample_rate
);
735 if (hwdata
.rate
< 2000 || hwdata
.rate
> 256000)
737 hwdata
.channels
= BE_INT(ap
->channels
);
738 if (hwdata
.channels
< 1 || hwdata
.channels
> 128)
740 if ((size_t)safe_read(fd
, buffer
+ sizeof(AuHeader
), BE_INT(ap
->hdr_size
) - sizeof(AuHeader
)) != BE_INT(ap
->hdr_size
) - sizeof(AuHeader
)) {
747 void AlsaPlayerThread::set_params(void)
749 snd_pcm_hw_params_t
*hwparams
;
750 snd_pcm_uframes_t period_size
;
754 unsigned int periods
;
756 snd_pcm_hw_params_alloca(&hwparams
);
757 err
= snd_pcm_hw_params_any(handle
, hwparams
);
759 ERR("Broken configuration for this PCM: no configurations available");
763 /* Create the pipe for communication about stop requests. */
764 if (pipe(alsa_stop_pipe
)) {
765 ERR("Stop pipe creation failed (%s)", strerror(errno
));
769 /* Find how many descriptors we will get for poll(). */
770 alsa_fd_count
= snd_pcm_poll_descriptors_count(handle
);
771 if (alsa_fd_count
<= 0){
772 ERR("Invalid poll descriptors count returned from ALSA.");
776 /* Create and fill in struct pollfd *alsa_poll_fds with ALSA descriptors. */
777 // alsa_poll_fds = (pollfd *)malloc ((alsa_fd_count + 1) * sizeof(struct pollfd));
778 alsa_poll_fds_barray
.resize((alsa_fd_count
+ 1) * sizeof(struct pollfd
));
779 alsa_poll_fds
= (pollfd
*)alsa_poll_fds_barray
.data();
780 assert(alsa_poll_fds
);
781 if ((err
= snd_pcm_poll_descriptors(handle
, alsa_poll_fds
, alsa_fd_count
)) < 0) {
782 ERR("Unable to obtain poll descriptors for playback: %s", snd_strerror(err
));
786 /* Create a new pollfd structure for requests by alsa_stop(). */
787 struct pollfd alsa_stop_pipe_pfd
;
788 alsa_stop_pipe_pfd
.fd
= alsa_stop_pipe
[0];
789 alsa_stop_pipe_pfd
.events
= POLLIN
;
790 alsa_stop_pipe_pfd
.revents
= 0;
792 /* Join this our own pollfd to the ALSAs ones. */
793 alsa_poll_fds
[alsa_fd_count
] = alsa_stop_pipe_pfd
;
797 snd_pcm_access_mask_t
*mask
= (snd_pcm_access_mask_t
*)alloca(snd_pcm_access_mask_sizeof());
798 snd_pcm_access_mask_none(mask
);
799 snd_pcm_access_mask_set(mask
, SND_PCM_ACCESS_MMAP_INTERLEAVED
);
800 snd_pcm_access_mask_set(mask
, SND_PCM_ACCESS_MMAP_NONINTERLEAVED
);
801 snd_pcm_access_mask_set(mask
, SND_PCM_ACCESS_MMAP_COMPLEX
);
802 err
= snd_pcm_hw_params_set_access_mask(handle
, hwparams
, mask
);
803 } else if (interleaved
)
804 err
= snd_pcm_hw_params_set_access(handle
, hwparams
,
805 SND_PCM_ACCESS_RW_INTERLEAVED
);
807 err
= snd_pcm_hw_params_set_access(handle
, hwparams
,
808 SND_PCM_ACCESS_RW_NONINTERLEAVED
);
810 ERR("Error setting access type: %s", snd_strerror(err
));
813 err
= snd_pcm_hw_params_set_format(handle
, hwparams
, hwdata
.format
);
815 ERR("Error setting sample format to %i: %s", hwdata
.format
, snd_strerror(err
));
818 err
= snd_pcm_hw_params_set_channels(handle
, hwparams
, hwdata
.channels
);
820 ERR("Error setting channel count to %i: %s", hwdata
.channels
, snd_strerror(err
));
825 err
= snd_pcm_hw_params_set_periods_min(handle
, hwparams
, 2);
829 err
= snd_pcm_hw_params_set_rate_near(handle
, hwparams
, &hwdata
.rate
, 0);
831 if ((float)rate
* 1.05 < hwdata
.rate
|| (float)rate
* 0.95 > hwdata
.rate
) {
832 MSG("Warning: rate is not accurate (requested = %iHz, got = %iHz)", rate
, hwdata
.rate
);
833 MSG(" please, try the plug plugin (-Dplug:%s)", snd_pcm_name(handle
));
836 period_size
= m_defPeriodSize
;
838 err
= snd_pcm_hw_params_set_period_size_near(handle
, hwparams
, &period_size
, &dir
);
840 MSG("Setting period_size to %lu failed, but continuing: %s", period_size
, snd_strerror(err
));
843 periods
= m_defPeriods
;
845 err
= snd_pcm_hw_params_set_periods_near(handle
, hwparams
, &periods
, &dir
);
847 MSG("Unable to set number of periods to %i, but continuing: %s", periods
, snd_strerror(err
));
849 /* Install hw parameters. */
850 err
= snd_pcm_hw_params(handle
, hwparams
);
852 MSG("Unable to install hw params: %s", snd_strerror(err
));
853 snd_pcm_hw_params_dump(hwparams
, log
);
857 /* Determine if device can pause. */
858 canPause
= (1 == snd_pcm_hw_params_can_pause(hwparams
));
860 /* Get final buffer size and calculate the chunk size we will pass to device. */
861 snd_pcm_hw_params_get_buffer_size(hwparams
, &buffer_size
);
862 chunk_size
= periods
* period_size
;
864 if (0 == chunk_size
) {
865 ERR("Invalid periods or period_size. Cannot continue.");
869 if (chunk_size
== buffer_size
)
870 MSG("WARNING: Shouldn't use chunk_size equal to buffer_size (%lu). Continuing anyway.", chunk_size
);
872 DBG("Final buffer_size = %lu, chunk_size = %lu, periods = %i, period_size = %lu, canPause = %i",
873 buffer_size
, chunk_size
, periods
, period_size
, canPause
);
875 if (m_debugLevel
>= 2)
876 snd_pcm_dump(handle
, log
);
878 bits_per_sample
= snd_pcm_format_physical_width(hwdata
.format
);
879 bits_per_frame
= bits_per_sample
* hwdata
.channels
;
880 chunk_bytes
= chunk_size
* bits_per_frame
/ 8;
881 audioBuffer
.resize(chunk_bytes
);
882 audiobuf
= audioBuffer
.data();
883 if (audiobuf
== NULL
) {
884 ERR("not enough memory");
890 #define timersub(a, b, result) \
892 (result)->tv_sec = (a)->tv_sec - (b)->tv_sec; \
893 (result)->tv_usec = (a)->tv_usec - (b)->tv_usec; \
894 if ((result)->tv_usec < 0) { \
895 --(result)->tv_sec; \
896 (result)->tv_usec += 1000000; \
901 /* I/O error handler */
902 void AlsaPlayerThread::xrun()
904 snd_pcm_status_t
*status
;
907 snd_pcm_status_alloca(&status
);
908 if ((res
= snd_pcm_status(handle
, status
))<0) {
909 ERR("status error: %s", snd_strerror(res
));
912 if (SND_PCM_STATE_XRUN
== snd_pcm_status_get_state(status
)) {
913 struct timeval now
, diff
, tstamp
;
914 gettimeofday(&now
, 0);
915 snd_pcm_status_get_trigger_tstamp(status
, &tstamp
);
916 timersub(&now
, &tstamp
, &diff
);
917 MSG("%s!!! (at least %.3f ms long)",
918 stream
== SND_PCM_STREAM_PLAYBACK
? "underrun" : "overrun",
919 diff
.tv_sec
* 1000 + diff
.tv_usec
/ 1000.0);
920 if (m_debugLevel
>= 2) {
922 snd_pcm_status_dump(status
, log
);
924 if ((res
= snd_pcm_prepare(handle
))<0) {
925 ERR("xrun: prepare error: %s", snd_strerror(res
));
928 return; /* ok, data should be accepted again */
929 } if (SND_PCM_STATE_DRAINING
== snd_pcm_status_get_state(status
)) {
930 if (m_debugLevel
>= 2) {
931 DBG("Status(DRAINING):");
932 snd_pcm_status_dump(status
, log
);
934 if (stream
== SND_PCM_STREAM_CAPTURE
) {
935 MSG("capture stream format change? attempting recover...");
936 if ((res
= snd_pcm_prepare(handle
))<0) {
937 ERR("xrun(DRAINING): prepare error: %s", snd_strerror(res
));
943 if (m_debugLevel
>= 2) {
945 snd_pcm_status_dump(status
, log
);
947 ERR("read/write error, state = %s", snd_pcm_state_name(snd_pcm_status_get_state(status
)));
951 /* I/O suspend handler */
952 void AlsaPlayerThread::suspend(void)
956 MSG("Suspended. Trying resume. ");
957 while ((res
= snd_pcm_resume(handle
)) == -EAGAIN
)
958 sleep(1); /* wait until suspend flag is released */
960 MSG("Failed. Restarting stream. ");
961 if ((res
= snd_pcm_prepare(handle
)) < 0) {
962 ERR("suspend: prepare error: %s", snd_strerror(res
));
966 MSG("Suspend done.");
970 void AlsaPlayerThread::compute_max_peak(char *data
, size_t count
)
972 signed int val
, max
, max_peak
= 0, perc
;
973 size_t ocount
= count
;
975 switch (bits_per_sample
) {
977 signed char *valp
= (signed char *)data
;
978 signed char mask
= snd_pcm_format_silence(hwdata
.format
);
979 while (count
-- > 0) {
980 val
= *valp
++ ^ mask
;
988 signed short *valp
= (signed short *)data
;
989 signed short mask
= snd_pcm_format_silence_16(hwdata
.format
);
991 while (count
-- > 0) {
992 val
= *valp
++ ^ mask
;
1000 signed int *valp
= (signed int *)data
;
1001 signed int mask
= snd_pcm_format_silence_32(hwdata
.format
);
1003 while (count
-- > 0) {
1004 val
= *valp
++ ^ mask
;
1014 max
= 1 << (bits_per_sample
-1);
1017 DBG("Max peak (%li samples): %05i (0x%04x) ", (long)ocount
, max_peak
, max_peak
);
1018 if (bits_per_sample
> 16)
1019 perc
= max_peak
/ (max
/ 100);
1021 perc
= max_peak
* 100 / max
;
1022 for (val
= 0; val
< 20; val
++)
1023 if (val
<= perc
/ 5)
1034 ssize_t
AlsaPlayerThread::pcm_write(char *data
, size_t count
)
1039 if (sleep_min
== 0 && count
< chunk_size
) {
1040 DBG("calling snd_pcm_format_set_silence");
1041 snd_pcm_format_set_silence(hwdata
.format
, data
+ count
* bits_per_frame
/ 8, (chunk_size
- count
) * hwdata
.channels
);
1045 DBG("calling writei_func, count = %i", count
);
1046 r
= writei_func(handle
, data
, count
);
1047 DBG("writei_func returned %i", r
);
1048 if (-EAGAIN
== r
|| (r
>= 0 && (size_t)r
< count
)) {
1049 DBG("r = %i calling snd_pcm_wait", r
);
1050 snd_pcm_wait(handle
, 100);
1051 } else if (-EPIPE
== r
) {
1053 } else if (-ESTRPIPE
== r
) {
1055 } else if (-EBUSY
== r
){
1056 MSG("WARNING: sleeping while PCM BUSY");
1060 ERR("write error: %s", snd_strerror(r
));
1064 if (m_debugLevel
>= 2 > 1)
1065 compute_max_peak(data
, r
* hwdata
.channels
);
1068 data
+= r
* bits_per_frame
/ 8;
1070 /* Report current state */
1071 DBG("PCM state before polling: %s",
1072 snd_pcm_state_name(snd_pcm_state(handle
)));
1074 int err
= wait_for_poll(0);
1076 ERR("Wait for poll() failed");
1080 MSG("Playback stopped");
1081 /* Drop the playback on the sound device (probably
1082 still in progress up till now) */
1083 err
= snd_pcm_drop(handle
);
1085 ERR("snd_pcm_drop() failed: %s", snd_strerror(err
));
1095 * ok, let's play a .voc file
1098 ssize_t
AlsaPlayerThread::voc_pcm_write(u_char
*data
, size_t count
)
1100 ssize_t result
= count
, r
;
1105 if (size
> chunk_bytes
- buffer_pos
)
1106 size
= chunk_bytes
- buffer_pos
;
1107 memcpy(audiobuf
+ buffer_pos
, data
, size
);
1111 if ((size_t)buffer_pos
== chunk_bytes
) {
1112 if ((size_t)(r
= pcm_write(audiobuf
, chunk_size
)) != chunk_size
)
1120 void AlsaPlayerThread::voc_write_silence(unsigned x
)
1125 QByteArray
buffer(chunk_bytes
);
1126 // buf = (char *) malloc(chunk_bytes);
1127 buf
= buffer
.data();
1129 ERR("can't allocate buffer for silence");
1130 return; /* not fatal error */
1132 snd_pcm_format_set_silence(hwdata
.format
, buf
, chunk_size
* hwdata
.channels
);
1137 if (voc_pcm_write((u_char
*)buf
, l
) != (ssize_t
)l
) {
1146 void AlsaPlayerThread::voc_pcm_flush(void)
1148 if (buffer_pos
> 0) {
1150 if (sleep_min
== 0) {
1151 if (snd_pcm_format_set_silence(hwdata
.format
, audiobuf
+ buffer_pos
, chunk_bytes
- buffer_pos
* 8 / bits_per_sample
) < 0)
1152 MSG("voc_pcm_flush - silence error");
1155 b
= buffer_pos
* 8 / bits_per_frame
;
1157 if (pcm_write(audiobuf
, b
) != (ssize_t
)b
)
1158 ERR("voc_pcm_flush error");
1160 snd_pcm_drain(handle
);
1163 void AlsaPlayerThread::voc_play(int fd
, int ofs
, const char* name
)
1169 size_t nextblock
, in_buffer
;
1171 char was_extended
= 0, output
= 0;
1172 u_short
*sp
, repeat
= 0;
1174 off64_t filepos
= 0;
1176 #define COUNT(x) nextblock -= x; in_buffer -= x; data += x
1177 #define COUNT1(x) in_buffer -= x; data += x
1179 QByteArray
buffer(64 * 1024);
1180 // data = buf = (u_char *)malloc(64 * 1024);
1181 data
= buf
= (u_char
*)buffer
.data();
1184 ERR("malloc error");
1187 MSG("Playing Creative Labs Channel file '%s'...", name
);
1188 /* first we waste the rest of header, ugly but we don't need seek */
1189 while (ofs
> (ssize_t
)chunk_bytes
) {
1190 if ((size_t)safe_read(fd
, buf
, chunk_bytes
) != chunk_bytes
) {
1197 if (safe_read(fd
, buf
, ofs
) != ofs
) {
1202 hwdata
.format
= DEFAULT_FORMAT
;
1203 hwdata
.channels
= 1;
1204 hwdata
.rate
= DEFAULT_SPEED
;
1207 in_buffer
= nextblock
= 0;
1209 Fill_the_buffer
: /* need this for repeat */
1210 if (in_buffer
< 32) {
1211 /* move the rest of buffer to pos 0 and fill the buf up */
1213 memcpy(buf
, data
, in_buffer
);
1215 if ((l
= safe_read(fd
, buf
+ in_buffer
, chunk_bytes
- in_buffer
)) > 0)
1217 else if (!in_buffer
) {
1218 /* the file is truncated, so simulate 'Terminator'
1219 and reduce the datablock for safe landing */
1220 nextblock
= buf
[0] = 0;
1227 while (!nextblock
) { /* this is a new block */
1228 if (in_buffer
< sizeof(VocBlockType
))
1230 bp
= (VocBlockType
*) data
;
1231 COUNT1(sizeof(VocBlockType
));
1232 nextblock
= VOC_DATALEN(bp
);
1234 MSG(" "); /* write /n after ASCII-out */
1241 return; /* VOC-file stop */
1243 vd
= (VocVoiceData
*) data
;
1244 COUNT1(sizeof(VocVoiceData
));
1245 /* we need a SYNC, before we can set new SPEED, STEREO ... */
1247 if (!was_extended
) {
1248 hwdata
.rate
= (int) (vd
->tc
);
1249 hwdata
.rate
= 1000000 / (256 - hwdata
.rate
);
1251 MSG("Channel data %d Hz", dsp_speed
);
1253 if (vd
->pack
) { /* /dev/dsp can't it */
1254 ERR("can't play packed .voc files");
1257 if (hwdata
.channels
== 2) /* if we are in Stereo-Mode, switch back */
1258 hwdata
.channels
= 1;
1259 } else { /* there was extended block */
1260 hwdata
.channels
= 2;
1265 case 2: /* nothing to do, pure data */
1267 MSG("Channel continuation");
1270 case 3: /* a silence block, no data, only a count */
1271 sp
= (u_short
*) data
;
1272 COUNT1(sizeof(u_short
));
1273 hwdata
.rate
= (int) (*data
);
1275 hwdata
.rate
= 1000000 / (256 - hwdata
.rate
);
1277 silence
= (((size_t) * sp
) * 1000) / hwdata
.rate
;
1279 MSG("Silence for %d ms", (int) silence
);
1281 voc_write_silence(*sp
);
1283 case 4: /* a marker for syncronisation, no effect */
1284 sp
= (u_short
*) data
;
1285 COUNT1(sizeof(u_short
));
1287 MSG("Marker %d", *sp
);
1290 case 5: /* ASCII text, we copy to stderr */
1293 MSG("ASCII - text :");
1296 case 6: /* repeat marker, says repeatcount */
1297 /* my specs don't say it: maybe this can be recursive, but
1298 I don't think somebody use it */
1299 repeat
= *(u_short
*) data
;
1300 COUNT1(sizeof(u_short
));
1302 MSG("Repeat loop %d times", repeat
);
1304 if (filepos
>= 0) { /* if < 0, one seek fails, why test another */
1305 if ((filepos
= lseek64(fd
, 0, 1)) < 0) {
1306 ERR("can't play loops; %s isn't seekable", name
);
1309 filepos
-= in_buffer
; /* set filepos after repeat */
1315 case 7: /* ok, lets repeat that be rewinding tape */
1317 if (repeat
!= 0xFFFF) {
1319 MSG("Repeat loop %d", repeat
);
1325 MSG("Neverending loop");
1327 lseek64(fd
, filepos
, 0);
1328 in_buffer
= 0; /* clear the buffer */
1329 goto Fill_the_buffer
;
1333 MSG("End repeat loop");
1336 case 8: /* the extension to play Stereo, I have SB 1.0 :-( */
1338 eb
= (VocExtBlock
*) data
;
1339 COUNT1(sizeof(VocExtBlock
));
1340 hwdata
.rate
= (int) (eb
->tc
);
1341 hwdata
.rate
= 256000000L / (65536 - hwdata
.rate
);
1342 hwdata
.channels
= eb
->mode
== VOC_MODE_STEREO
? 2 : 1;
1343 if (hwdata
.channels
== 2)
1344 hwdata
.rate
= hwdata
.rate
>> 1;
1345 if (eb
->pack
) { /* /dev/dsp can't it */
1346 ERR("can't play packed .voc files");
1350 MSG("Extended block %s %d Hz",
1351 (eb
->mode
? "Stereo" : "Mono"), dsp_speed
);
1355 ERR("unknown blocktype %d. terminate.", bp
->type
);
1357 } /* switch (bp->type) */
1358 } /* while (! nextblock) */
1359 /* put nextblock data bytes to dsp */
1361 if (nextblock
< (size_t)l
)
1365 if (write(2, data
, l
) != l
) { /* to stderr */
1370 if (voc_pcm_write(data
, l
) != l
) {
1382 /* that was a big one, perhaps somebody split it :-) */
1384 /* setting the globals for playing raw data */
1385 void AlsaPlayerThread::init_raw_data(void)
1390 /* calculate the data count to read from/to dsp */
1391 off64_t
AlsaPlayerThread::calc_count(void)
1395 if (timelimit
== 0) {
1396 count
= pbrec_count
;
1398 count
= snd_pcm_format_size(hwdata
.format
, hwdata
.rate
* hwdata
.channels
);
1399 count
*= (off64_t
)timelimit
;
1401 return count
< pbrec_count
? count
: pbrec_count
;
1404 void AlsaPlayerThread::header(int /*rtype*/, const char* /*name*/)
1406 // fprintf(stderr, "%s %s '%s' : ",
1407 // (stream == SND_PCM_STREAM_PLAYBACK) ? "Playing" : "Recording",
1408 // fmt_rec_table[rtype].what,
1411 if (hwdata
.channels
== 1)
1413 else if (hwdata
.channels
== 2)
1414 channels
= "Stereo";
1416 channels
= QString("Channels %1").arg(hwdata
.channels
);
1417 DBG("Format: %s, Rate %d Hz, %s",
1418 snd_pcm_format_description(hwdata
.format
),
1423 /* playing raw data */
1425 void AlsaPlayerThread::playback_go(int fd
, size_t loaded
, off64_t count
, int rtype
, const char *name
)
1428 off64_t written
= 0;
1431 if (m_debugLevel
>= 1) header(rtype
, name
);
1434 while (loaded
> chunk_bytes
&& written
< count
) {
1435 if (pcm_write(audiobuf
+ written
, chunk_size
) <= 0)
1437 written
+= chunk_bytes
;
1438 loaded
-= chunk_bytes
;
1440 if (written
> 0 && loaded
> 0)
1441 memmove(audiobuf
, audiobuf
+ written
, loaded
);
1444 while (written
< count
) {
1446 c
= count
- written
;
1447 if (c
> chunk_bytes
)
1453 r
= safe_read(fd
, audiobuf
+ l
, c
);
1462 } while (sleep_min
== 0 && (size_t)l
< chunk_bytes
);
1463 l
= l
* 8 / bits_per_frame
;
1464 DBG("calling pcm_write with %i frames.", l
);
1465 r
= pcm_write(audiobuf
, l
);
1466 DBG("pcm_write returned r = %i", r
);
1470 r
= r
* bits_per_frame
/ 8;
1477 /* We want the next "device ready" notification only when the buffer is completely empty. */
1478 /* Do this by setting the avail_min to the buffer size. */
1480 DBG("Getting swparams");
1481 snd_pcm_sw_params_t
*swparams
;
1482 snd_pcm_sw_params_alloca(&swparams
);
1483 err
= snd_pcm_sw_params_current(handle
, swparams
);
1485 ERR("Unable to get current swparams: %s", snd_strerror(err
));
1488 DBG("Setting avail min to %lu", buffer_size
);
1489 err
= snd_pcm_sw_params_set_avail_min(handle
, swparams
, buffer_size
);
1491 ERR("Unable to set avail min for playback: %s", snd_strerror(err
));
1494 /* write the parameters to the playback device */
1495 DBG("Writing swparams");
1496 err
= snd_pcm_sw_params(handle
, swparams
);
1498 ERR("Unable to set sw params for playback: %s", snd_strerror(err
));
1502 DBG("Waiting for poll");
1503 err
= wait_for_poll(1);
1505 ERR("Wait for poll() failed");
1507 } else if (err
== 1){
1508 MSG("Playback stopped while draining");
1510 /* Drop the playback on the sound device (probably
1511 still in progress up till now) */
1512 err
= snd_pcm_drop(handle
);
1514 ERR("snd_pcm_drop() failed: %s", snd_strerror(err
));
1518 DBG("Draining completed");
1522 * let's play or capture it (capture_type says VOC/WAVE/raw)
1525 void AlsaPlayerThread::playback(int fd
)
1531 pbrec_count
= LLONG_MAX
;
1534 /* read the file header */
1535 dta
= sizeof(AuHeader
);
1536 if ((size_t)safe_read(fd
, audiobuf
, dta
) != dta
) {
1540 if (test_au(fd
, audiobuf
) >= 0) {
1541 rhwdata
.format
= hwdata
.format
;
1542 pbrec_count
= calc_count();
1543 playback_go(fd
, 0, pbrec_count
, FORMAT_AU
, name
.ascii());
1546 dta
= sizeof(VocHeader
);
1547 if ((size_t)safe_read(fd
, audiobuf
+ sizeof(AuHeader
),
1548 dta
- sizeof(AuHeader
)) != dta
- sizeof(AuHeader
)) {
1552 if ((ofs
= test_vocfile(audiobuf
)) >= 0) {
1553 pbrec_count
= calc_count();
1554 voc_play(fd
, ofs
, name
.ascii());
1557 /* read bytes for WAVE-header */
1558 if ((dtawave
= test_wavefile(fd
, audiobuf
, dta
)) >= 0) {
1559 pbrec_count
= calc_count();
1560 playback_go(fd
, dtawave
, pbrec_count
, FORMAT_WAVE
, name
.ascii());
1562 /* should be raw data */
1564 pbrec_count
= calc_count();
1565 playback_go(fd
, dta
, pbrec_count
, FORMAT_RAW
, name
.ascii());
1571 /* Wait until ALSA is ready for more samples or stop() was called.
1572 @return 0 if ALSA is ready for more input, +1 if a request to stop
1573 the sound output was received and a negative value on error. */
1574 int AlsaPlayerThread::wait_for_poll(int draining
)
1576 unsigned short revents
;
1577 snd_pcm_state_t state
;
1580 DBG("Waiting for poll");
1582 /* Wait for certain events */
1584 /* Simulated pause by not writing to alsa device, which will lead to an XRUN
1586 if (m_simulatedPause
)
1590 ret
= poll(alsa_poll_fds
, alsa_fd_count
, -1);
1591 DBG("activity on %d descriptors", ret
);
1593 /* Check for stop request from alsa_stop on the last file descriptors. */
1594 if ((revents
= alsa_poll_fds
[alsa_fd_count
-1].revents
)) {
1595 if (revents
& POLLIN
){
1596 DBG("stop requested");
1601 /* Check the first count-1 descriptors for ALSA events */
1602 snd_pcm_poll_descriptors_revents(handle
, alsa_poll_fds
, alsa_fd_count
-1, &revents
);
1604 /* Ensure we are in the right state */
1605 state
= snd_pcm_state(handle
);
1606 DBG("State after poll returned is %s", snd_pcm_state_name(state
));
1608 if (SND_PCM_STATE_XRUN
== state
){
1610 MSG("WARNING: Buffer underrun detected!");
1614 DBG("Playback terminated");
1619 if (SND_PCM_STATE_SUSPENDED
== state
){
1620 DBG("WARNING: Suspend detected!");
1625 /* Check for errors */
1626 if (revents
& POLLERR
) {
1627 DBG("poll revents says POLLERR");
1631 /* Is ALSA ready for more input? */
1632 if ((revents
& POLLOUT
)){
1633 DBG("Ready for more input");
1640 // ====================================================================
1643 // AlsaPlayer is nothing more than a container for AlsaPlayerThread
1644 // in order to avoid ambiguous QObject, since both Player and QThread
1645 // derive from QObject. Is there a better solution?
1647 AlsaPlayer::AlsaPlayer(QObject
* parent
, const char* name
, const QStringList
& args
):
1648 Player(parent
, name
, args
)
1650 m_AlsaPlayerThread
= new AlsaPlayerThread(this);
1653 AlsaPlayer::~AlsaPlayer()
1655 delete m_AlsaPlayerThread
;
1658 /*virtual*/ void AlsaPlayer::startPlay(const QString
& file
) { m_AlsaPlayerThread
->startPlay(file
); }
1659 /*virtual*/ void AlsaPlayer::pause() { m_AlsaPlayerThread
->pause(); }
1660 /*virtual*/ void AlsaPlayer::stop() { m_AlsaPlayerThread
->stop(); }
1662 /*virtual*/ void AlsaPlayer::setVolume(float volume
) { m_AlsaPlayerThread
->setVolume(volume
); }
1663 /*virtual*/ float AlsaPlayer::volume() const { return m_AlsaPlayerThread
->volume(); }
1665 /*virtual*/ bool AlsaPlayer::playing() const { return m_AlsaPlayerThread
->playing(); }
1666 /*virtual*/ bool AlsaPlayer::paused() const { return m_AlsaPlayerThread
->paused(); }
1668 /*virtual*/ int AlsaPlayer::totalTime() const { return m_AlsaPlayerThread
->totalTime(); }
1669 /*virtual*/ int AlsaPlayer::currentTime() const { return m_AlsaPlayerThread
->currentTime(); }
1670 /*virtual*/ int AlsaPlayer::position() const { return m_AlsaPlayerThread
->position(); } // in this case not really the percent
1672 /*virtual*/ void AlsaPlayer::seek(int seekTime
) { m_AlsaPlayerThread
->seek(seekTime
); }
1673 /*virtual*/ void AlsaPlayer::seekPosition(int position
) { m_AlsaPlayerThread
->seekPosition(position
); }
1675 /*virtual*/ QStringList
AlsaPlayer::getPluginList( const QByteArray
& classname
)
1676 { return m_AlsaPlayerThread
->getPluginList(classname
); }
1677 /*virtual*/ void AlsaPlayer::setSinkName(const QString
&sinkName
)
1678 { m_AlsaPlayerThread
->setSinkName(sinkName
); }
1680 #include "alsaplayer.moc"
1686 // vim: sw=4 ts=8 et