aboutsummaryrefslogtreecommitdiff
path: root/src/plugins/Output
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/Output')
-rw-r--r--src/plugins/Output/CMakeLists.txt24
-rw-r--r--src/plugins/Output/Output.pro26
-rw-r--r--src/plugins/Output/alsa/CMakeLists.txt65
-rw-r--r--src/plugins/Output/alsa/alsa.pro37
-rw-r--r--src/plugins/Output/alsa/outputalsa.cpp539
-rw-r--r--src/plugins/Output/alsa/outputalsa.h85
-rw-r--r--src/plugins/Output/alsa/outputalsafactory.cpp63
-rw-r--r--src/plugins/Output/alsa/outputalsafactory.h48
-rw-r--r--src/plugins/Output/alsa/settingsdialog.cpp237
-rw-r--r--src/plugins/Output/alsa/settingsdialog.h59
-rw-r--r--src/plugins/Output/alsa/settingsdialog.ui261
-rw-r--r--src/plugins/Output/alsa/translations/alsa_plugin_cs.ts90
-rw-r--r--src/plugins/Output/alsa/translations/alsa_plugin_ru.qmbin0 -> 1580 bytes
-rw-r--r--src/plugins/Output/alsa/translations/alsa_plugin_ru.ts89
-rw-r--r--src/plugins/Output/alsa/translations/translations.qrc6
-rw-r--r--src/plugins/Output/jack/CMakeLists.txt74
-rw-r--r--src/plugins/Output/jack/bio2jack.c2635
-rw-r--r--src/plugins/Output/jack/bio2jack.h145
-rw-r--r--src/plugins/Output/jack/jack.pro33
-rw-r--r--src/plugins/Output/jack/outputjack.cpp208
-rw-r--r--src/plugins/Output/jack/outputjack.h49
-rw-r--r--src/plugins/Output/jack/outputjackfactory.cpp60
-rw-r--r--src/plugins/Output/jack/outputjackfactory.h48
-rw-r--r--src/plugins/Output/jack/translations/jack_plugin_ru.qmbin0 -> 540 bytes
-rw-r--r--src/plugins/Output/jack/translations/jack_plugin_ru.ts26
-rw-r--r--src/plugins/Output/jack/translations/translations.qrc6
-rw-r--r--src/plugins/Output/oss/CMakeLists.txt67
-rw-r--r--src/plugins/Output/oss/oss.pro36
-rw-r--r--src/plugins/Output/oss/outputoss.cpp505
-rw-r--r--src/plugins/Output/oss/outputoss.h76
-rw-r--r--src/plugins/Output/oss/outputossfactory.cpp70
-rw-r--r--src/plugins/Output/oss/outputossfactory.h48
-rw-r--r--src/plugins/Output/oss/settingsdialog.cpp60
-rw-r--r--src/plugins/Output/oss/settingsdialog.h47
-rw-r--r--src/plugins/Output/oss/settingsdialog.ui309
-rw-r--r--src/plugins/Output/oss/translations/oss_plugin_cs.ts90
36 files changed, 6221 insertions, 0 deletions
diff --git a/src/plugins/Output/CMakeLists.txt b/src/plugins/Output/CMakeLists.txt
new file mode 100644
index 000000000..864d945c1
--- /dev/null
+++ b/src/plugins/Output/CMakeLists.txt
@@ -0,0 +1,24 @@
+SET(USE_ALSA TRUE CACHE BOOL "enable/disable alsa plugin")
+SET(USE_JACK TRUE CACHE BOOL "enable/disable jack plugin")
+SET(USE_OSS TRUE CACHE BOOL "enable/disable oss plugin")
+
+IF(USE_ALSA)
+MESSAGE( STATUS "ALSA ON")
+add_subdirectory(alsa)
+ELSE(USE_ALSA)
+MESSAGE( STATUS "ALSA OFF")
+ENDIF(USE_ALSA)
+
+IF(USE_JACK)
+MESSAGE( STATUS "JACK ON")
+add_subdirectory(jack)
+ELSE(USE_JACK)
+MESSAGE( STATUS "JACK OFF")
+ENDIF(USE_JACK)
+
+IF(USE_OSS)
+MESSAGE( STATUS "OSS ON")
+add_subdirectory(oss)
+ELSE(USE_OSS)
+MESSAGE( STATUS "OSS OFF")
+ENDIF(USE_OSS) \ No newline at end of file
diff --git a/src/plugins/Output/Output.pro b/src/plugins/Output/Output.pro
new file mode 100644
index 000000000..eed8b9f14
--- /dev/null
+++ b/src/plugins/Output/Output.pro
@@ -0,0 +1,26 @@
+# ???? ?????? ? KDevelop ?????????? qmake.
+# -------------------------------------------
+# ?????????? ???????????? ???????? ???????? ???????: ./Plugins/Output
+# ???? - ?????? ? ?????????????
+
+include(../../../qmmp.pri)
+
+CONFIG += release warn_on
+TEMPLATE = subdirs
+
+SUBDIRS += alsa
+
+contains(CONFIG, JACK_PLUGIN){
+ SUBDIRS += jack
+ message(***********************)
+ message(* JACK plugin enabled *)
+ message(***********************)
+}
+
+contains(CONFIG, OSS_PLUGIN){
+ SUBDIRS += oss
+ message(**********************)
+ message(* OSS plugin enabled *)
+ message(**********************)
+}
+
diff --git a/src/plugins/Output/alsa/CMakeLists.txt b/src/plugins/Output/alsa/CMakeLists.txt
new file mode 100644
index 000000000..92f7af8d9
--- /dev/null
+++ b/src/plugins/Output/alsa/CMakeLists.txt
@@ -0,0 +1,65 @@
+project(libalsa)
+
+cmake_minimum_required(VERSION 2.4.0)
+
+
+INCLUDE(UsePkgConfig)
+INCLUDE(FindQt4)
+
+find_package(Qt4 REQUIRED) # find and setup Qt4 for this project
+include(${QT_USE_FILE})
+
+# qt plugin
+ADD_DEFINITIONS( -Wall )
+ADD_DEFINITIONS(${QT_DEFINITIONS})
+ADD_DEFINITIONS(-DQT_PLUGIN)
+ADD_DEFINITIONS(-DQT_NO_DEBUG)
+ADD_DEFINITIONS(-DQT_SHARED)
+ADD_DEFINITIONS(-DQT_THREAD)
+
+include_directories(${CMAKE_CURRENT_BINARY_DIR})
+
+SET(QT_INCLUDES
+ ${QT_INCLUDES}
+ ${CMAKE_CURRENT_BINARY_DIR}/../../../
+)
+
+# libqmmp
+include_directories(${CMAKE_CURRENT_BINARY_DIR}/../../../)
+link_directories(${CMAKE_CURRENT_BINARY_DIR}/../../../)
+
+SET(libalsa_SRCS
+ outputalsa.cpp
+ outputalsafactory.cpp
+ settingsdialog.cpp
+)
+
+SET(libalsa_MOC_HDRS
+ outputalsa.h
+ outputalsafactory.h
+ settingsdialog.h
+)
+
+SET(libalsa_RCCS translations/translations.qrc)
+
+QT4_ADD_RESOURCES(libalsa_RCC_SRCS ${libalsa_RCCS})
+
+QT4_WRAP_CPP(libalsa_MOC_SRCS ${libalsa_MOC_HDRS})
+
+# user interface
+
+
+SET(libalsa_UIS
+ settingsdialog.ui
+)
+
+QT4_WRAP_UI(libalsa_UIS_H ${libalsa_UIS})
+# Don't forget to include output directory, otherwise
+# the UI file won't be wrapped!
+include_directories(${CMAKE_CURRENT_BINARY_DIR})
+
+ADD_LIBRARY(alsa SHARED ${libalsa_SRCS} ${libalsa_MOC_SRCS} ${libalsa_UIS_H}
+ ${libalsa_RCC_SRCS})
+target_link_libraries(alsa ${QT_LIBRARIES} -lqmmp -lasound)
+install(TARGETS alsa DESTINATION ${LIB_DIR}/qmmp/Output PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_EXECUTE GROUP_READ WORLD_EXECUTE WORLD_READ)
+
diff --git a/src/plugins/Output/alsa/alsa.pro b/src/plugins/Output/alsa/alsa.pro
new file mode 100644
index 000000000..465fabe4a
--- /dev/null
+++ b/src/plugins/Output/alsa/alsa.pro
@@ -0,0 +1,37 @@
+# ???? ?????? ? KDevelop ?????????? qmake.
+# -------------------------------------------
+# ?????????? ???????????? ???????? ???????? ???????: ./Plugins/Output/alsa
+# ???? - ??????????:
+
+include(../../plugins.pri)
+
+HEADERS += outputalsa.h \
+ outputalsafactory.h \
+ settingsdialog.h
+SOURCES += outputalsa.cpp \
+ outputalsafactory.cpp \
+ settingsdialog.cpp
+
+TARGET=$$PLUGINS_PREFIX/Output/alsa
+QMAKE_CLEAN =$$PLUGINS_PREFIX/Output/libalsa.so
+
+
+INCLUDEPATH += ../../../qmmp
+QMAKE_LIBDIR += ../../../../lib
+
+CONFIG += release \
+warn_on \
+thread \
+plugin
+TEMPLATE = lib
+LIBS += -lqmmp -lasound
+FORMS += settingsdialog.ui
+#TRANSLATIONS = translations/alsa_plugin_ru.ts
+#RESOURCES = translations/translations.qrc
+
+isEmpty (LIB_DIR){
+LIB_DIR = /lib
+}
+
+target.path = $$LIB_DIR/qmmp/Output
+INSTALLS += target
diff --git a/src/plugins/Output/alsa/outputalsa.cpp b/src/plugins/Output/alsa/outputalsa.cpp
new file mode 100644
index 000000000..66c031bbb
--- /dev/null
+++ b/src/plugins/Output/alsa/outputalsa.cpp
@@ -0,0 +1,539 @@
+/***************************************************************************
+ * Copyright (C) 2006 by Ilya Kotov *
+ * forkotov02@hotmail.ru *
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program; if not, write to the *
+ * Free Software Foundation, Inc., *
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
+ ***************************************************************************/
+
+#include <QObject>
+#include <QApplication>
+#include <QtGlobal>
+#include <QDir>
+#include <QSettings>
+#include <QTimer>
+
+#include <stdio.h>
+#include <string.h>
+#include <iostream>
+
+#include "outputalsa.h"
+#include "constants.h"
+#include "buffer.h"
+#include "visual.h"
+
+OutputALSA::OutputALSA(QObject * parent, bool useVolume)
+ : Output(parent), m_inited(FALSE), m_pause(FALSE), m_play(FALSE),
+ m_userStop(FALSE), m_totalWritten(0), m_currentSeconds(-1),
+ m_bps(-1), m_frequency(-1), m_channels(-1), m_precision(-1)
+{
+ QSettings settings(QDir::homePath()+"/.qmmp/qmmprc", QSettings::IniFormat);
+ QString dev_name = settings.value("ALSA/device","default").toString();
+ pcm_name = strdup(dev_name.toAscii().data());
+ stream = SND_PCM_STREAM_PLAYBACK;
+ snd_pcm_hw_params_alloca(&hwparams);
+ pcm_handle = 0;
+ //alsa mixer
+ mixer = 0;
+ if (useVolume)
+ {
+ QString card = settings.value("ALSA/mixer_card","hw:0").toString();
+ QString dev = settings.value("ALSA/mixer_device", "PCM").toString();
+ setupMixer(card, dev);
+ }
+}
+
+OutputALSA::~OutputALSA()
+{
+ uninitialize();
+ free (pcm_name);
+ if (mixer)
+ snd_mixer_close(mixer);
+}
+
+void OutputALSA::stop()
+{
+ m_userStop = TRUE;
+}
+
+void OutputALSA::status()
+{
+ long ct = (m_totalWritten - latency()) / m_bps;
+
+ if (ct < 0)
+ ct = 0;
+
+ if (ct > m_currentSeconds)
+ {
+ m_currentSeconds = ct;
+ dispatch(m_currentSeconds, m_totalWritten, m_rate,
+ m_frequency, m_precision, m_channels);
+ }
+}
+
+long OutputALSA::written()
+{
+ return m_totalWritten;
+}
+
+void OutputALSA::seek(long pos)
+{
+ m_totalWritten = (pos * m_bps);
+ m_currentSeconds = -1;
+}
+
+void OutputALSA::configure(long freq, int chan, int prec, int brate)
+{
+ // we need to configure
+ if (freq != m_frequency || chan != m_channels || prec != m_precision)
+ {
+ m_frequency = freq;
+ m_channels = chan;
+ m_precision = prec;
+ m_bps = freq * chan * (prec / 8);
+ snd_pcm_hw_params_alloca(&hwparams);
+ if (snd_pcm_hw_params_any(pcm_handle, hwparams) < 0)
+ {
+ qWarning("OutputALSA: Can not configure this PCM device.");
+ return;
+ }
+
+ uint rate = m_frequency; /* Sample rate */
+ uint exact_rate = m_frequency; /* Sample rate returned by */
+
+ /* load settings from config */
+ QSettings settings(QDir::homePath()+"/.qmmp/qmmprc", QSettings::IniFormat);
+ settings.beginGroup("ALSA");
+ uint buffer_time = settings.value("buffer_time",500).toUInt()*1000;
+ uint period_time = settings.value("period_time",100).toUInt()*1000;
+ settings.endGroup();
+
+ if (snd_pcm_hw_params_set_access(pcm_handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED) < 0)
+ {
+ qWarning("OutputALSA: Error setting access.");
+ return;
+ }
+
+
+ if (snd_pcm_hw_params_set_format(pcm_handle, hwparams, SND_PCM_FORMAT_S16_LE) < 0)
+ {
+ qDebug("OutputALSA: Error setting format.");
+ return;
+ }
+
+
+ exact_rate = rate;// = 11000;
+ qDebug("OutputALSA: frequency=%d, channels=%d, bitrate=%d",
+ rate, chan, brate);
+ if (snd_pcm_hw_params_set_rate_near(pcm_handle, hwparams, &exact_rate, 0) < 0)
+ {
+ qWarning("OutputALSA: Error setting rate.\n");
+ return;
+ }
+ if (rate != exact_rate)
+ {
+ qWarning("OutputALSA: The rate %d Hz is not supported by your hardware.\n==> Using %d Hz instead.", rate, exact_rate);
+ }
+
+ uint c = m_channels;
+ if (snd_pcm_hw_params_set_channels_near(pcm_handle, hwparams, &c) < 0)
+ {
+ qWarning("OutputALSA: Error setting channels.");
+ return;
+ }
+
+ if (snd_pcm_hw_params_set_period_time_near(pcm_handle, hwparams,
+ &period_time ,0) < 0 )
+ {
+ qWarning("OutputALSA: Error setting HW buffer.");
+ return;
+ }
+ if (snd_pcm_hw_params_set_buffer_time_near(pcm_handle, hwparams,
+ &buffer_time ,0) < 0 )
+ {
+ qWarning("Error setting HW buffer.\n");
+ return;
+ }
+ if (snd_pcm_hw_params(pcm_handle, hwparams) < 0)
+ {
+ qWarning("OutputALSA: Error setting HW params.");
+ return;
+ }
+ }
+}
+
+void OutputALSA::reset()
+{
+ if (pcm_handle)
+ {
+ snd_pcm_close(pcm_handle);
+ pcm_handle = 0;
+ }
+ if (snd_pcm_open(&pcm_handle, pcm_name, stream, SND_PCM_NONBLOCK) < 0)
+ {
+ qWarning ("OutputALSA: Error opening PCM device %s", pcm_name);
+ return;
+ }
+}
+
+
+void OutputALSA::pause()
+{
+ if (!m_play)
+ return;
+ m_pause = (m_pause) ? FALSE : TRUE;
+ OutputState::Type state = m_pause ? OutputState::Paused: OutputState::Playing;
+ dispatch(state);
+}
+
+bool OutputALSA::initialize()
+{
+ m_inited = m_pause = m_play = m_userStop = FALSE;
+
+ if (!pcm_handle < 0)
+ return FALSE;
+
+ m_currentSeconds = -1;
+ m_totalWritten = 0;
+ if (snd_pcm_open(&pcm_handle, pcm_name, stream, SND_PCM_NONBLOCK) < 0)
+ {
+ qWarning ("OutputALSA: Error opening PCM device %s", pcm_name);
+ return FALSE;
+ }
+
+ m_inited = TRUE;
+ return TRUE;
+}
+
+
+long OutputALSA::latency()
+{
+ long used = 0;
+
+ /*if (! m_pause)
+ {
+ if (ioctl(audio_fd, SNDCTL_DSP_GETODELAY, &used) == -1)
+ used = 0;
+ }*/
+
+ return used;
+}
+
+void OutputALSA::run()
+{
+
+ mutex()->lock ();
+ if (! m_inited)
+ {
+ mutex()->unlock();
+ return;
+ }
+
+ m_play = TRUE;
+
+ mutex()->unlock();
+
+ Buffer *b = 0;
+ bool done = FALSE;
+ unsigned long n = 0;
+ long m = 0;
+ snd_pcm_uframes_t l;
+
+ dispatch(OutputState::Playing);
+
+ while (! done)
+ {
+ mutex()->lock ();
+ recycler()->mutex()->lock ();
+
+ done = m_userStop;
+
+ while (! done && (recycler()->empty() || m_pause))
+ {
+ mutex()->unlock();
+ recycler()->cond()->wakeOne();
+ recycler()->cond()->wait(recycler()->mutex());
+ mutex()->lock ();
+ done = m_userStop;
+ status();
+ }
+
+ if (! b)
+ {
+ b = recycler()->next();
+ if (b->rate)
+ m_rate = b->rate;
+ }
+
+ recycler()->cond()->wakeOne();
+ recycler()->mutex()->unlock();
+
+ if (b)
+ {
+ l = snd_pcm_bytes_to_frames(pcm_handle, b->nbytes - n);
+ while (l>0)
+ {
+ m = snd_pcm_writei (pcm_handle, b->data+n, l);
+
+ if (m > 0)
+ {
+ n += snd_pcm_frames_to_bytes(pcm_handle, m);
+ l -= m;
+ status();
+ dispatchVisual(b, m_totalWritten, m_channels, m_precision);
+ }
+ else if (m == -EAGAIN)
+ {
+ mutex()->unlock();
+ snd_pcm_wait(pcm_handle, 500);
+ mutex()->lock ();
+ }
+ else if (m == -EPIPE)
+ {
+ qDebug ("OutputALSA: underrun!");
+ if ((m = snd_pcm_prepare(pcm_handle)) < 0)
+ {
+ qDebug ("OutputALSA: Can't recover after underrun: %s",
+ snd_strerror(m));
+ /* TODO: reopen the device */
+ break;
+ }
+ }
+ else if (m == -ESTRPIPE)
+ {
+ qDebug ("OutputALSA: Suspend, trying to resume");
+ while ((m = snd_pcm_resume(pcm_handle))
+ == -EAGAIN)
+ sleep (1);
+ if (m < 0)
+ {
+ qDebug ("OutputALSA: Failed, restarting");
+ if ((m = snd_pcm_prepare(pcm_handle))
+ < 0)
+ {
+ qDebug ("OutputALSA: Failed to restart device: %s.",
+ snd_strerror(m));
+ break;
+ }
+ }
+ }
+ else if (m < 0)
+ {
+ qDebug ("OutputALSA: Can't play: %s", snd_strerror(m));
+ break;
+ }
+ }
+ status();
+ // force buffer change
+ m_totalWritten += n;
+ n = b->nbytes;
+ m = 0;
+ }
+ if (n == b->nbytes)
+ {
+ recycler()->mutex()->lock ();
+ recycler()->done();
+ recycler()->mutex()->unlock();
+ b = 0;
+ n = 0;
+ }
+ mutex()->unlock();
+ }
+
+ mutex()->lock ();
+
+ m_play = FALSE;
+
+ dispatch(OutputState::Stopped);
+
+ mutex()->unlock();
+
+}
+
+void OutputALSA::uninitialize()
+{
+ if (!m_inited)
+ return;
+ m_inited = FALSE;
+ m_pause = FALSE;
+ m_play = FALSE;
+ m_userStop = FALSE;
+ m_totalWritten = 0;
+ m_currentSeconds = -1;
+ m_bps = -1;
+ m_frequency = -1;
+ m_channels = -1;
+ m_precision = -1;
+ if (pcm_handle)
+ {
+ qDebug("OutputALSA: closing pcm_handle");
+ snd_pcm_close(pcm_handle);
+ pcm_handle = 0;
+ }
+ dispatch(OutputState::Stopped);
+}
+/* ****** MIXER ******* */
+
+int OutputALSA::setupMixer(QString card, QString device)
+{
+ char *name;
+ long int a, b;
+ long alsa_min_vol = 0, alsa_max_vol = 100;
+ int err, index;
+
+ qDebug("OutputALSA: setupMixer()");
+
+ if ((err = getMixer(&mixer, card)) < 0)
+ return err;
+
+ parseMixerName(device.toAscii().data(), &name, &index);
+
+ pcm_element = getMixerElem(mixer, name, index);
+
+ free(name);
+
+ if (!pcm_element)
+ {
+ qWarning("OutputALSA: Failed to find mixer element");
+ return -1;
+ }
+
+ /* This hack was copied from xmms.
+ * Work around a bug in alsa-lib up to 1.0.0rc2 where the
+ * new range don't take effect until the volume is changed.
+ * This hack should be removed once we depend on Alsa 1.0.0.
+ */
+ snd_mixer_selem_get_playback_volume(pcm_element,
+ SND_MIXER_SCHN_FRONT_LEFT, &a);
+ snd_mixer_selem_get_playback_volume(pcm_element,
+ SND_MIXER_SCHN_FRONT_RIGHT, &b);
+
+ snd_mixer_selem_get_playback_volume_range(pcm_element,
+ &alsa_min_vol, &alsa_max_vol);
+ snd_mixer_selem_set_playback_volume_range(pcm_element, 0, 100);
+
+ if (alsa_max_vol == 0)
+ {
+ pcm_element = NULL;
+ return -1;
+ }
+
+ setVolume(a * 100 / alsa_max_vol, b * 100 / alsa_max_vol);
+
+ qDebug("OutputALSA: setupMixer() succes");
+
+ return 0;
+}
+
+void OutputALSA::parseMixerName(char *str, char **name, int *index)
+{
+ char *end;
+
+ while (isspace(*str))
+ str++;
+
+ if ((end = strchr(str, ',')) != NULL)
+ {
+ *name = strndup(str, end - str);
+ end++;
+ *index = atoi(end);
+ }
+ else
+ {
+ *name = strdup(str);
+ *index = 0;
+ }
+}
+
+snd_mixer_elem_t* OutputALSA::getMixerElem(snd_mixer_t *mixer, char *name, int index)
+{
+ snd_mixer_selem_id_t* selem_id;
+ snd_mixer_elem_t* elem;
+ snd_mixer_selem_id_alloca(&selem_id);
+
+ if (index != -1)
+ snd_mixer_selem_id_set_index(selem_id, index);
+ if (name != NULL)
+ snd_mixer_selem_id_set_name(selem_id, name);
+
+ elem = snd_mixer_find_selem(mixer, selem_id);
+
+ return elem;
+}
+
+void OutputALSA::setVolume(int l, int r)
+{
+
+ if (!pcm_element)
+ return;
+
+ snd_mixer_selem_set_playback_volume(pcm_element,
+ SND_MIXER_SCHN_FRONT_LEFT, l);
+ snd_mixer_selem_set_playback_volume(pcm_element,
+ SND_MIXER_SCHN_FRONT_RIGHT, r);
+}
+
+void OutputALSA::volume(int * l, int * r)
+{
+ if (!pcm_element)
+ return;
+ snd_mixer_handle_events(mixer);
+ snd_mixer_selem_get_playback_volume(pcm_element,
+ SND_MIXER_SCHN_FRONT_LEFT, (long int*)l);
+ snd_mixer_selem_get_playback_volume(pcm_element,
+ SND_MIXER_SCHN_FRONT_RIGHT, (long int*)r);
+}
+
+int OutputALSA::getMixer(snd_mixer_t **mixer, QString card)
+{
+ char *dev;
+ int err;
+
+
+ dev = strdup(card.toAscii().data());
+
+ if ((err = snd_mixer_open(mixer, 0)) < 0)
+ {
+ qWarning("OutputALSA: Failed to open empty mixer: %s",
+ snd_strerror(-err));
+ mixer = NULL;
+ return -1;
+ }
+ if ((err = snd_mixer_attach(*mixer, dev)) < 0)
+ {
+ qWarning("OutputALSA: Attaching to mixer %s failed: %s",
+ dev, snd_strerror(-err));
+ return -1;
+ }
+ if ((err = snd_mixer_selem_register(*mixer, NULL, NULL)) < 0)
+ {
+ qWarning("OutputALSA: Failed to register mixer: %s",
+ snd_strerror(-err));
+ return -1;
+ }
+ if ((err = snd_mixer_load(*mixer)) < 0)
+ {
+ qWarning("OutputALSA: Failed to load mixer: %s",
+ snd_strerror(-err));
+ return -1;
+ }
+
+ free(dev);
+
+ return (*mixer != NULL);
+}
+
+
+
diff --git a/src/plugins/Output/alsa/outputalsa.h b/src/plugins/Output/alsa/outputalsa.h
new file mode 100644
index 000000000..f3dd222fe
--- /dev/null
+++ b/src/plugins/Output/alsa/outputalsa.h
@@ -0,0 +1,85 @@
+/***************************************************************************
+ * Copyright (C) 2006 by Ilya Kotov *
+ * forkotov02@hotmail.ru *
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program; if not, write to the *
+ * Free Software Foundation, Inc., *
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
+ ***************************************************************************/
+
+#ifndef OUTPUTALSA_H
+#define OUTPUTALSA_H
+
+class OutputALSA;
+
+#include <output.h>
+#include <QObject>
+extern "C" {
+#include <alsa/asoundlib.h>
+}
+#if defined( Q_OS_WIN32 )
+#include <dsound.h>
+#include "constants.h"
+#endif
+
+
+class OutputALSA : public Output
+{
+Q_OBJECT
+public:
+ OutputALSA(QObject * parent = 0, bool useVolume = TRUE);
+ ~OutputALSA();
+
+ bool initialize();
+ bool isInitialized() const { return m_inited; }
+ void uninitialize();
+ void configure(long, int, int, int);
+ void stop();
+ void pause();
+ long written();
+ long latency();
+ void seek(long);
+ void setVolume(int l, int r);
+ void volume(int *l, int *r);
+ void checkVolume();
+
+private:
+ // thread run function
+ void run();
+
+ // helper functions
+ void reset();
+ void status();
+
+ bool m_inited, m_pause, m_play, m_userStop;
+ long m_totalWritten, m_currentSeconds, m_bps;
+ int m_rate, m_frequency, m_channels, m_precision;
+ //alsa
+ snd_pcm_t *pcm_handle;
+ snd_pcm_stream_t stream;
+ snd_pcm_hw_params_t *hwparams;
+ char *pcm_name;
+ //alsa
+
+ //alsa mixer
+ int setupMixer(QString card, QString device);
+ void parseMixerName(char *str, char **name, int *index);
+ int getMixer(snd_mixer_t **mixer, QString card);
+ snd_mixer_elem_t* getMixerElem(snd_mixer_t *mixer, char *name, int index);
+ snd_mixer_t *mixer;
+ snd_mixer_elem_t *pcm_element;
+};
+
+
+#endif // OUTPUTALSA_H
diff --git a/src/plugins/Output/alsa/outputalsafactory.cpp b/src/plugins/Output/alsa/outputalsafactory.cpp
new file mode 100644
index 000000000..641ff1278
--- /dev/null
+++ b/src/plugins/Output/alsa/outputalsafactory.cpp
@@ -0,0 +1,63 @@
+/***************************************************************************
+ * Copyright (C) 2007 by Ilya Kotov *
+ * forkotov02@hotmail.ru *
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program; if not, write to the *
+ * Free Software Foundation, Inc., *
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
+ ***************************************************************************/
+
+#include <QtGui>
+
+#include "settingsdialog.h"
+#include "outputalsa.h"
+#include "outputalsafactory.h"
+
+
+const OutputProperties OutputALSAFactory::properties() const
+{
+ OutputProperties properties;
+ properties.name = tr("ALSA Plugin");
+ properties.hasAbout = TRUE;
+ properties.hasSettings = TRUE;
+ return properties;
+}
+
+Output* OutputALSAFactory::create(QObject* parent, bool volume)
+{
+ return new OutputALSA(parent, volume);
+}
+
+void OutputALSAFactory::showSettings(QWidget* parent)
+{
+ SettingsDialog *s = new SettingsDialog(parent);
+ s -> show();
+}
+
+void OutputALSAFactory::showAbout(QWidget *parent)
+{
+ QMessageBox::about (parent, tr("About ALSA Output Plugin"),
+ tr("Qmmp ALSA Output Plugin")+"\n"+
+ tr("Writen by: Ilya Kotov <forkotov02@hotmail.ru>"));
+}
+
+QTranslator *OutputALSAFactory::createTranslator(QObject *parent)
+{
+ QTranslator *translator = new QTranslator(parent);
+ QString locale = QLocale::system().name();
+ translator->load(QString(":/alsa_plugin_") + locale);
+ return translator;
+}
+
+Q_EXPORT_PLUGIN(OutputALSAFactory)
diff --git a/src/plugins/Output/alsa/outputalsafactory.h b/src/plugins/Output/alsa/outputalsafactory.h
new file mode 100644
index 000000000..afaa18358
--- /dev/null
+++ b/src/plugins/Output/alsa/outputalsafactory.h
@@ -0,0 +1,48 @@
+/***************************************************************************
+ * Copyright (C) 2007 by Ilya Kotov *
+ * forkotov02@hotmail.ru *
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program; if not, write to the *
+ * Free Software Foundation, Inc., *
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
+ ***************************************************************************/
+#ifndef OUTPUTALSAFACTORY_H
+#define OUTPUTALSAFACTORY_H
+
+
+#include <QObject>
+#include <QString>
+#include <QIODevice>
+#include <QWidget>
+
+#include <output.h>
+#include <outputfactory.h>
+
+
+class OutputALSAFactory : public QObject,
+ OutputFactory
+{
+Q_OBJECT
+Q_INTERFACES(OutputFactory);
+
+public:
+ const OutputProperties properties() const;
+ Output* create(QObject* parent, bool volume);
+ void showSettings(QWidget* parent);
+ void showAbout(QWidget *parent);
+ QTranslator *createTranslator(QObject *parent);
+
+};
+
+#endif
diff --git a/src/plugins/Output/alsa/settingsdialog.cpp b/src/plugins/Output/alsa/settingsdialog.cpp
new file mode 100644
index 000000000..89c6cae84
--- /dev/null
+++ b/src/plugins/Output/alsa/settingsdialog.cpp
@@ -0,0 +1,237 @@
+/***************************************************************************
+ * Copyright (C) 2006 by Ilya Kotov *
+ * forkotov02@hotmail.ru *
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program; if not, write to the *
+ * Free Software Foundation, Inc., *
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
+ ***************************************************************************/
+#include <QSettings>
+#include <QDir>
+
+extern "C"
+{
+#include <alsa/asoundlib.h>
+}
+
+#include "settingsdialog.h"
+
+SettingsDialog::SettingsDialog ( QWidget *parent )
+ : QDialog ( parent )
+{
+ ui.setupUi ( this );
+ setAttribute ( Qt::WA_DeleteOnClose );
+ ui.deviceComboBox->setEditable ( TRUE );
+ getCards();
+ connect (ui.deviceComboBox, SIGNAL(activated(int)),SLOT(setText(int)));
+ connect(ui.okButton, SIGNAL(clicked()), SLOT(writeSettings()));
+ connect(ui.mixerCardComboBox, SIGNAL(activated(int)), SLOT(showMixerDevices(int)));
+ QSettings settings(QDir::homePath()+"/.qmmp/qmmprc", QSettings::IniFormat);
+ settings.beginGroup("ALSA");
+ ui.deviceComboBox->setEditText(settings.value("device","default").toString());
+ ui.bufferSpinBox->setValue(settings.value("buffer_time",500).toInt());
+ ui.periodSpinBox->setValue(settings.value("period_time",100).toInt());
+
+ int d = m_cards.indexOf(settings.value("mixer_card","hw:0").toString());
+ if (d >= 0)
+ ui.mixerCardComboBox->setCurrentIndex(d);
+
+ showMixerDevices(ui.mixerCardComboBox->currentIndex ());
+ d = ui.mixerDeviceComboBox->findText(settings.value("mixer_device",
+ "PCM").toString());
+
+ if (d >= 0)
+ ui.mixerDeviceComboBox->setCurrentIndex(d);
+
+ settings.endGroup();
+}
+
+
+SettingsDialog::~SettingsDialog()
+{}
+
+void SettingsDialog::getCards()
+{
+ int card = -1, err;
+
+ m_devices.clear();
+ m_devices << "default";
+ ui.deviceComboBox->addItem("Default PCM device (default)");
+
+ if ((err = snd_card_next(&card)) !=0)
+ qWarning("SettingsDialog (ALSA): snd_next_card() failed: %s",
+ snd_strerror(-err));
+
+ while (card > -1)
+ {
+ getCardDevices(card);
+ m_cards << QString("hw:%1").arg(card);
+ if ((err = snd_card_next(&card)) !=0)
+ {
+ qWarning("SettingsDialog (ALSA): snd_next_card() failed: %s",
+ snd_strerror(-err));
+ break;
+ }
+ }
+}
+
+void SettingsDialog::getCardDevices(int card)
+{
+ int pcm_device = -1, err;
+ snd_pcm_info_t *pcm_info;
+ snd_ctl_t *ctl;
+ char dev[64], *card_name;
+
+ sprintf(dev, "hw:%i", card);
+
+ if ((err = snd_ctl_open(&ctl, dev, 0)) < 0)
+ {
+ qWarning("SettingsDialog (ALSA): snd_ctl_open() failed: %s",
+ snd_strerror(-err));
+ return;
+ }
+
+ if ((err = snd_card_get_name(card, &card_name)) != 0)
+ {
+ qWarning("SettingsDialog (ALSA): snd_card_get_name() failed: %s",
+ snd_strerror(-err));
+ card_name = "Unknown soundcard";
+ }
+ ui.mixerCardComboBox->addItem(QString(card_name));
+
+ snd_pcm_info_alloca(&pcm_info);
+
+ qDebug("SettingsDialog (ALSA): detected sound cards:");
+
+ for (;;)
+ {
+ QString device;
+ if ((err = snd_ctl_pcm_next_device(ctl, &pcm_device)) < 0)
+ {
+ qWarning("SettingsDialog (ALSA): snd_ctl_pcm_next_device() failed: %s",
+ snd_strerror(-err));
+ pcm_device = -1;
+ }
+ if (pcm_device < 0)
+ break;
+
+ snd_pcm_info_set_device(pcm_info, pcm_device);
+ snd_pcm_info_set_subdevice(pcm_info, 0);
+ snd_pcm_info_set_stream(pcm_info, SND_PCM_STREAM_PLAYBACK);
+
+ if ((err = snd_ctl_pcm_info(ctl, pcm_info)) < 0)
+ {
+ if (err != -ENOENT)
+ qWarning("SettingsDialog (ALSA): get_devices_for_card(): "
+ "snd_ctl_pcm_info() "
+ "failed (%d:%d): %s.", card,
+ pcm_device, snd_strerror(-err));
+ }
+ device = QString("hw:%1,%2").arg(card).arg(pcm_device);
+ m_devices << device;
+ QString str;
+ str = QString(card_name) + ": "+
+ snd_pcm_info_get_name(pcm_info)+" ("+device+")";
+ qDebug(str.toAscii());
+ ui.deviceComboBox->addItem(str);
+ }
+
+ snd_ctl_close(ctl);
+}
+
+void SettingsDialog::getMixerDevices(QString card)
+{
+ ui.mixerDeviceComboBox->clear();
+ int err;
+ snd_mixer_t *mixer;
+ snd_mixer_elem_t *current;
+
+ if ((err = getMixer(&mixer, card)) < 0)
+ return;
+
+ current = snd_mixer_first_elem(mixer);
+
+ while (current)
+ {
+ const char *sname = snd_mixer_selem_get_name(current);
+ if (snd_mixer_selem_is_active(current) &&
+ snd_mixer_selem_has_playback_volume(current))
+ ui.mixerDeviceComboBox->addItem(QString(sname));
+ current = snd_mixer_elem_next(current);
+ }
+}
+
+void SettingsDialog::setText(int n)
+{
+ ui.deviceComboBox->setEditText(m_devices.at(n));
+}
+
+void SettingsDialog::writeSettings()
+{
+ qDebug("SettingsDialog (ALSA):: writeSettings()");
+ QSettings settings(QDir::homePath()+"/.qmmp/qmmprc", QSettings::IniFormat);
+ settings.beginGroup("ALSA");
+ settings.setValue("device", ui.deviceComboBox->currentText ());
+ settings.setValue("buffer_time",ui.bufferSpinBox->value());
+ settings.setValue("period_time",ui.periodSpinBox->value());
+ QString card = m_cards.at(ui.mixerCardComboBox->currentIndex());
+ settings.setValue("mixer_card", card);
+ settings.setValue("mixer_device", ui.mixerDeviceComboBox->currentText ());
+ settings.endGroup();
+ accept();
+}
+
+int SettingsDialog::getMixer(snd_mixer_t **mixer, QString card)
+{
+ char *dev;
+ int err;
+
+ dev = strdup(QString(card).toAscii().data());
+ if ((err = snd_mixer_open(mixer, 0)) < 0)
+ {
+ qWarning("SettingsDialog (ALSA): alsa_get_mixer(): "
+ "Failed to open empty mixer: %s", snd_strerror(-err));
+ mixer = NULL;
+ return -1;
+ }
+ if ((err = snd_mixer_attach(*mixer, dev)) < 0)
+ {
+ qWarning("SettingsDialog (ALSA): alsa_get_mixer(): "
+ "Attaching to mixer %s failed: %s", dev, snd_strerror(-err));
+ return -1;
+ }
+ if ((err = snd_mixer_selem_register(*mixer, NULL, NULL)) < 0)
+ {
+ qWarning("SettingsDialog (ALSA): alsa_get_mixer(): "
+ "Failed to register mixer: %s", snd_strerror(-err));
+ return -1;
+ }
+ if ((err = snd_mixer_load(*mixer)) < 0)
+ {
+ qWarning("SettingsDialog (ALSA): alsa_get_mixer(): Failed to load mixer: %s",
+ snd_strerror(-err));
+ return -1;
+ }
+
+ free (dev);
+
+ return (*mixer != NULL);
+}
+
+void SettingsDialog::showMixerDevices(int d)
+{
+ if (0<=d && d<m_cards.size())
+ getMixerDevices(m_cards.at(d));
+}
+
diff --git a/src/plugins/Output/alsa/settingsdialog.h b/src/plugins/Output/alsa/settingsdialog.h
new file mode 100644
index 000000000..467b25a03
--- /dev/null
+++ b/src/plugins/Output/alsa/settingsdialog.h
@@ -0,0 +1,59 @@
+/***************************************************************************
+ * Copyright (C) 2006 by Ilya Kotov *
+ * forkotov02@hotmail.ru *
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program; if not, write to the *
+ * Free Software Foundation, Inc., *
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
+ ***************************************************************************/
+#ifndef SETTINGSDIALOG_H
+#define SETTINGSDIALOG_H
+
+#include <QDialog>
+
+extern "C"{
+#include <alsa/asoundlib.h>
+}
+//#include <alsa/pcm_plugin.h>
+
+#include "ui_settingsdialog.h"
+
+/**
+ @author Ilya Kotov <forkotov02@hotmail.ru>
+*/
+class SettingsDialog : public QDialog
+{
+Q_OBJECT
+public:
+ SettingsDialog(QWidget *parent = 0);
+
+ ~SettingsDialog();
+
+private slots:
+ void setText(int);
+ void writeSettings();
+ void showMixerDevices(int);
+
+private:
+ Ui::SettingsDialog ui;
+ void getCards();
+ void getCardDevices(int card);
+ void getMixerDevices(QString card);
+ int getMixer(snd_mixer_t **mixer, QString card);
+ QStringList m_devices;
+ QList <QString> m_cards;
+
+};
+
+#endif
diff --git a/src/plugins/Output/alsa/settingsdialog.ui b/src/plugins/Output/alsa/settingsdialog.ui
new file mode 100644
index 000000000..2f9a20753
--- /dev/null
+++ b/src/plugins/Output/alsa/settingsdialog.ui
@@ -0,0 +1,261 @@
+<ui version="4.0" >
+ <class>SettingsDialog</class>
+ <widget class="QDialog" name="SettingsDialog" >
+ <property name="geometry" >
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>403</width>
+ <height>300</height>
+ </rect>
+ </property>
+ <property name="windowTitle" >
+ <string>ALSA Plugin Settings</string>
+ </property>
+ <layout class="QGridLayout" >
+ <property name="margin" >
+ <number>9</number>
+ </property>
+ <property name="spacing" >
+ <number>6</number>
+ </property>
+ <item row="0" column="0" colspan="3" >
+ <widget class="QTabWidget" name="tabWidget" >
+ <property name="currentIndex" >
+ <number>0</number>
+ </property>
+ <widget class="QWidget" name="tab" >
+ <attribute name="title" >
+ <string>Device Settings</string>
+ </attribute>
+ <layout class="QVBoxLayout" >
+ <property name="margin" >
+ <number>9</number>
+ </property>
+ <property name="spacing" >
+ <number>6</number>
+ </property>
+ <item>
+ <widget class="QGroupBox" name="groupBox" >
+ <property name="title" >
+ <string>Audio device</string>
+ </property>
+ <widget class="QComboBox" name="deviceComboBox" >
+ <property name="geometry" >
+ <rect>
+ <x>11</x>
+ <y>47</y>
+ <width>338</width>
+ <height>22</height>
+ </rect>
+ </property>
+ </widget>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="groupBox_2" >
+ <property name="title" >
+ <string>Mixer</string>
+ </property>
+ <layout class="QGridLayout" >
+ <property name="margin" >
+ <number>9</number>
+ </property>
+ <property name="spacing" >
+ <number>6</number>
+ </property>
+ <item row="0" column="1" >
+ <widget class="QComboBox" name="mixerCardComboBox" />
+ </item>
+ <item row="0" column="0" >
+ <widget class="QLabel" name="label_3" >
+ <property name="text" >
+ <string>Mixer card:</string>
+ </property>
+ <property name="alignment" >
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0" >
+ <widget class="QLabel" name="label_4" >
+ <property name="text" >
+ <string>Mixer device:</string>
+ </property>
+ <property name="alignment" >
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1" >
+ <widget class="QComboBox" name="mixerDeviceComboBox" />
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="tab_2" >
+ <attribute name="title" >
+ <string>Advanced Settings</string>
+ </attribute>
+ <layout class="QVBoxLayout" >
+ <property name="margin" >
+ <number>9</number>
+ </property>
+ <property name="spacing" >
+ <number>6</number>
+ </property>
+ <item>
+ <widget class="QGroupBox" name="groupBox_3" >
+ <property name="title" >
+ <string>Soundcard</string>
+ </property>
+ <layout class="QGridLayout" >
+ <property name="margin" >
+ <number>9</number>
+ </property>
+ <property name="spacing" >
+ <number>6</number>
+ </property>
+ <item row="2" column="1" >
+ <spacer>
+ <property name="orientation" >
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" >
+ <size>
+ <width>20</width>
+ <height>111</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="0" column="2" >
+ <spacer>
+ <property name="orientation" >
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" >
+ <size>
+ <width>188</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="1" column="1" >
+ <widget class="QSpinBox" name="periodSpinBox" >
+ <property name="maximum" >
+ <number>5000</number>
+ </property>
+ <property name="minimum" >
+ <number>20</number>
+ </property>
+ <property name="value" >
+ <number>100</number>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1" >
+ <widget class="QSpinBox" name="bufferSpinBox" >
+ <property name="maximum" >
+ <number>10000</number>
+ </property>
+ <property name="minimum" >
+ <number>200</number>
+ </property>
+ <property name="value" >
+ <number>500</number>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0" >
+ <widget class="QLabel" name="label" >
+ <property name="text" >
+ <string>Buffer time (ms):</string>
+ </property>
+ <property name="alignment" >
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0" >
+ <widget class="QLabel" name="label_2" >
+ <property name="text" >
+ <string>Period time (ms):</string>
+ </property>
+ <property name="alignment" >
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="2" >
+ <spacer>
+ <property name="orientation" >
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" >
+ <size>
+ <width>188</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ </item>
+ <item row="1" column="0" >
+ <spacer>
+ <property name="orientation" >
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" >
+ <size>
+ <width>191</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="1" column="2" >
+ <widget class="QPushButton" name="cancelButton" >
+ <property name="text" >
+ <string>Cancel</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1" >
+ <widget class="QPushButton" name="okButton" >
+ <property name="text" >
+ <string>OK</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>cancelButton</sender>
+ <signal>clicked()</signal>
+ <receiver>SettingsDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>338</x>
+ <y>283</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>164</x>
+ <y>294</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/src/plugins/Output/alsa/translations/alsa_plugin_cs.ts b/src/plugins/Output/alsa/translations/alsa_plugin_cs.ts
new file mode 100644
index 000000000..3108490ec
--- /dev/null
+++ b/src/plugins/Output/alsa/translations/alsa_plugin_cs.ts
@@ -0,0 +1,90 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS><TS version="1.1" language="pl">
+<defaultcodec></defaultcodec>
+<context>
+ <name>OutputALSAFactory</name>
+ <message>
+ <location filename="../outputalsafactory.cpp" line="30"/>
+ <source>ALSA Plugin</source>
+ <translation>Plugin ALSA</translation>
+ </message>
+ <message>
+ <location filename="../outputalsafactory.cpp" line="47"/>
+ <source>About ALSA Output Plugin</source>
+ <translation>O pluginu ALSA</translation>
+ </message>
+ <message>
+ <location filename="../outputalsafactory.cpp" line="48"/>
+ <source>Qmmp ALSA Output Plugin</source>
+ <translation>Výstupní plugin Qmmp ALSA</translation>
+ </message>
+ <message>
+ <location filename="../outputalsafactory.cpp" line="49"/>
+ <source>Writen by: Ilya Kotov &lt;forkotov02@hotmail.ru&gt;</source>
+ <translation>Autor: Ilja Kotov &lt;forkotov02@hotmail.ru&gt;</translation>
+ </message>
+</context>
+<context>
+ <name>SettingsDialog</name>
+ <message>
+ <location filename="../settingsdialog.ui" line="13"/>
+ <source>ALSA Plugin Settings</source>
+ <translation>Nastavení pluginu ALSA</translation>
+ </message>
+ <message>
+ <location filename="../settingsdialog.ui" line="29"/>
+ <source>Device Settings</source>
+ <translation>Nastavení zařízení</translation>
+ </message>
+ <message>
+ <location filename="../settingsdialog.ui" line="41"/>
+ <source>Audio device</source>
+ <translation>Zvukové zařízení</translation>
+ </message>
+ <message>
+ <location filename="../settingsdialog.ui" line="58"/>
+ <source>Mixer</source>
+ <translation>Mixér</translation>
+ </message>
+ <message>
+ <location filename="../settingsdialog.ui" line="73"/>
+ <source>Mixer card:</source>
+ <translation>Zvuková karta:</translation>
+ </message>
+ <message>
+ <location filename="../settingsdialog.ui" line="83"/>
+ <source>Mixer device:</source>
+ <translation>Ovládání hlasitosti:</translation>
+ </message>
+ <message>
+ <location filename="../settingsdialog.ui" line="100"/>
+ <source>Advanced Settings</source>
+ <translation>Pokročilá nastavení</translation>
+ </message>
+ <message>
+ <location filename="../settingsdialog.ui" line="112"/>
+ <source>Soundcard</source>
+ <translation>Zvuková karta</translation>
+ </message>
+ <message>
+ <location filename="../settingsdialog.ui" line="176"/>
+ <source>Buffer time (ms):</source>
+ <translation>Velikost bufferu (ms):</translation>
+ </message>
+ <message>
+ <location filename="../settingsdialog.ui" line="186"/>
+ <source>Period time (ms):</source>
+ <translation type="unfinished">Délka periody (ms):</translation>
+ </message>
+ <message>
+ <location filename="../settingsdialog.ui" line="229"/>
+ <source>Cancel</source>
+ <translation>Zrušit</translation>
+ </message>
+ <message>
+ <location filename="../settingsdialog.ui" line="236"/>
+ <source>OK</source>
+ <translation>OK</translation>
+ </message>
+</context>
+</TS>
diff --git a/src/plugins/Output/alsa/translations/alsa_plugin_ru.qm b/src/plugins/Output/alsa/translations/alsa_plugin_ru.qm
new file mode 100644
index 000000000..78ed38962
--- /dev/null
+++ b/src/plugins/Output/alsa/translations/alsa_plugin_ru.qm
Binary files differ
diff --git a/src/plugins/Output/alsa/translations/alsa_plugin_ru.ts b/src/plugins/Output/alsa/translations/alsa_plugin_ru.ts
new file mode 100644
index 000000000..2c7296750
--- /dev/null
+++ b/src/plugins/Output/alsa/translations/alsa_plugin_ru.ts
@@ -0,0 +1,89 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS><TS version="1.1" language="ru">
+<context>
+ <name>OutputALSAFactory</name>
+ <message>
+ <location filename="../outputalsafactory.cpp" line="30"/>
+ <source>ALSA Plugin</source>
+ <translation>Модуль ALSA</translation>
+ </message>
+ <message>
+ <location filename="../outputalsafactory.cpp" line="47"/>
+ <source>About ALSA Output Plugin</source>
+ <translation>О модуле вывода ALSA</translation>
+ </message>
+ <message>
+ <location filename="../outputalsafactory.cpp" line="48"/>
+ <source>Qmmp ALSA Output Plugin</source>
+ <translation>Модуль вывода ALSA для Qmmp</translation>
+ </message>
+ <message>
+ <location filename="../outputalsafactory.cpp" line="49"/>
+ <source>Writen by: Ilya Kotov &lt;forkotov02@hotmail.ru&gt;</source>
+ <translation>Разработчик: Илья Котов &lt;forkotov02@hotmail.ru&gt;</translation>
+ </message>
+</context>
+<context>
+ <name>SettingsDialog</name>
+ <message>
+ <location filename="../settingsdialog.ui" line="29"/>
+ <source>Device Settings</source>
+ <translation>Параметры устройства</translation>
+ </message>
+ <message>
+ <location filename="../settingsdialog.ui" line="41"/>
+ <source>Audio device</source>
+ <translation>Аудио устройство</translation>
+ </message>
+ <message>
+ <location filename="../settingsdialog.ui" line="58"/>
+ <source>Mixer</source>
+ <translation>Микшер</translation>
+ </message>
+ <message>
+ <location filename="../settingsdialog.ui" line="73"/>
+ <source>Mixer card:</source>
+ <translation>Карта микшера:</translation>
+ </message>
+ <message>
+ <location filename="../settingsdialog.ui" line="83"/>
+ <source>Mixer device:</source>
+ <translation>Устройство микшера:</translation>
+ </message>
+ <message>
+ <location filename="../settingsdialog.ui" line="100"/>
+ <source>Advanced Settings</source>
+ <translation>Дополнительные настройки</translation>
+ </message>
+ <message>
+ <location filename="../settingsdialog.ui" line="112"/>
+ <source>Soundcard</source>
+ <translation>Звуковая карта</translation>
+ </message>
+ <message>
+ <location filename="../settingsdialog.ui" line="176"/>
+ <source>Buffer time (ms):</source>
+ <translation>Время буферизации (мс):</translation>
+ </message>
+ <message>
+ <location filename="../settingsdialog.ui" line="186"/>
+ <source>Period time (ms):</source>
+ <translation>Время периода (мс):</translation>
+ </message>
+ <message>
+ <location filename="../settingsdialog.ui" line="229"/>
+ <source>Cancel</source>
+ <translation>Отмена</translation>
+ </message>
+ <message>
+ <location filename="../settingsdialog.ui" line="236"/>
+ <source>OK</source>
+ <translation>Применить</translation>
+ </message>
+ <message>
+ <location filename="../settingsdialog.ui" line="13"/>
+ <source>ALSA Plugin Settings</source>
+ <translation>Настройки модуля ALSA</translation>
+ </message>
+</context>
+</TS>
diff --git a/src/plugins/Output/alsa/translations/translations.qrc b/src/plugins/Output/alsa/translations/translations.qrc
new file mode 100644
index 000000000..beac1cd17
--- /dev/null
+++ b/src/plugins/Output/alsa/translations/translations.qrc
@@ -0,0 +1,6 @@
+<!DOCTYPE RCC>
+<RCC version="1.0">
+ <qresource>
+ <file>alsa_plugin_ru.qm</file>
+ </qresource>
+</RCC>
diff --git a/src/plugins/Output/jack/CMakeLists.txt b/src/plugins/Output/jack/CMakeLists.txt
new file mode 100644
index 000000000..665a6fb13
--- /dev/null
+++ b/src/plugins/Output/jack/CMakeLists.txt
@@ -0,0 +1,74 @@
+project(libjack)
+
+cmake_minimum_required(VERSION 2.4.0)
+
+
+INCLUDE(UsePkgConfig)
+INCLUDE(FindQt4)
+
+find_package(Qt4 REQUIRED) # find and setup Qt4 for this project
+include(${QT_USE_FILE})
+
+# qt plugin
+ADD_DEFINITIONS( -Wall )
+ADD_DEFINITIONS(${QT_DEFINITIONS})
+ADD_DEFINITIONS(-DQT_PLUGIN)
+ADD_DEFINITIONS(-DQT_NO_DEBUG)
+ADD_DEFINITIONS(-DQT_SHARED)
+ADD_DEFINITIONS(-DQT_THREAD)
+
+include_directories(${CMAKE_CURRENT_BINARY_DIR})
+
+SET(QT_INCLUDES
+ ${QT_INCLUDES}
+ ${CMAKE_CURRENT_BINARY_DIR}/../../../
+)
+
+# libqmmp
+include_directories(${CMAKE_CURRENT_BINARY_DIR}/../../../)
+link_directories(${CMAKE_CURRENT_BINARY_DIR}/../../../)
+
+# libjack and taglib
+PKGCONFIG(jack JACK_INCLUDE_DIR JACK_LINK_DIR JACK_LINK_FLAGS JACK_CFLAGS)
+PKGCONFIG(samplerate SAMPLERATE_INCLUDE_DIR SAMPLERATE_LINK_DIR SAMPLERATE_LINK_FLAGS SAMPLERATE_CFLAGS)
+
+
+IF(NOT JACK_LINK_FLAGS)
+ SET(JACK_LINK_FLAGS -ljack -lrt)
+ENDIF(NOT JACK_LINK_FLAGS)
+
+IF(NOT SAMPLERATE_LINK_FLAGS)
+ SET(SAMPLERATE_LINK_FLAGS -lsamplerate)
+ENDIF(NOT SAMPLERATE_LINK_FLAGS)
+
+include_directories(${JACK_INCLUDE_DIR} ${JACK_INCLUDE_DIR})
+link_directories(${SAMPLERATE_LINK_DIR} ${SAMPLERATE_LINK_DIR})
+
+ADD_DEFINITIONS(${JACK_CFLAGS})
+ADD_DEFINITIONS(${SAMPLERATE_CFLAGS})
+
+
+SET(libjack_SRCS
+ outputjackfactory.cpp
+ outputjack.cpp
+ bio2jack.c
+)
+
+SET(libjack_MOC_HDRS
+ outputjackfactory.h
+ outputjack.h
+ bio2jack.h
+)
+
+SET(libjack_RCCS translations/translations.qrc)
+
+QT4_ADD_RESOURCES(libjack_RCC_SRCS ${libjack_RCCS})
+
+QT4_WRAP_CPP(libjack_MOC_SRCS ${libjack_MOC_HDRS})
+
+
+
+ADD_LIBRARY(jack SHARED ${libjack_SRCS} ${libjack_MOC_SRCS} ${libjack_RCC_SRCS})
+target_link_libraries(jack ${QT_LIBRARIES} -lqmmp ${JACK_LINK_FLAGS} ${SAMPLERATE_LINK_FLAGS})
+install(TARGETS jack DESTINATION ${LIB_DIR}/qmmp/Output PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_EXECUTE GROUP_READ WORLD_EXECUTE WORLD_READ)
+
diff --git a/src/plugins/Output/jack/bio2jack.c b/src/plugins/Output/jack/bio2jack.c
new file mode 100644
index 000000000..aef7ea9b7
--- /dev/null
+++ b/src/plugins/Output/jack/bio2jack.c
@@ -0,0 +1,2635 @@
+/*
+ * Copyright 2003-2006 Chris Morgan <cmorgan@alum.wpi.edu>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/* NOTE: All functions that take a jack_driver_t* do NOT lock the device, in order to get a */
+/* jack_driver_t* you must call getDriver() which will pthread_mutex_lock() */
+
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <math.h>
+#include <unistd.h>
+#include <inttypes.h>
+#include <jack/jack.h>
+#include <jack/ringbuffer.h>
+#include <pthread.h>
+#include <sys/time.h>
+#include <samplerate.h>
+
+#include "bio2jack.h"
+
+/* enable/disable TRACING through the JACK_Callback() function */
+/* this can sometimes be too much information */
+#define TRACE_CALLBACK 0
+
+/* set to 1 for verbose output */
+#define VERBOSE_OUTPUT 0
+
+/* set to 1 to enable debug messages */
+#define DEBUG_OUTPUT 0
+
+/* set to 1 to enable tracing */
+#define TRACE_ENABLE 0
+
+/* set to 1 to enable the function timers */
+#define TIMER_ENABLE 0
+
+/* set to 1 to enable tracing of getDriver() and releaseDriver() */
+#define TRACE_getReleaseDevice 0
+
+#define ENABLE_WARNINGS 0
+
+#define DEFAULT_RB_SIZE 4096
+
+#define OUTFILE stderr
+
+#if TIMER_ENABLE
+/* This seemingly construct makes timing arbitrary functions really easy
+ all you have to do is place a 'TIMER("start\n")' at the beginning and
+ a 'TIMER("stop\n")' at the end of any function and this does the rest
+ (naturally you can place any printf-compliant text you like in the argument
+ along with the associated values). */
+static struct timeval timer_now;
+#define TIMER(format,args...) gettimeofday(&timer_now,0); \
+ fprintf(OUTFILE, "%ld.%06ld: %s::%s(%d) "format, timer_now.tv_sec, timer_now.tv_usec, __FILE__, __FUNCTION__, __LINE__, ##args)
+#else
+#define TIMER(...)
+#endif
+
+#if TRACE_ENABLE
+#define TRACE(format,args...) fprintf(OUTFILE, "%s::%s(%d) "format, __FILE__, __FUNCTION__, __LINE__,##args); \
+ fflush(OUTFILE);
+#else
+#define TRACE(...)
+#endif
+
+#if DEBUG_OUTPUT
+#define DEBUG(format,args...) fprintf(OUTFILE, "%s::%s(%d) "format, __FILE__, __FUNCTION__, __LINE__,##args); \
+ fflush(OUTFILE);
+#else
+#define DEBUG(...)
+#endif
+
+#if TRACE_CALLBACK
+#define CALLBACK_TRACE(format,args...) fprintf(OUTFILE, "%s::%s(%d) "format, __FILE__, __FUNCTION__, __LINE__,##args); \
+ fflush(OUTFILE);
+#else
+#define CALLBACK_TRACE(...)
+#endif
+
+#if ENABLE_WARNINGS
+#define WARN(format,args...) fprintf(OUTFILE, "WARN: %s::%s(%d) "format, __FILE__,__FUNCTION__,__LINE__,##args); \
+ fflush(OUTFILE);
+#else
+#define WARN(...)
+#endif
+
+#define ERR(format,args...) fprintf(OUTFILE, "ERR: %s::%s(%d) "format, __FILE__,__FUNCTION__,__LINE__,##args); \
+ fflush(OUTFILE);
+
+#define min(a,b) (((a) < (b)) ? (a) : (b))
+#define max(a,b) (((a) < (b)) ? (b) : (a))
+
+#define MAX_OUTPUT_PORTS 10
+#define MAX_INPUT_PORTS 10
+
+typedef struct jack_driver_s
+{
+ bool allocated; /* whether or not this device has been allocated */
+
+ int deviceID; /* id of this device */
+ int clientCtr; /* to prevent overlapping client ids */
+ long jack_sample_rate; /* jack samples(frames) per second */
+
+ long client_sample_rate; /* client samples(frames) per second */
+ double output_sample_rate_ratio; /* ratio between jack's output rate & ours */
+ double input_sample_rate_ratio; /* ratio between our input rate & jack's */
+
+ unsigned long num_input_channels; /* number of input channels(1 is mono, 2 stereo etc..) */
+ unsigned long num_output_channels; /* number of output channels(1 is mono, 2 stereo etc..) */
+
+ unsigned long bits_per_channel; /* number of bits per channel (only 8 & 16 are currently supported) */
+
+ unsigned long bytes_per_output_frame; /* (num_output_channels * bits_per_channel) / 8 */
+ unsigned long bytes_per_input_frame; /* (num_input_channels * bits_per_channel) / 8 */
+
+ unsigned long bytes_per_jack_output_frame; /* (num_output_channels * bits_per_channel) / 8 */
+ unsigned long bytes_per_jack_input_frame; /* (num_input_channels * bits_per_channel) / 8 */
+
+ unsigned long latencyMS; /* latency in ms between writing and actual audio output of the written data */
+
+ long clientBytesInJack; /* number of INPUT bytes(from the client of bio2jack) we wrote to jack(not necessary the number of bytes we wrote to jack) */
+ long jack_buffer_size; /* size of the buffer jack will pass in to the process callback */
+
+ unsigned long callback_buffer1_size; /* number of bytes in the buffer allocated for processing data in JACK_Callback */
+ char *callback_buffer1;
+ unsigned long callback_buffer2_size; /* number of bytes in the buffer allocated for processing data in JACK_Callback */
+ char *callback_buffer2;
+
+ unsigned long rw_buffer1_size; /* number of bytes in the buffer allocated for processing data in JACK_(Read|Write) */
+ char *rw_buffer1;
+
+ struct timeval previousTime; /* time of last JACK_Callback() write to jack, allows for MS accurate bytes played */
+
+ unsigned long written_client_bytes; /* input bytes we wrote to jack, not necessarily actual bytes we wrote to jack due to channel and other conversion */
+ unsigned long played_client_bytes; /* input bytes that jack has played */
+
+ unsigned long client_bytes; /* total bytes written by the client of bio2jack via JACK_Write() */
+
+ jack_port_t *output_port[MAX_OUTPUT_PORTS]; /* output ports */
+ jack_port_t *input_port[MAX_OUTPUT_PORTS]; /* input ports */
+
+ jack_client_t *client; /* pointer to jack client */
+
+ char **jack_port_name; /* user given strings for the port names, can be NULL */
+ unsigned int jack_port_name_count; /* the number of port names given */
+
+ unsigned long jack_output_port_flags; /* flags to be passed to jack when opening the output ports */
+ unsigned long jack_input_port_flags; /* flags to be passed to jack when opening the output ports */
+
+ jack_ringbuffer_t *pPlayPtr; /* the playback ringbuffer */
+ jack_ringbuffer_t *pRecPtr; /* the recording ringbuffer */
+
+ SRC_STATE *output_src; /* SRC object for the output stream */
+ SRC_STATE *input_src; /* SRC object for the output stream */
+
+ enum status_enum state; /* one of PLAYING, PAUSED, STOPPED, CLOSED, RESET etc */
+
+ unsigned int volume[MAX_OUTPUT_PORTS]; /* percentage of sample value to preserve, 100 would be no attenuation */
+ enum JACK_VOLUME_TYPE volumeEffectType; /* linear or dbAttenuation, if dbAttenuation volume is the number of dBs of
+ attenuation to apply, 0 volume being no attenuation, full volume */
+
+ long position_byte_offset; /* an offset that we will apply to returned position queries to achieve */
+ /* the position that the user of the driver desires set */
+
+ bool in_use; /* true if this device is currently in use */
+
+ pthread_mutex_t mutex; /* mutex to lock this specific device */
+
+ /* variables used for trying to restart the connection to jack */
+ bool jackd_died; /* true if jackd has died and we should try to restart it */
+ struct timeval last_reconnect_attempt;
+} jack_driver_t;
+
+
+static char *client_name; /* the name bio2jack will use when creating a new
+ jack client. client_name_%deviceID% will be used */
+
+
+static bool do_sample_rate_conversion; /* whether the client has requested sample rate conversion,
+ default to on for improved compatibility */
+
+/*
+ Which SRC converter function we should use when doing sample rate conversion.
+ Default to the fastest of the 'good quality' set.
+ */
+static int preferred_src_converter = SRC_SINC_FASTEST;
+
+static bool init_done = 0; /* just to prevent clients from calling JACK_Init twice, that would be very bad */
+
+static enum JACK_PORT_CONNECTION_MODE port_connection_mode = CONNECT_ALL;
+
+/* enable/disable code that allows us to close a device without actually closing the jack device */
+/* this works around the issue where jack doesn't always close devices by the time the close function call returns */
+#define JACK_CLOSE_HACK 1
+
+typedef jack_default_audio_sample_t sample_t;
+typedef jack_nframes_t nframes_t;
+
+/* allocate devices for output */
+/* if you increase this past 10, you might want to update 'out_client_name = ... ' in JACK_OpenDevice */
+#define MAX_OUTDEVICES 10
+static jack_driver_t outDev[MAX_OUTDEVICES];
+
+static pthread_mutex_t device_mutex = PTHREAD_MUTEX_INITIALIZER; /* this is to lock the entire outDev array
+ to make managing it in a threaded
+ environment sane */
+
+#if JACK_CLOSE_HACK
+static void JACK_CloseDevice(jack_driver_t * drv, bool close_client);
+#else
+static void JACK_CloseDevice(jack_driver_t * drv);
+#endif
+
+
+/* Prototypes */
+static int JACK_OpenDevice(jack_driver_t * drv);
+static unsigned long JACK_GetBytesFreeSpaceFromDriver(jack_driver_t * drv);
+static void JACK_ResetFromDriver(jack_driver_t * drv);
+static long JACK_GetPositionFromDriver(jack_driver_t * drv,
+ enum pos_enum position, int type);
+static void JACK_CleanupDriver(jack_driver_t * drv);
+
+
+/* Return the difference between two timeval structures in terms of milliseconds */
+long
+TimeValDifference(struct timeval *start, struct timeval *end)
+{
+ double long ms; /* milliseconds value */
+
+ ms = end->tv_sec - start->tv_sec; /* compute seconds difference */
+ ms *= (double) 1000; /* convert to milliseconds */
+
+ ms += (double) (end->tv_usec - start->tv_usec) / (double) 1000; /* add on microseconds difference */
+
+ return (long) ms;
+}
+
+/* get a device and lock the devices mutex */
+/* */
+/* also attempt to reconnect to jack since this function is called from */
+/* most other bio2jack functions it provides a good point to attempt reconnection */
+/* */
+/* Ok, I know this looks complicated and it kind of is. The point is that when you're
+ trying to trace mutexes it's more important to know *who* called us than just that
+ we were called. This uses from pre-processor trickery so that the fprintf is actually
+ placed in the function making the getDriver call. Thus, the __FUNCTION__ and __LINE__
+ macros will actually reference our caller, rather than getDriver. The reason the
+ fprintf call is passes as a parameter is because this macro has to still return a
+ jack_driver_t* and we want to log both before *and* after the getDriver call for
+ easier detection of blocked calls.
+ */
+#if TRACE_getReleaseDevice
+#define getDriver(x) _getDriver(x,fprintf(OUTFILE, "%s::%s(%d) getting driver %d\n", __FILE__, __FUNCTION__, __LINE__,x)); TRACE("got driver %d\n",x);
+jack_driver_t *
+_getDriver(int deviceID, int ignored)
+{
+ fflush(OUTFILE);
+#else
+jack_driver_t *
+getDriver(int deviceID)
+{
+#endif
+ jack_driver_t *drv = &outDev[deviceID];
+
+ if(pthread_mutex_lock(&drv->mutex) != 0)
+ ERR("lock returned an error\n");
+
+ /* should we try to restart the jack server? */
+ if(drv->jackd_died && drv->client == 0)
+ {
+ struct timeval now;
+ gettimeofday(&now, 0);
+
+ /* wait 250ms before trying again */
+ if(TimeValDifference(&drv->last_reconnect_attempt, &now) >= 250)
+ {
+ JACK_OpenDevice(drv);
+ drv->last_reconnect_attempt = now;
+ }
+ }
+
+ return drv;
+}
+
+#if TRACE_getReleaseDevice
+#define tryGetDriver(x) _tryGetDriver(x,fprintf(OUTFILE, "%s::%s(%d) trying to get driver %d\n", __FILE__, __FUNCTION__, __LINE__,x)); TRACE("got driver %d\n",x);
+jack_driver_t *
+_tryGetDriver(int deviceID, int ignored)
+{
+ fflush(OUTFILE);
+#else
+jack_driver_t *
+tryGetDriver(int deviceID)
+{
+#endif
+ jack_driver_t *drv = &outDev[deviceID];
+
+ int err;
+ if((err = pthread_mutex_trylock(&drv->mutex)) == 0)
+ return drv;
+
+ if(err == EBUSY)
+ {
+ TRACE("driver %d is busy\n",deviceID);
+ return 0;
+ }
+
+ ERR("lock returned an error\n");
+ return 0;
+}
+
+
+/* release a device's mutex */
+/* */
+/* This macro is similar to the one for getDriver above, only simpler since we only
+ really need to know when the lock was release for the sake of debugging.
+*/
+#if TRACE_getReleaseDevice
+#define releaseDriver(x) TRACE("releasing driver %d\n",x->deviceID); _releaseDriver(x);
+void
+_releaseDriver(jack_driver_t * drv)
+#else
+void
+releaseDriver(jack_driver_t * drv)
+#endif
+{
+ /*
+ #if TRACE_getReleaseDevice
+ TRACE("deviceID == %d\n", drv->deviceID);
+ #endif
+ */
+ if(pthread_mutex_unlock(&drv->mutex) != 0)
+ ERR("lock returned an error\n");
+}
+
+
+/* Return a string corresponding to the input state */
+char *
+DEBUGSTATE(enum status_enum state)
+{
+ if(state == PLAYING)
+ return "PLAYING";
+ else if(state == PAUSED)
+ return "PAUSED";
+ else if(state == STOPPED)
+ return "STOPPED";
+ else if(state == CLOSED)
+ return "CLOSED";
+ else if(state == RESET)
+ return "RESET";
+ else
+ return "unknown state";
+}
+
+
+#define SAMPLE_MAX_16BIT 32767.0f
+#define SAMPLE_MAX_8BIT 255.0f
+
+/* floating point volume routine */
+/* volume should be a value between 0.0 and 1.0 */
+static void
+float_volume_effect(sample_t * buf, unsigned long nsamples, float volume,
+ int skip)
+{
+ if(volume < 0)
+ volume = 0;
+ if(volume > 1.0)
+ volume = 1.0;
+
+ while(nsamples--)
+ {
+ *buf = (*buf) * volume;
+ buf += skip;
+ }
+}
+
+/* place one channel into a multi-channel stream */
+static inline void
+mux(sample_t * dst, sample_t * src, unsigned long nsamples,
+ unsigned long dst_skip)
+{
+ /* ALERT: signed sign-extension portability !!! */
+ while(nsamples--)
+ {
+ *dst = *src;
+ dst += dst_skip;
+ src++;
+ }
+}
+
+/* pull one channel out of a multi-channel stream */
+static void
+demux(sample_t * dst, sample_t * src, unsigned long nsamples,
+ unsigned long src_skip)
+{
+ /* ALERT: signed sign-extension portability !!! */
+ while(nsamples--)
+ {
+ *dst = *src;
+ dst++;
+ src += src_skip;
+ }
+}
+
+/* convert from 16 bit to floating point */
+static inline void
+sample_move_short_float(sample_t * dst, short *src, unsigned long nsamples)
+{
+ /* ALERT: signed sign-extension portability !!! */
+ unsigned long i;
+ for(i = 0; i < nsamples; i++)
+ dst[i] = (sample_t) (src[i]) / SAMPLE_MAX_16BIT;
+}
+
+/* convert from floating point to 16 bit */
+static inline void
+sample_move_float_short(short *dst, sample_t * src, unsigned long nsamples)
+{
+ /* ALERT: signed sign-extension portability !!! */
+ unsigned long i;
+ for(i = 0; i < nsamples; i++)
+ dst[i] = (short) ((src[i]) * SAMPLE_MAX_16BIT);
+}
+
+/* convert from 8 bit to floating point */
+static inline void
+sample_move_char_float(sample_t * dst, unsigned char *src, unsigned long nsamples)
+{
+ /* ALERT: signed sign-extension portability !!! */
+ unsigned long i;
+ for(i = 0; i < nsamples; i++)
+ dst[i] = (sample_t) (src[i]) / SAMPLE_MAX_8BIT;
+}
+
+/* convert from floating point to 8 bit */
+static inline void
+sample_move_float_char(unsigned char *dst, sample_t * src, unsigned long nsamples)
+{
+ /* ALERT: signed sign-extension portability !!! */
+ unsigned long i;
+ for(i = 0; i < nsamples; i++)
+ dst[i] = (char) ((src[i]) * SAMPLE_MAX_8BIT);
+}
+
+/* fill dst buffer with nsamples worth of silence */
+static inline void
+sample_silence_float(sample_t * dst, unsigned long nsamples)
+{
+ /* ALERT: signed sign-extension portability !!! */
+ while(nsamples--)
+ {
+ *dst = 0;
+ dst++;
+ }
+}
+
+static inline bool
+ensure_buffer_size(char **buffer, unsigned long *cur_size,
+ unsigned long needed_size)
+{
+ DEBUG("current size = %lu, needed size = %lu\n", *cur_size, needed_size);
+ if(*cur_size >= needed_size)
+ return TRUE;
+ DEBUG("reallocing\n");
+ char *tmp = realloc(*buffer, needed_size);
+ if(tmp)
+ {
+ *cur_size = needed_size;
+ *buffer = tmp;
+ return TRUE;
+ }
+ DEBUG("reallocing failed\n");
+ return FALSE;
+}
+
+/******************************************************************
+ * JACK_callback
+ *
+ * every time the jack server wants something from us it calls this
+ * function, so we either deliver it some sound to play or deliver it nothing
+ * to play
+ */
+static int
+JACK_callback(nframes_t nframes, void *arg)
+{
+ jack_driver_t *drv = (jack_driver_t *) arg;
+
+ unsigned int i;
+ int src_error = 0;
+
+ TIMER("start\n");
+ gettimeofday(&drv->previousTime, 0); /* record the current time */
+
+ CALLBACK_TRACE("nframes %ld, sizeof(sample_t) == %d\n", (long) nframes,
+ sizeof(sample_t));
+
+ if(!drv->client)
+ ERR("client is closed, this is weird...\n");
+
+ sample_t *out_buffer[MAX_OUTPUT_PORTS];
+ /* retrieve the buffers for the output ports */
+ for(i = 0; i < drv->num_output_channels; i++)
+ out_buffer[i] = (sample_t *) jack_port_get_buffer(drv->output_port[i], nframes);
+
+ sample_t *in_buffer[MAX_INPUT_PORTS];
+ /* retrieve the buffers for the input ports */
+ for(i = 0; i < drv->num_input_channels; i++)
+ in_buffer[i] = (sample_t *) jack_port_get_buffer(drv->input_port[i], nframes);
+
+ /* handle playing state */
+ if(drv->state == PLAYING)
+ {
+ /* handle playback data, if any */
+ if(drv->num_output_channels > 0)
+ {
+ unsigned long jackFramesAvailable = nframes; /* frames we have left to write to jack */
+ unsigned long numFramesToWrite; /* num frames we are writing */
+ size_t inputBytesAvailable = jack_ringbuffer_read_space(drv->pPlayPtr);
+ unsigned long inputFramesAvailable; /* frames we have available */
+
+ inputFramesAvailable = inputBytesAvailable / drv->bytes_per_jack_output_frame;
+ size_t jackBytesAvailable = jackFramesAvailable * drv->bytes_per_jack_output_frame;
+
+ long read = 0;
+
+ CALLBACK_TRACE("playing... jackFramesAvailable = %ld inputFramesAvailable = %ld\n",
+ jackFramesAvailable, inputFramesAvailable);
+
+#if JACK_CLOSE_HACK
+ if(drv->in_use == FALSE)
+ {
+ /* output silence if nothing is being outputted */
+ for(i = 0; i < drv->num_output_channels; i++)
+ sample_silence_float(out_buffer[i], nframes);
+
+ return -1;
+ }
+#endif
+
+ /* make sure our buffer is large enough for the data we are writing */
+ /* ie. callback_buffer2_size < (bytes we already wrote + bytes we are going to write in this loop) */
+ if(!ensure_buffer_size
+ (&drv->callback_buffer2, &drv->callback_buffer2_size,
+ jackBytesAvailable))
+ {
+ ERR("allocated %lu bytes, need %lu bytes\n",
+ drv->callback_buffer2_size, (unsigned long)jackBytesAvailable);
+ return -1;
+ }
+
+ /* do sample rate conversion if needed & requested */
+ if(drv->output_src && drv->output_sample_rate_ratio != 1.0)
+ {
+ long bytes_needed_write = nframes * drv->bytes_per_jack_output_frame;
+
+ /* make a very good guess at how many raw bytes we'll need to satisfy jack's request after conversion */
+ long bytes_needed_read = min(inputBytesAvailable,
+ (double) (bytes_needed_write +
+ drv->
+ output_sample_rate_ratio
+ *
+ drv->
+ bytes_per_jack_output_frame)
+ / drv->output_sample_rate_ratio);
+ DEBUG("guessing that we need %ld bytes in and %ld out for rate conversion ratio = %f\n",
+ bytes_needed_read, bytes_needed_write,
+ drv->output_sample_rate_ratio);
+
+ if(!ensure_buffer_size(&drv->callback_buffer1,
+ &drv->callback_buffer1_size,
+ bytes_needed_read))
+ {
+ ERR("could not realloc callback_buffer2!\n");
+ return 1;
+ }
+ if(!ensure_buffer_size(&drv->callback_buffer2,
+ &drv->callback_buffer2_size,
+ bytes_needed_write))
+ {
+ ERR("could not realloc callback_buffer2!\n");
+ return 1;
+ }
+
+ if(jackFramesAvailable && inputBytesAvailable > 0)
+ {
+ /* read in the data, but don't move the read pointer until we know how much SRC used */
+ jack_ringbuffer_peek(drv->pPlayPtr, drv->callback_buffer1,
+ bytes_needed_read);
+
+ SRC_DATA srcdata;
+ srcdata.data_in = (sample_t *) drv->callback_buffer1;
+ srcdata.input_frames = bytes_needed_read / drv->bytes_per_jack_output_frame;
+ srcdata.src_ratio = drv->output_sample_rate_ratio;
+ srcdata.data_out = (sample_t *) drv->callback_buffer2;
+ srcdata.output_frames = nframes;
+ srcdata.end_of_input = 0; // it's a stream, it never ends
+ DEBUG("input_frames = %ld, output_frames = %ld\n",
+ srcdata.input_frames, srcdata.output_frames);
+ /* convert the sample rate */
+ src_error = src_process(drv->output_src, &srcdata);
+ DEBUG("used = %ld, generated = %ld, error = %d: %s.\n",
+ srcdata.input_frames_used, srcdata.output_frames_gen,
+ src_error, src_strerror(src_error));
+
+ if(src_error == 0)
+ {
+ /* now we can move the read pointer */
+ jack_ringbuffer_read_advance(drv->pPlayPtr,
+ srcdata.
+ input_frames_used *
+ drv->bytes_per_jack_output_frame);
+ /* add on what we wrote */
+ read = srcdata.input_frames_used * drv->bytes_per_output_frame;
+ jackFramesAvailable -= srcdata.output_frames_gen; /* take away what was used */
+ }
+ }
+ }
+ else /* no resampling needed or requested */
+ {
+ /* read as much data from the buffer as is available */
+ if(jackFramesAvailable && inputBytesAvailable > 0)
+ {
+ /* write as many bytes as we have space remaining, or as much as we have data to write */
+ numFramesToWrite = min(jackFramesAvailable, inputFramesAvailable);
+ jack_ringbuffer_read(drv->pPlayPtr, drv->callback_buffer2,
+ jackBytesAvailable);
+ /* add on what we wrote */
+ read = numFramesToWrite * drv->bytes_per_output_frame;
+ jackFramesAvailable -= numFramesToWrite; /* take away what was written */
+ }
+ }
+
+ drv->written_client_bytes += read;
+ drv->played_client_bytes += drv->clientBytesInJack; /* move forward by the previous bytes we wrote since those must have finished by now */
+ drv->clientBytesInJack = read; /* record the input bytes we wrote to jack */
+
+ /* see if we still have jackBytesLeft here, if we do that means that we
+ ran out of wave data to play and had a buffer underrun, fill in
+ the rest of the space with zero bytes so at least there is silence */
+ if(jackFramesAvailable)
+ {
+ WARN("buffer underrun of %ld frames\n", jackFramesAvailable);
+ for(i = 0; i < drv->num_output_channels; i++)
+ sample_silence_float(out_buffer[i] +
+ (nframes - jackFramesAvailable),
+ jackFramesAvailable);
+ }
+
+ /* if we aren't converting or we are converting and src_error == 0 then we should */
+ /* apply volume and demux */
+ if(!(drv->output_src && drv->output_sample_rate_ratio != 1.0) || (src_error == 0))
+ {
+ /* apply volume */
+ for(i = 0; i < drv->num_output_channels; i++)
+ {
+ if(drv->volumeEffectType == dbAttenuation)
+ {
+ /* assume the volume setting is dB of attenuation, a volume of 0 */
+ /* is 0dB attenuation */
+ float volume = powf(10.0, -((float) drv->volume[i]) / 20.0);
+ float_volume_effect((sample_t *) drv->callback_buffer2 + i,
+ (nframes - jackFramesAvailable), volume, drv->num_output_channels);
+ } else
+ {
+ float_volume_effect((sample_t *) drv->callback_buffer2 + i, (nframes - jackFramesAvailable),
+ ((float) drv->volume[i] / 100.0),
+ drv->num_output_channels);
+ }
+ }
+
+ /* demux the stream: we skip over the number of samples we have output channels as the channel data */
+ /* is encoded like chan1,chan2,chan3,chan1,chan2,chan3... */
+ for(i = 0; i < drv->num_output_channels; i++)
+ {
+ demux(out_buffer[i],
+ (sample_t *) drv->callback_buffer2 + i,
+ (nframes - jackFramesAvailable), drv->num_output_channels);
+ }
+ }
+ }
+
+ /* handle record data, if any */
+ if(drv->num_input_channels > 0)
+ {
+ long jack_bytes = nframes * drv->bytes_per_jack_input_frame; /* how many bytes jack is feeding us */
+
+ if(!ensure_buffer_size(&drv->callback_buffer1, &drv->callback_buffer1_size, jack_bytes))
+ {
+ ERR("allocated %lu bytes, need %lu bytes\n",
+ drv->callback_buffer1_size, jack_bytes);
+ return -1;
+ }
+
+ /* mux the invividual channels into one stream */
+ for(i = 0; i < drv->num_input_channels; i++)
+ {
+ mux((sample_t *) drv->callback_buffer1 + i, in_buffer[i],
+ nframes, drv->num_input_channels);
+ }
+
+ /* do sample rate conversion if needed & requested */
+ if(drv->input_src && drv->input_sample_rate_ratio != 1.0)
+ {
+ /* make a very good guess at how many raw bytes we'll need to read all the data jack gave us */
+ long bytes_needed_write = (double) (jack_bytes +
+ drv->input_sample_rate_ratio *
+ drv->bytes_per_jack_input_frame) *
+ drv->input_sample_rate_ratio;
+ DEBUG("guessing that we need %ld bytes in and %ld out for rate conversion ratio = %f\n",
+ nframes * drv->bytes_per_jack_input_frame,
+ bytes_needed_write, drv->input_sample_rate_ratio);
+
+ if(!ensure_buffer_size(&drv->callback_buffer2,
+ &drv->callback_buffer2_size,
+ bytes_needed_write))
+ {
+ ERR("could not realloc callback_buffer2!\n");
+ return 1;
+ }
+
+ SRC_DATA srcdata;
+ srcdata.data_in = (sample_t *) drv->callback_buffer1;
+ srcdata.input_frames = nframes;
+ srcdata.src_ratio = drv->input_sample_rate_ratio;
+ srcdata.data_out = (sample_t *) drv->callback_buffer2;
+ srcdata.output_frames = drv->callback_buffer2_size / drv->bytes_per_jack_input_frame;
+ srcdata.end_of_input = 0; // it's a stream, it never ends
+ DEBUG("input_frames = %ld, output_frames = %ld\n",
+ srcdata.input_frames, srcdata.output_frames);
+ /* convert the sample rate */
+ src_error = src_process(drv->input_src, &srcdata);
+ DEBUG("used = %ld, generated = %ld, error = %d: %s.\n",
+ srcdata.input_frames_used, srcdata.output_frames_gen,
+ src_error, src_strerror(src_error));
+
+ if(src_error == 0)
+ {
+ long write_space = jack_ringbuffer_write_space(drv->pRecPtr);
+ long bytes_used = srcdata.output_frames_gen * drv->bytes_per_jack_input_frame;
+ /* if there isn't enough room, make some. sure this discards data, but when dealing with input sources
+ it seems like it's better to throw away old data than new */
+ if(write_space < bytes_used)
+ {
+ /* the ringbuffer is designed such that only one thread should ever access each pointer.
+ since calling read_advance here will be touching the read pointer which is also accessed
+ by JACK_Read, we need to lock the mutex first for safety */
+ jack_driver_t *d = tryGetDriver(drv->deviceID);
+ if( d )
+ {
+ /* double check the write space after we've gained the lock, just
+ in case JACK_Read was being called before we gained it */
+ write_space = jack_ringbuffer_write_space(drv->pRecPtr);
+ if(write_space < bytes_used)
+ {
+ /* hey, we warn about underruns, we might as well warn about overruns as well */
+ WARN("buffer overrun of %ld bytes\n", jack_bytes - write_space);
+ jack_ringbuffer_read_advance(drv->pRecPtr, bytes_used - write_space);
+ }
+
+ releaseDriver(drv);
+ }
+ }
+
+ jack_ringbuffer_write(drv->pRecPtr, drv->callback_buffer2,
+ bytes_used);
+ }
+ }
+ else /* no resampling needed */
+ {
+ long write_space = jack_ringbuffer_write_space(drv->pRecPtr);
+ /* if there isn't enough room, make some. sure this discards data, but when dealing with input sources
+ it seems like it's better to throw away old data than new */
+ if(write_space < jack_bytes)
+ {
+ /* the ringbuffer is designed such that only one thread should ever access each pointer.
+ since calling read_advance here will be touching the read pointer which is also accessed
+ by JACK_Read, we need to lock the mutex first for safety */
+ jack_driver_t *d = tryGetDriver(drv->deviceID);
+ if( d )
+ {
+ /* double check the write space after we've gained the lock, just
+ in case JACK_Read was being called before we gained it */
+ write_space = jack_ringbuffer_write_space(drv->pRecPtr);
+ if(write_space < jack_bytes)
+ {
+ ERR("buffer overrun of %ld bytes\n", jack_bytes - write_space);
+ jack_ringbuffer_read_advance(drv->pRecPtr, jack_bytes - write_space);
+ }
+ releaseDriver(drv);
+ }
+ }
+
+ jack_ringbuffer_write(drv->pRecPtr, drv->callback_buffer1,
+ jack_bytes);
+ }
+ }
+ }
+ else if(drv->state == PAUSED ||
+ drv->state == STOPPED ||
+ drv->state == CLOSED || drv->state == RESET)
+ {
+ CALLBACK_TRACE("%s, outputting silence\n", DEBUGSTATE(drv->state));
+
+ /* output silence if nothing is being outputted */
+ for(i = 0; i < drv->num_output_channels; i++)
+ sample_silence_float(out_buffer[i], nframes);
+
+ /* if we were told to reset then zero out some variables */
+ /* and transition to STOPPED */
+ if(drv->state == RESET)
+ {
+ drv->written_client_bytes = 0;
+ drv->played_client_bytes = 0; /* number of the clients bytes that jack has played */
+
+ drv->client_bytes = 0; /* bytes that the client wrote to use */
+
+ drv->clientBytesInJack = 0; /* number of input bytes in jack(not necessary the number of bytes written to jack) */
+
+ drv->position_byte_offset = 0;
+
+ if(drv->pPlayPtr)
+ jack_ringbuffer_reset(drv->pPlayPtr);
+
+ if(drv->pRecPtr)
+ jack_ringbuffer_reset(drv->pRecPtr);
+
+ drv->state = STOPPED; /* transition to STOPPED */
+ }
+ }
+
+ CALLBACK_TRACE("done\n");
+ TIMER("finish\n");
+
+ return 0;
+}
+
+
+/******************************************************************
+ * JACK_bufsize
+ *
+ * Called whenever the jack server changes the the max number
+ * of frames passed to JACK_callback
+ */
+static int
+JACK_bufsize(nframes_t nframes, void *arg)
+{
+ jack_driver_t *drv = (jack_driver_t *) arg;
+ TRACE("the maximum buffer size is now %lu frames\n", (long) nframes);
+
+ drv->jack_buffer_size = nframes;
+
+ return 0;
+}
+
+/******************************************************************
+ * JACK_srate
+ */
+int
+JACK_srate(nframes_t nframes, void *arg)
+{
+ jack_driver_t *drv = (jack_driver_t *) arg;
+
+ drv->jack_sample_rate = (long) nframes;
+
+ /* make sure to recalculate the ratios needed for proper sample rate conversion */
+ drv->output_sample_rate_ratio = (double) drv->jack_sample_rate / (double) drv->client_sample_rate;
+ if(drv->output_src) src_set_ratio(drv->output_src, drv->output_sample_rate_ratio);
+
+ drv->input_sample_rate_ratio = (double) drv->client_sample_rate / (double) drv->jack_sample_rate;
+ if(drv->input_src) src_set_ratio(drv->input_src, drv->input_sample_rate_ratio);
+
+ TRACE("the sample rate is now %lu/sec\n", (long) nframes);
+ return 0;
+}
+
+
+/******************************************************************
+ * JACK_shutdown
+ *
+ * if this is called then jack shut down... handle this appropriately */
+void
+JACK_shutdown(void *arg)
+{
+ jack_driver_t *drv = (jack_driver_t *) arg;
+
+ TRACE("\n");
+
+ getDriver(drv->deviceID);
+
+ drv->client = 0; /* reset client */
+ drv->jackd_died = TRUE;
+
+ TRACE("jack shutdown, setting client to 0 and jackd_died to true, closing device\n");
+
+#if JACK_CLOSE_HACK
+ JACK_CloseDevice(drv, TRUE);
+#else
+ JACK_CloseDevice(drv);
+#endif
+
+ TRACE("trying to reconnect right now\n");
+ /* lets see if we can't reestablish the connection */
+ if(JACK_OpenDevice(drv) != ERR_SUCCESS)
+ {
+ ERR("unable to reconnect with jack\n");
+ }
+
+ releaseDriver(drv);
+}
+
+
+/******************************************************************
+ * JACK_Error
+ *
+ * Callback for jack errors
+ */
+static void
+JACK_Error(const char *desc)
+{
+ ERR("%s\n", desc);
+}
+
+
+/******************************************************************
+ * JACK_OpenDevice
+ *
+ * RETURNS: ERR_SUCCESS upon success
+ */
+static int
+JACK_OpenDevice(jack_driver_t * drv)
+{
+ const char **ports;
+ char *our_client_name = 0;
+ unsigned int i;
+ int failed = 0;
+
+ TRACE("creating jack client and setting up callbacks\n");
+
+#if JACK_CLOSE_HACK
+ /* see if this device is already open */
+ if(drv->client)
+ {
+ /* if this device is already in use then it is bad for us to be in here */
+ if(drv->in_use)
+ return ERR_OPENING_JACK;
+
+ TRACE("using existing client\n");
+ drv->in_use = TRUE;
+ return ERR_SUCCESS;
+ }
+#endif
+
+ /* set up an error handler */
+ jack_set_error_function(JACK_Error);
+
+
+ /* build the client name */
+ our_client_name = (char *) malloc(snprintf
+ (our_client_name, 0, "%s_%d_%d%02d", client_name, getpid(),
+ drv->deviceID, drv->clientCtr + 1) + 1);
+ sprintf(our_client_name, "%s_%d_%d%02d", client_name, getpid(),
+ drv->deviceID, drv->clientCtr++);
+
+ /* try to become a client of the JACK server */
+ TRACE("client name '%s'\n", our_client_name);
+ if((drv->client = jack_client_new(our_client_name)) == 0)
+ {
+ /* try once more */
+ TRACE("trying once more to jack_client_new");
+ if((drv->client = jack_client_new(our_client_name)) == 0)
+ {
+ ERR("jack server not running?\n");
+ free(our_client_name);
+ return ERR_OPENING_JACK;
+ }
+ }
+
+ free(our_client_name);
+
+ TRACE("setting up jack callbacks\n");
+
+ /* JACK server to call `JACK_callback()' whenever
+ there is work to be done. */
+ jack_set_process_callback(drv->client, JACK_callback, drv);
+
+ /* setup a buffer size callback */
+ jack_set_buffer_size_callback(drv->client, JACK_bufsize, drv);
+
+ /* tell the JACK server to call `srate()' whenever
+ the sample rate of the system changes. */
+ jack_set_sample_rate_callback(drv->client, JACK_srate, drv);
+
+ /* tell the JACK server to call `jack_shutdown()' if
+ it ever shuts down, either entirely, or if it
+ just decides to stop calling us. */
+ jack_on_shutdown(drv->client, JACK_shutdown, drv);
+
+ /* display the current sample rate. once the client is activated
+ (see below), you should rely on your own sample rate
+ callback (see above) for this value. */
+ drv->jack_sample_rate = jack_get_sample_rate(drv->client);
+ drv->output_sample_rate_ratio = (double) drv->jack_sample_rate / (double) drv->client_sample_rate;
+ drv->input_sample_rate_ratio = (double) drv->client_sample_rate / (double) drv->jack_sample_rate;
+ TRACE("client sample rate: %lu, jack sample rate: %lu, output ratio = %f, input ratio = %f\n",
+ drv->client_sample_rate, drv->jack_sample_rate,
+ drv->output_sample_rate_ratio, drv->input_sample_rate_ratio);
+
+ drv->jack_buffer_size = jack_get_buffer_size(drv->client);
+
+ /* create the output ports */
+ TRACE("creating output ports\n");
+ for(i = 0; i < drv->num_output_channels; i++)
+ {
+ char portname[32];
+ sprintf(portname, "out_%d", i);
+ TRACE("port %d is named '%s'\n", i, portname);
+ /* NOTE: Yes, this is supposed to be JackPortIsOutput since this is an output */
+ /* port FROM bio2jack */
+ drv->output_port[i] = jack_port_register(drv->client, portname,
+ JACK_DEFAULT_AUDIO_TYPE,
+ JackPortIsOutput, 0);
+ }
+
+ /* create the input ports */
+ TRACE("creating input ports\n");
+ for(i = 0; i < drv->num_input_channels; i++)
+ {
+ char portname[32];
+ sprintf(portname, "in_%d", i);
+ TRACE("port %d is named '%s'\n", i, portname);
+ /* NOTE: Yes, this is supposed to be JackPortIsInput since this is an input */
+ /* port TO bio2jack */
+ drv->input_port[i] = jack_port_register(drv->client, portname,
+ JACK_DEFAULT_AUDIO_TYPE,
+ JackPortIsInput, 0);
+ }
+
+#if JACK_CLOSE_HACK
+ drv->in_use = TRUE;
+#endif
+
+ /* tell the JACK server that we are ready to roll */
+ TRACE("calling jack_activate()\n");
+ if(jack_activate(drv->client))
+ {
+ ERR("cannot activate client\n");
+ return ERR_OPENING_JACK;
+ }
+
+ /* if we have output channels and the port connection mode isn't CONNECT_NONE */
+ /* then we should connect up some ports */
+ if((drv->num_output_channels > 0) && (port_connection_mode != CONNECT_NONE))
+ {
+ /* determine how we are to acquire output port names */
+ if((drv->jack_port_name_count == 0) || (drv->jack_port_name_count == 1))
+ {
+ if(drv->jack_port_name_count == 0)
+ {
+ TRACE("jack_get_ports() passing in NULL/NULL\n");
+ ports = jack_get_ports(drv->client, NULL, NULL,
+ drv->jack_output_port_flags);
+ }
+ else
+ {
+ TRACE("jack_get_ports() passing in port of '%s'\n",
+ drv->jack_port_name[0]);
+ ports = jack_get_ports(drv->client, drv->jack_port_name[0], NULL,
+ drv->jack_output_port_flags);
+ }
+
+ /* display a trace of the output ports we found */
+ unsigned int num_ports = 0;
+ if(ports)
+ {
+ for(i = 0; ports[i]; i++)
+ {
+ TRACE("ports[%d] = '%s'\n", i, ports[i]);
+ num_ports++;
+ }
+ }
+
+ /* ensure that we found enough ports */
+ if(!ports || (i < drv->num_output_channels))
+ {
+ TRACE("ERR: jack_get_ports() failed to find ports with jack port flags of 0x%lX'\n",
+ drv->jack_output_port_flags);
+#if JACK_CLOSE_HACK
+ JACK_CloseDevice(drv, TRUE);
+#else
+ JACK_CloseDevice(drv);
+#endif
+ return ERR_PORT_NOT_FOUND;
+ }
+
+ /* connect a port for each output channel. Note: you can't do this before
+ the client is activated (this may change in the future). */
+ for(i = 0; i < drv->num_output_channels; i++)
+ {
+ TRACE("jack_connect() to port %d('%p')\n", i, drv->output_port[i]);
+ if(jack_connect(drv->client, jack_port_name(drv->output_port[i]), ports[i]))
+ {
+ ERR("cannot connect to output port %d('%s')\n", i, ports[i]);
+ failed = 1;
+ }
+ }
+
+ /* only if we are in CONNECT_ALL mode should we keep connecting ports up beyond */
+ /* the minimum number of ports required for each output channel coming into bio2jack */
+ if(port_connection_mode == CONNECT_ALL)
+ {
+ /* It's much cheaper and easier to let JACK do the processing required to
+ connect 2 channels to 4 or 4 channels to 2 or any other combinations.
+ This effectively eliminates the need for sample_move_d16_d16() */
+ if(drv->num_output_channels < num_ports)
+ {
+ for(i = drv->num_output_channels; ports[i]; i++)
+ {
+ int n = i % drv->num_output_channels;
+ TRACE("jack_connect() to port %d('%p')\n", i, drv->output_port[n]);
+ if(jack_connect(drv->client, jack_port_name(drv->output_port[n]), ports[i]))
+ {
+ // non fatal
+ ERR("cannot connect to output port %d('%s')\n", n, ports[i]);
+ }
+ }
+ }
+ else if(drv->num_output_channels > num_ports)
+ {
+ for(i = num_ports; i < drv->num_output_channels; i++)
+ {
+ int n = i % num_ports;
+ TRACE("jack_connect() to port %d('%p')\n", i, drv->output_port[n]);
+ if(jack_connect(drv->client, jack_port_name(drv->output_port[i]), ports[n]))
+ {
+ // non fatal
+ ERR("cannot connect to output port %d('%s')\n", i, ports[n]);
+ }
+ }
+ }
+ }
+
+ free(ports); /* free the returned array of ports */
+ }
+ else
+ {
+ for(i = 0; i < drv->jack_port_name_count; i++)
+ {
+ TRACE("jack_get_ports() portname %d of '%s\n", i,
+ drv->jack_port_name[i]);
+ ports = jack_get_ports(drv->client, drv->jack_port_name[i], NULL,
+ drv->jack_output_port_flags);
+
+ if(!ports)
+ {
+ ERR("jack_get_ports() failed to find ports with jack port flags of 0x%lX'\n",
+ drv->jack_output_port_flags);
+ return ERR_PORT_NOT_FOUND;
+ }
+
+ TRACE("ports[%d] = '%s'\n", 0, ports[0]); /* display a trace of the output port we found */
+
+ /* connect the port */
+ TRACE("jack_connect() to port %d('%p')\n", i, drv->output_port[i]);
+ if(jack_connect(drv->client, jack_port_name(drv->output_port[i]), ports[0]))
+ {
+ ERR("cannot connect to output port %d('%s')\n", 0, ports[0]);
+ failed = 1;
+ }
+ free(ports); /* free the returned array of ports */
+ }
+ }
+ } /* if( drv->num_output_channels > 0 ) */
+
+
+ if(drv->num_input_channels > 0)
+ {
+ /* determine how we are to acquire input port names */
+ if((drv->jack_port_name_count == 0) || (drv->jack_port_name_count == 1))
+ {
+ if(drv->jack_port_name_count == 0)
+ {
+ TRACE("jack_get_ports() passing in NULL/NULL\n");
+ ports = jack_get_ports(drv->client, NULL, NULL, drv->jack_input_port_flags);
+ }
+ else
+ {
+ TRACE("jack_get_ports() passing in port of '%s'\n",
+ drv->jack_port_name[0]);
+ ports = jack_get_ports(drv->client, drv->jack_port_name[0], NULL,
+ drv->jack_input_port_flags);
+ }
+
+ /* display a trace of the input ports we found */
+ unsigned int num_ports = 0;
+ if(ports)
+ {
+ for(i = 0; ports[i]; i++)
+ {
+ TRACE("ports[%d] = '%s'\n", i, ports[i]);
+ num_ports++;
+ }
+ }
+
+ /* ensure that we found enough ports */
+ if(!ports || (i < drv->num_input_channels))
+ {
+ TRACE("ERR: jack_get_ports() failed to find ports with jack port flags of 0x%lX'\n",
+ drv->jack_input_port_flags);
+#if JACK_CLOSE_HACK
+ JACK_CloseDevice(drv, TRUE);
+#else
+ JACK_CloseDevice(drv);
+#endif
+ return ERR_PORT_NOT_FOUND;
+ }
+
+ /* connect the ports. Note: you can't do this before
+ the client is activated (this may change in the future). */
+ for(i = 0; i < drv->num_input_channels; i++)
+ {
+ TRACE("jack_connect() to port %d('%p')\n", i, drv->input_port[i]);
+ if(jack_connect(drv->client, ports[i], jack_port_name(drv->input_port[i])))
+ {
+ ERR("cannot connect to input port %d('%s')\n", i, ports[i]);
+ failed = 1;
+ }
+ }
+
+ /* It's much cheaper and easier to let JACK do the processing required to
+ connect 2 channels to 4 or 4 channels to 2 or any other combinations.
+ This effectively eliminates the need for sample_move_d16_d16() */
+ if(drv->num_input_channels < num_ports)
+ {
+ for(i = drv->num_input_channels; ports[i]; i++)
+ {
+ int n = i % drv->num_input_channels;
+ TRACE("jack_connect() to port %d('%p')\n", i, drv->input_port[n]);
+ if(jack_connect(drv->client, ports[i], jack_port_name(drv->input_port[n])))
+ {
+ // non fatal
+ ERR("cannot connect to input port %d('%s')\n", n, ports[i]);
+ }
+ }
+ }
+ else if(drv->num_input_channels > num_ports)
+ {
+ for(i = num_ports; i < drv->num_input_channels; i++)
+ {
+ int n = i % num_ports;
+ TRACE("jack_connect() to port %d('%p')\n", i, drv->input_port[n]);
+ if(jack_connect(drv->client, ports[n], jack_port_name(drv->input_port[i])))
+ {
+ // non fatal
+ ERR("cannot connect to input port %d('%s')\n", i, ports[n]);
+ }
+ }
+ }
+
+ free(ports); /* free the returned array of ports */
+ }
+ else
+ {
+ for(i = 0; i < drv->jack_port_name_count; i++)
+ {
+ TRACE("jack_get_ports() portname %d of '%s\n", i,
+ drv->jack_port_name[i]);
+ ports = jack_get_ports(drv->client, drv->jack_port_name[i], NULL,
+ drv->jack_input_port_flags);
+
+ if(!ports)
+ {
+ ERR("jack_get_ports() failed to find ports with jack port flags of 0x%lX'\n",
+ drv->jack_input_port_flags);
+ return ERR_PORT_NOT_FOUND;
+ }
+
+ TRACE("ports[%d] = '%s'\n", 0, ports[0]); /* display a trace of the input port we found */
+
+ /* connect the port */
+ TRACE("jack_connect() to port %d('%p')\n", i, drv->input_port[i]);
+ if(jack_connect(drv->client, jack_port_name(drv->input_port[i]), ports[0]))
+ {
+ ERR("cannot connect to input port %d('%s')\n", 0, ports[0]);
+ failed = 1;
+ }
+ free(ports); /* free the returned array of ports */
+ }
+ }
+ } /* if( drv->num_input_channels > 0 ) */
+
+ /* if something failed we need to shut the client down and return 0 */
+ if(failed)
+ {
+ TRACE("failed, closing and returning error\n");
+#if JACK_CLOSE_HACK
+ JACK_CloseDevice(drv, TRUE);
+#else
+ JACK_CloseDevice(drv);
+#endif
+ return ERR_OPENING_JACK;
+ }
+
+ TRACE("success\n");
+
+ drv->jackd_died = FALSE; /* clear out this flag so we don't keep attempting to restart things */
+ drv->state = PLAYING; /* clients seem to behave much better with this on from the start, especially when recording */
+
+ return ERR_SUCCESS; /* return success */
+}
+
+
+/******************************************************************
+ * JACK_CloseDevice
+ *
+ * Close the connection to the server cleanly.
+ * If close_client is TRUE we close the client for this device instead of
+ * just marking the device as in_use(JACK_CLOSE_HACK only)
+ */
+#if JACK_CLOSE_HACK
+static void
+JACK_CloseDevice(jack_driver_t * drv, bool close_client)
+#else
+static void
+JACK_CloseDevice(jack_driver_t * drv)
+#endif
+{
+ unsigned int i;
+
+#if JACK_CLOSE_HACK
+ if(close_client)
+ {
+#endif
+
+ TRACE("closing the jack client thread\n");
+ if(drv->client)
+ {
+ TRACE("after jack_deactivate()\n");
+ int errorCode = jack_client_close(drv->client);
+ if(errorCode)
+ ERR("jack_client_close() failed returning an error code of %d\n",
+ errorCode);
+ }
+
+ /* reset client */
+ drv->client = 0;
+
+ /* free up the port strings */
+ TRACE("freeing up %d port strings\n", drv->jack_port_name_count);
+ if(drv->jack_port_name_count > 1)
+ {
+ for(i = 0; i < drv->jack_port_name_count; i++)
+ free(drv->jack_port_name[i]);
+ free(drv->jack_port_name);
+ }
+ JACK_CleanupDriver(drv);
+
+ JACK_ResetFromDriver(drv);
+
+#if JACK_CLOSE_HACK
+ } else
+ {
+ TRACE("setting in_use to FALSE\n");
+ drv->in_use = FALSE;
+
+ if(!drv->client)
+ {
+ TRACE("critical error, closing a device that has no client\n");
+ }
+ }
+#endif
+}
+
+
+
+
+/**************************************/
+/* External interface functions below */
+/**************************************/
+
+/* Clear out any buffered data, stop playing, zero out some variables */
+static void
+JACK_ResetFromDriver(jack_driver_t * drv)
+{
+ TRACE("resetting drv->deviceID(%d)\n", drv->deviceID);
+
+ /* NOTE: we use the RESET state so we don't need to worry about clearing out */
+ /* variables that the callback modifies while the callback is running */
+ /* we set the state to RESET and the callback clears the variables out for us */
+ drv->state = RESET; /* tell the callback that we are to reset, the callback will transition this to STOPPED */
+}
+
+/* Clear out any buffered data, stop playing, zero out some variables */
+void
+JACK_Reset(int deviceID)
+{
+ jack_driver_t *drv = getDriver(deviceID);
+ TRACE("resetting deviceID(%d)\n", deviceID);
+ JACK_ResetFromDriver(drv);
+ releaseDriver(drv);
+}
+
+
+/*
+ * open the audio device for writing to
+ *
+ * deviceID is set to the opened device
+ * if client is non-zero and in_use is FALSE then just set in_use to TRUE
+ *
+ * return value is zero upon success, non-zero upon failure
+ *
+ * if ERR_RATE_MISMATCH (*rate) will be updated with the jack servers rate
+ */
+int
+JACK_Open(int *deviceID, unsigned int bits_per_channel, unsigned long *rate,
+ int channels)
+{
+ /* we call through to JACK_OpenEx(), but default the input channels to 0 for better backwards
+ compatibility with clients written before recording was available */
+ return JACK_OpenEx(deviceID, bits_per_channel,
+ rate,
+ 0, channels,
+ NULL, 0, JackPortIsPhysical);
+}
+
+/*
+ * see JACK_Open() for comments
+ * NOTE: jack_port_name has three ways of being used:
+ * - NULL - finds all ports with the given flags
+ * - A single regex string used to retrieve all port names
+ * - A series of port names, one for each output channel
+ *
+ * we set *deviceID
+ */
+int
+JACK_OpenEx(int *deviceID, unsigned int bits_per_channel,
+ unsigned long *rate,
+ unsigned int input_channels, unsigned int output_channels,
+ const char **jack_port_name,
+ unsigned int jack_port_name_count, unsigned long jack_port_flags)
+{
+ jack_driver_t *drv = 0;
+ unsigned int i;
+ int retval;
+
+ if(input_channels < 1 && output_channels < 1)
+ {
+ ERR("no input OR output channels, nothing to do\n");
+ return ERR_OPENING_JACK;
+ }
+
+ switch (bits_per_channel)
+ {
+ case 8:
+ case 16:
+ break;
+ default:
+ ERR("invalid bits_per_channel\n");
+ return ERR_OPENING_JACK;
+ }
+
+ /* Lock the device_mutex and find one that's not allocated already.
+ We'll keep this lock until we've either made use of it, or given up. */
+ pthread_mutex_lock(&device_mutex);
+
+ for(i = 0; i < MAX_OUTDEVICES; i++)
+ {
+ if(!outDev[i].allocated)
+ {
+ drv = &outDev[i];
+ break;
+ }
+ }
+
+ if(!drv)
+ {
+ ERR("no more devices available\n");
+ return ERR_OPENING_JACK;
+ }
+
+ /* We found an unallocated device, now lock it for extra saftey */
+ getDriver(drv->deviceID);
+
+ TRACE("bits_per_channel=%d rate=%ld, input_channels=%d, output_channels=%d\n",
+ bits_per_channel, *rate, input_channels, output_channels);
+
+ if(output_channels > MAX_OUTPUT_PORTS)
+ {
+ ERR("output_channels == %d, MAX_OUTPUT_PORTS == %d\n", output_channels,
+ MAX_OUTPUT_PORTS);
+ releaseDriver(drv);
+ pthread_mutex_unlock(&device_mutex);
+ return ERR_TOO_MANY_OUTPUT_CHANNELS;
+ }
+
+ if(input_channels > MAX_INPUT_PORTS)
+ {
+ ERR("input_channels == %d, MAX_INPUT_PORTS == %d\n", input_channels,
+ MAX_INPUT_PORTS);
+ releaseDriver(drv);
+ pthread_mutex_unlock(&device_mutex);
+ return ERR_TOO_MANY_INPUT_CHANNELS;
+ }
+
+ drv->jack_output_port_flags = jack_port_flags | JackPortIsInput; /* port must be input(ie we can put data into it), so mask this in */
+ drv->jack_input_port_flags = jack_port_flags | JackPortIsOutput; /* port must be output(ie we can get data from it), so mask this in */
+
+ /* check that we have the correct number of port names
+ FIXME?: not sure how we should handle output ports vs input ports....
+ */
+ if((jack_port_name_count > 1)
+ && ((jack_port_name_count < output_channels)
+ || (jack_port_name_count < input_channels)))
+ {
+ ERR("specified individual port names but not enough, gave %d names, need %d\n",
+ jack_port_name_count, output_channels);
+ releaseDriver(drv);
+ pthread_mutex_unlock(&device_mutex);
+ return ERR_PORT_NAME_OUTPUT_CHANNEL_MISMATCH;
+ } else
+ {
+ /* copy this data into the device information */
+ drv->jack_port_name_count = jack_port_name_count;
+
+ if(drv->jack_port_name_count != 0)
+ {
+ drv->jack_port_name =
+ (char **) malloc(sizeof(char *) * drv->jack_port_name_count);
+ for(i = 0; i < drv->jack_port_name_count; i++)
+ {
+ drv->jack_port_name[i] = strdup(jack_port_name[i]);
+ TRACE("jack_port_name[%d] == '%s'\n", i, jack_port_name[i]);
+ }
+ } else
+ {
+ drv->jack_port_name = NULL;
+ TRACE("jack_port_name = NULL\n");
+ }
+ }
+
+ /* initialize some variables */
+ drv->in_use = FALSE;
+
+ JACK_ResetFromDriver(drv); /* flushes all queued buffers, sets status to STOPPED and resets some variables */
+
+ /* drv->jack_sample_rate is set by JACK_OpenDevice() */
+ drv->client_sample_rate = *rate;
+ drv->bits_per_channel = bits_per_channel;
+ drv->num_input_channels = input_channels;
+ drv->num_output_channels = output_channels;
+ drv->bytes_per_input_frame = (drv->bits_per_channel * drv->num_input_channels) / 8;
+ drv->bytes_per_output_frame = (drv->bits_per_channel * drv->num_output_channels) / 8;
+ drv->bytes_per_jack_output_frame = sizeof(sample_t) * drv->num_output_channels;
+ drv->bytes_per_jack_input_frame = sizeof(sample_t) * drv->num_input_channels;
+
+ if(drv->num_output_channels > 0)
+ {
+ drv->pPlayPtr = jack_ringbuffer_create(drv->num_output_channels *
+ drv->bytes_per_jack_output_frame *
+ DEFAULT_RB_SIZE);
+ }
+
+ if(drv->num_input_channels > 0)
+ {
+ drv->pRecPtr = jack_ringbuffer_create(drv->num_input_channels *
+ drv->bytes_per_jack_input_frame *
+ DEFAULT_RB_SIZE);
+ }
+
+ DEBUG("bytes_per_output_frame == %ld\n", drv->bytes_per_output_frame);
+ DEBUG("bytes_per_input_frame == %ld\n", drv->bytes_per_input_frame);
+ DEBUG("bytes_per_jack_output_frame == %ld\n",
+ drv->bytes_per_jack_output_frame);
+ DEBUG("bytes_per_jack_input_frame == %ld\n",
+ drv->bytes_per_jack_input_frame);
+
+ /* go and open up the device */
+ retval = JACK_OpenDevice(drv);
+ if(retval != ERR_SUCCESS)
+ {
+ TRACE("error opening jack device\n");
+ releaseDriver(drv);
+ pthread_mutex_unlock(&device_mutex);
+ return retval;
+ }
+ else
+ {
+ TRACE("succeeded opening jack device\n");
+ }
+
+ /* setup SRC objects just in case they'll be needed but only if requested */
+ if(do_sample_rate_conversion)
+ {
+ int error;
+ if(drv->num_output_channels > 0)
+ {
+ drv->output_src = src_new(preferred_src_converter, drv->num_output_channels, &error);
+ if(error != 0)
+ {
+ src_delete(drv->output_src);
+ drv->output_src = 0;
+ ERR("Could not created SRC object for output stream %d: %s\n",
+ error, src_strerror(error));
+ }
+ }
+ if(drv->num_input_channels > 0)
+ {
+ drv->input_src = src_new(preferred_src_converter, drv->num_input_channels, &error);
+ if(error != 0)
+ {
+ src_delete(drv->input_src);
+ drv->input_src = 0;
+ ERR("Could not created SRC object for input stream %d: %s\n",
+ error, src_strerror(error));
+ }
+ }
+ }
+ else if((long) (*rate) != drv->jack_sample_rate)
+ {
+ TRACE("rate of %ld doesn't match jack sample rate of %ld, returning error\n",
+ *rate, drv->jack_sample_rate);
+ *rate = drv->jack_sample_rate;
+#if JACK_CLOSE_HACK
+ JACK_CloseDevice(drv, TRUE);
+#else
+ JACK_CloseDevice(drv);
+#endif
+ releaseDriver(drv);
+ pthread_mutex_unlock(&device_mutex);
+ return ERR_RATE_MISMATCH;
+ }
+
+ drv->allocated = TRUE; /* record that we opened this device */
+
+ DEBUG("sizeof(sample_t) == %d\n", sizeof(sample_t));
+
+ int periodSize = jack_get_buffer_size(drv->client);
+ int periods = 0;
+ /* FIXME: maybe we should keep different latency values for input vs output? */
+ if(drv->num_output_channels > 0)
+ {
+ periods = jack_port_get_total_latency(drv->client,
+ drv->output_port[0]) / periodSize;
+ drv->latencyMS = periodSize * periods * 1000 / (drv->jack_sample_rate *
+ (drv->bits_per_channel / 8 *
+ drv->num_output_channels));
+ }
+ else if(drv->num_input_channels > 0)
+ {
+ periods = jack_port_get_total_latency(drv->client,
+ drv->input_port[0]) / periodSize;
+ drv->latencyMS =
+ periodSize * periods * 1000 / (drv->jack_sample_rate *
+ (drv->bits_per_channel / 8 *
+ drv->num_input_channels));
+ }
+
+ TRACE("drv->latencyMS == %ldms\n", drv->latencyMS);
+
+ *deviceID = drv->deviceID; /* set the deviceID for the caller */
+ releaseDriver(drv);
+ pthread_mutex_unlock(&device_mutex);
+ return ERR_SUCCESS; /* success */
+}
+
+/* Close the jack device */
+//FIXME: add error handling in here at some point...
+/* NOTE: return 0 for success, non-zero for failure */
+int
+JACK_Close(int deviceID)
+{
+ jack_driver_t *drv = getDriver(deviceID);
+
+ TRACE("deviceID(%d)\n", deviceID);
+
+#if JACK_CLOSE_HACK
+ JACK_CloseDevice(drv, TRUE);
+#else
+ JACK_CloseDevice(drv);
+#endif
+
+ JACK_ResetFromDriver(drv); /* reset this device to a normal starting state */
+
+ pthread_mutex_lock(&device_mutex);
+
+ /* free buffer memory */
+ drv->callback_buffer1_size = 0;
+ if(drv->callback_buffer1) free(drv->callback_buffer1);
+ drv->callback_buffer1 = 0;
+
+ drv->callback_buffer2_size = 0;
+ if(drv->callback_buffer2) free(drv->callback_buffer2);
+ drv->callback_buffer2 = 0;
+
+ drv->rw_buffer1_size = 0;
+ if(drv->rw_buffer1) free(drv->rw_buffer1);
+ drv->rw_buffer1 = 0;
+
+ if(drv->pPlayPtr) jack_ringbuffer_free(drv->pPlayPtr);
+ drv->pPlayPtr = 0;
+
+ if(drv->pRecPtr) jack_ringbuffer_free(drv->pRecPtr);
+ drv->pRecPtr = 0;
+
+ /* free the SRC objects */
+ if(drv->output_src) src_delete(drv->output_src);
+ drv->output_src = 0;
+
+ if(drv->input_src) src_delete(drv->input_src);
+ drv->input_src = 0;
+
+ drv->allocated = FALSE; /* release this device */
+
+ pthread_mutex_unlock(&device_mutex);
+
+ releaseDriver(drv);
+
+ return 0;
+}
+
+/* If we haven't already taken in the max allowed data then create a wave header */
+/* to package the audio data and attach the wave header to the end of the */
+/* linked list of wave headers */
+/* These wave headers will be peeled off as they are played by the callback routine */
+/* Return value is the number of bytes written */
+/* NOTE: this function takes the length of data to be written bytes */
+long
+JACK_Write(int deviceID, unsigned char *data, unsigned long bytes)
+{
+ jack_driver_t *drv = getDriver(deviceID);
+
+ long frames_free, frames;
+
+ TIMER("start\n");
+
+ TRACE("deviceID(%d), bytes == %ld\n", deviceID, bytes);
+
+ /* check and see that we have enough space for this audio */
+ frames_free =
+ jack_ringbuffer_write_space(drv->pPlayPtr) /
+ drv->bytes_per_jack_output_frame;
+ frames = bytes / drv->bytes_per_output_frame;
+ TRACE("frames free == %ld, bytes = %lu\n", frames_free, bytes);
+
+ TRACE("state = '%s'\n", DEBUGSTATE(drv->state));
+ /* if we are currently STOPPED we should start playing now...
+ do this before the check for bytes == 0 since some clients like
+ to write 0 bytes the first time out */
+ if(drv->state == STOPPED)
+ {
+ TRACE("currently STOPPED, transitioning to PLAYING\n");
+ drv->state = PLAYING;
+ }
+
+ /* handle the case where the user calls this routine with 0 bytes */
+ if(bytes == 0 || frames_free < 1)
+ {
+ TRACE("no room left\n");
+ TIMER("finish (nothing to do, buffer is full)\n");
+ releaseDriver(drv);
+ return 0; /* indicate that we couldn't write any bytes */
+ }
+
+ frames = min(frames, frames_free);
+ long jack_bytes = frames * drv->bytes_per_jack_output_frame;
+ if(!ensure_buffer_size(&drv->rw_buffer1, &drv->rw_buffer1_size, jack_bytes))
+ {
+ ERR("couldn't allocate enough space for the buffer\n");
+ releaseDriver(drv);
+ return 0;
+ }
+ /* adjust bytes to be how many client bytes we're actually writing */
+ bytes = frames * drv->bytes_per_output_frame;
+
+ /* convert from client samples to jack samples
+ we have to tell it how many samples there are, which is frames * channels */
+ switch (drv->bits_per_channel)
+ {
+ case 8:
+ sample_move_char_float((sample_t *) drv->rw_buffer1, (unsigned char *) data,
+ frames * drv->num_output_channels);
+ break;
+ case 16:
+ sample_move_short_float((sample_t *) drv->rw_buffer1, (short *) data,
+ frames * drv->num_output_channels);
+ break;
+ }
+
+ DEBUG("ringbuffer read space = %d, write space = %d\n",
+ jack_ringbuffer_read_space(drv->pPlayPtr),
+ jack_ringbuffer_write_space(drv->pPlayPtr));
+
+ jack_ringbuffer_write(drv->pPlayPtr, drv->rw_buffer1, jack_bytes);
+ DEBUG("wrote %lu bytes, %lu jack_bytes\n", bytes, jack_bytes);
+
+ DEBUG("ringbuffer read space = %d, write space = %d\n",
+ jack_ringbuffer_read_space(drv->pPlayPtr),
+ jack_ringbuffer_write_space(drv->pPlayPtr));
+
+ drv->client_bytes += bytes; /* update client_bytes */
+
+ TIMER("finish\n");
+
+ DEBUG("returning bytes written of %ld\n", bytes);
+
+ releaseDriver(drv);
+ return bytes; /* return the number of bytes we wrote out */
+}
+
+long
+JACK_Read(int deviceID, unsigned char *data, unsigned long bytes)
+{
+ jack_driver_t *drv = getDriver(deviceID);
+
+ long frames_available, frames;
+
+ TIMER("start\n");
+
+ TRACE("deviceID(%d), bytes == %ld\n", deviceID, bytes);
+
+ /* find out if there's any room to write this data */
+ frames_available =
+ jack_ringbuffer_read_space(drv->pRecPtr) /
+ drv->bytes_per_jack_input_frame;
+ frames = bytes / drv->bytes_per_input_frame;
+ DEBUG("frames available = %ld, bytes = %lu\n", frames_available, bytes);
+
+ TRACE("state = '%s'\n", DEBUGSTATE(drv->state));
+ /* if we are currently STOPPED we should start recording now... */
+ if(drv->state == STOPPED)
+ {
+ TRACE("currently STOPPED, transitioning to PLAYING\n");
+ drv->state = PLAYING;
+ }
+
+ /* handle the case where the user calls this routine with 0 bytes */
+ if(bytes == 0 || frames_available < 1)
+ {
+ TRACE("no bytes in buffer\n");
+
+ TIMER("finish (nothing to do)\n");
+ releaseDriver(drv);
+ return 0;
+ }
+
+ frames = min(frames, frames_available);
+ long jack_bytes = frames * drv->bytes_per_jack_input_frame;
+ if(!ensure_buffer_size(&drv->rw_buffer1, &drv->rw_buffer1_size, jack_bytes))
+ {
+ ERR("couldn't allocate enough space for the buffer\n");
+ releaseDriver(drv);
+ return 0;
+ }
+
+ DEBUG("ringbuffer read space = %d, write space = %d\n",
+ jack_ringbuffer_read_space(drv->pRecPtr),
+ jack_ringbuffer_write_space(drv->pRecPtr));
+
+ jack_ringbuffer_read(drv->pRecPtr, drv->rw_buffer1,
+ frames * drv->bytes_per_jack_input_frame);
+
+ DEBUG("ringbuffer read space = %d, write space = %d\n",
+ jack_ringbuffer_read_space(drv->pRecPtr),
+ jack_ringbuffer_write_space(drv->pRecPtr));
+
+ unsigned int i;
+ for(i = 0; i < drv->num_output_channels; i++)
+ {
+ /* apply volume to the floating value */
+ if(drv->volumeEffectType == dbAttenuation)
+ {
+ /* assume the volume setting is dB of attenuation, a volume of 0 */
+ /* is 0dB attenuation */
+ float volume = powf(10.0, -((float) drv->volume[i]) / 20.0);
+ float_volume_effect((sample_t *) drv->rw_buffer1 + i,
+ frames, volume, drv->num_output_channels);
+ } else
+ {
+ float_volume_effect((sample_t *) drv->rw_buffer1 + i, frames,
+ ((float) drv->volume[i] / 100.0),
+ drv->num_output_channels);
+ }
+ }
+
+ /* convert from jack samples to client samples
+ we have to tell it how many samples there are, which is frames * channels */
+ switch (drv->bits_per_channel)
+ {
+ case 8:
+ sample_move_float_char((unsigned char *) data, (sample_t *) drv->rw_buffer1,
+ frames * drv->num_input_channels);
+ break;
+ case 16:
+ sample_move_float_short((short *) data, (sample_t *) drv->rw_buffer1,
+ frames * drv->num_input_channels);
+ break;
+ }
+
+ TIMER("finish\n");
+
+ long read_bytes = frames * drv->bytes_per_input_frame;
+
+ DEBUG("returning bytes read of %ld\n", bytes);
+
+ releaseDriver(drv);
+ return read_bytes;
+}
+
+/* return ERR_SUCCESS for success */
+static int
+JACK_SetVolumeForChannelFromDriver(jack_driver_t * drv,
+ unsigned int channel, unsigned int volume)
+{
+ /* TODO?: maybe we should have different volume levels for input & output */
+ /* ensure that we have the channel we are setting volume for */
+ if(channel > (drv->num_output_channels - 1))
+ return 1;
+
+ if(volume > 100)
+ volume = 100; /* check for values in excess of max */
+
+ drv->volume[channel] = volume;
+ return ERR_SUCCESS;
+}
+
+/* return ERR_SUCCESS for success */
+int
+JACK_SetVolumeForChannel(int deviceID, unsigned int channel,
+ unsigned int volume)
+{
+ jack_driver_t *drv = getDriver(deviceID);
+ int retval = JACK_SetVolumeForChannelFromDriver(drv, channel, volume);
+ releaseDriver(drv);
+ return retval;
+}
+
+/* Set the volume */
+/* return 0 for success */
+/* NOTE: we check for invalid volume values */
+int
+JACK_SetAllVolume(int deviceID, unsigned int volume)
+{
+ jack_driver_t *drv = getDriver(deviceID);
+ unsigned int i;
+
+ TRACE("deviceID(%d), setting volume of %d\n", deviceID, volume);
+
+ for(i = 0; i < drv->num_output_channels; i++)
+ {
+ if(JACK_SetVolumeForChannelFromDriver(drv, i, volume) != ERR_SUCCESS)
+ {
+ releaseDriver(drv);
+ return 1;
+ }
+ }
+
+ releaseDriver(drv);
+ return ERR_SUCCESS;
+}
+
+/* Return the current volume in the inputted pointers */
+/* NOTE: we check for null pointers being passed in just in case */
+void
+JACK_GetVolumeForChannel(int deviceID, unsigned int channel,
+ unsigned int *volume)
+{
+ jack_driver_t *drv = getDriver(deviceID);
+
+ /* ensure that we have the channel we are getting volume for */
+ if(channel > (drv->num_output_channels - 1))
+ {
+ ERR("asking for channel index %d but we only have %ld channels\n", channel, drv->num_output_channels);
+ releaseDriver(drv);
+ return;
+ }
+
+ if(volume)
+ *volume = drv->volume[channel];
+
+#if VERBOSE_OUTPUT
+ if(volume)
+ {
+ TRACE("deviceID(%d), returning volume of %d for channel %d\n",
+ deviceID, *volume, channel);
+ }
+ else
+ {
+ TRACE("volume is null, can't dereference it\n");
+ }
+#endif
+
+ releaseDriver(drv);
+}
+
+
+/* linear means 0 volume is silence, 100 is full volume */
+/* dbAttenuation means 0 volume is 0dB attenuation */
+/* Bio2jack defaults to linear */
+enum JACK_VOLUME_TYPE
+JACK_SetVolumeEffectType(int deviceID, enum JACK_VOLUME_TYPE type)
+{
+ enum JACK_VOLUME_TYPE retval;
+ jack_driver_t *drv = getDriver(deviceID);
+
+ TRACE("setting type of '%s'\n",
+ (type == dbAttenuation ? "dbAttenuation" : "linear"));
+
+ retval = drv->volumeEffectType;
+ drv->volumeEffectType = type;
+
+ releaseDriver(drv);
+ return retval;
+}
+
+
+/* Controls the state of the playback(playing, paused, ...) */
+int
+JACK_SetState(int deviceID, enum status_enum state)
+{
+ jack_driver_t *drv = getDriver(deviceID);
+
+ switch (state)
+ {
+ case PAUSED:
+ drv->state = PAUSED;
+ break;
+ case PLAYING:
+ drv->state = PLAYING;
+ break;
+ case STOPPED:
+ drv->state = STOPPED;
+ break;
+ default:
+ TRACE("unknown state of %d\n", state);
+ }
+
+ TRACE("%s\n", DEBUGSTATE(drv->state));
+
+ releaseDriver(drv);
+ return 0;
+}
+
+/* Retrieve the current state of the device */
+enum status_enum
+JACK_GetState(int deviceID)
+{
+ jack_driver_t *drv = getDriver(deviceID);
+ enum status_enum return_val;
+
+ return_val = drv->state;
+ releaseDriver(drv);
+
+ TRACE("deviceID(%d), returning current state of %s\n", deviceID,
+ DEBUGSTATE(return_val));
+ return return_val;
+}
+
+/* Retrieve the number of bytes per second we are outputting */
+unsigned long
+JACK_GetOutputBytesPerSecondFromDriver(jack_driver_t * drv)
+{
+ unsigned long return_val;
+
+ return_val = drv->bytes_per_output_frame * drv->client_sample_rate;
+
+#if VERBOSE_OUTPUT
+ TRACE("deviceID(%d), return_val = %ld\n", drv->deviceID, return_val);
+#endif
+
+ return return_val;
+}
+
+/* Retrieve the number of bytes per second we are outputting */
+unsigned long
+JACK_GetOutputBytesPerSecond(int deviceID)
+{
+ jack_driver_t *drv = getDriver(deviceID);
+ unsigned long return_val;
+
+ return_val = JACK_GetOutputBytesPerSecondFromDriver(drv);
+ releaseDriver(drv);
+
+ return return_val;
+}
+
+/* Retrieve the number of input bytes(from jack) per second we are outputting
+ to the user of bio2jack */
+static long
+JACK_GetInputBytesPerSecondFromDriver(jack_driver_t * drv)
+{
+ long return_val;
+
+ return_val = drv->bytes_per_input_frame * drv->client_sample_rate;
+#if VERBOSE_OUTPUT
+ TRACE("drv->deviceID(%d), return_val = %ld\n", drv->deviceID, return_val);
+#endif
+
+ return return_val;
+}
+
+/* Retrieve the number of input bytes(from jack) per second we are outputting
+ to the user of bio2jack */
+unsigned long
+JACK_GetInputBytesPerSecond(int deviceID)
+{
+ jack_driver_t *drv = getDriver(deviceID);
+ long return_val = JACK_GetInputBytesPerSecondFromDriver(drv);
+ releaseDriver(drv);
+
+#if VERBOSE_OUTPUT
+ TRACE("deviceID(%d), return_val = %ld\n", deviceID, return_val);
+#endif
+
+ return return_val;
+}
+
+/* Return the number of bytes we have buffered thus far for output */
+/* NOTE: convert from output bytes to input bytes in here */
+static long
+JACK_GetBytesStoredFromDriver(jack_driver_t * drv)
+{
+ if(drv->pPlayPtr == 0 || drv->bytes_per_jack_output_frame == 0)
+ return 0;
+
+ /* leave at least one frame in the buffer at all times to prevent underruns */
+ long return_val =
+ jack_ringbuffer_read_space(drv->pPlayPtr) - drv->jack_buffer_size;
+ if(return_val <= 0)
+ {
+ return_val = 0;
+ } else
+ {
+ /* adjust from jack bytes to client bytes */
+ return_val =
+ return_val / drv->bytes_per_jack_output_frame *
+ drv->bytes_per_output_frame;
+ }
+
+ return return_val;
+}
+
+/* An approximation of how many bytes we have to send out to jack */
+/* that is computed as if we were sending jack a continuous stream of */
+/* bytes rather than chunks during discrete callbacks. */
+/* Return the number of bytes we have buffered thus far for output */
+/* NOTE: convert from output bytes to input bytes in here */
+unsigned long
+JACK_GetBytesStored(int deviceID)
+{
+ jack_driver_t *drv = getDriver(deviceID);
+ long retval = JACK_GetBytesStoredFromDriver(drv);
+ releaseDriver(drv);
+ TRACE("deviceID(%d), retval = %ld\n", deviceID, retval);
+ return retval;
+}
+
+static unsigned long
+JACK_GetBytesFreeSpaceFromDriver(jack_driver_t * drv)
+{
+ if(drv->pPlayPtr == 0 || drv->bytes_per_jack_output_frame == 0)
+ return 0;
+
+ /* leave at least one frame in the buffer at all times to prevent underruns */
+ long return_val = jack_ringbuffer_write_space(drv->pPlayPtr) - drv->jack_buffer_size;
+ if(return_val <= 0)
+ {
+ return_val = 0;
+ } else
+ {
+ /* adjust from jack bytes to client bytes */
+ return_val =
+ return_val / drv->bytes_per_jack_output_frame *
+ drv->bytes_per_output_frame;
+ }
+
+ return return_val;
+}
+
+/* Return the number of bytes we can write to the device */
+unsigned long
+JACK_GetBytesFreeSpace(int deviceID)
+{
+ jack_driver_t *drv = getDriver(deviceID);
+ unsigned long return_val;
+
+ return_val = JACK_GetBytesFreeSpaceFromDriver(drv);
+ releaseDriver(drv);
+
+ TRACE("deviceID(%d), retval == %ld\n", deviceID, return_val);
+
+ return return_val;
+}
+
+/* bytes of space used in the input buffer */
+unsigned long
+JACK_GetBytesUsedSpace(int deviceID)
+{
+ jack_driver_t *drv = getDriver(deviceID);
+ long return_val;
+
+ if(drv->pRecPtr == 0 || drv->bytes_per_jack_input_frame == 0)
+ {
+ return_val = 0;
+ } else
+ {
+ /* adjust from jack bytes to client bytes */
+ return_val =
+ jack_ringbuffer_read_space(drv->pRecPtr) /
+ drv->bytes_per_jack_input_frame * drv->bytes_per_input_frame;
+ }
+
+ releaseDriver(drv);
+
+ if(return_val < 0)
+ return_val = 0;
+ TRACE("deviceID(%d), retval == %ld\n", deviceID, return_val);
+
+ return return_val;
+}
+
+/* Get the current position of the driver, either in bytes or */
+/* in milliseconds */
+/* NOTE: this is position relative to input bytes, output bytes may differ greatly due to
+ input vs. output channel count */
+static long
+JACK_GetPositionFromDriver(jack_driver_t * drv, enum pos_enum position,
+ int type)
+{
+ long return_val = 0;
+ struct timeval now;
+ long elapsedMS;
+ double sec2msFactor = 1000;
+
+ char *type_str = "UNKNOWN type";
+
+ /* if we are reset we should return a position of 0 */
+ if(drv->state == RESET)
+ {
+ TRACE("we are currently RESET, returning 0\n");
+ return 0;
+ }
+
+ if(type == WRITTEN)
+ {
+ type_str = "WRITTEN";
+ return_val = drv->client_bytes;
+ } else if(type == WRITTEN_TO_JACK)
+ {
+ type_str = "WRITTEN_TO_JACK";
+ return_val = drv->written_client_bytes;
+ } else if(type == PLAYED) /* account for the elapsed time for the played_bytes */
+ {
+ type_str = "PLAYED";
+ return_val = drv->played_client_bytes;
+ gettimeofday(&now, 0);
+
+ elapsedMS = TimeValDifference(&drv->previousTime, &now); /* find the elapsed milliseconds since last JACK_Callback() */
+
+ TRACE("elapsedMS since last callback is '%ld'\n", elapsedMS);
+
+ /* account for the bytes played since the last JACK_Callback() */
+ /* NOTE: [Xms * (Bytes/Sec)] * (1 sec/1,000ms) */
+ /* NOTE: don't do any compensation if no data has been sent to jack since the last callback */
+ /* as this would result a bogus computed result */
+ if(drv->clientBytesInJack != 0)
+ {
+ return_val += (long) ((double) elapsedMS *
+ ((double) JACK_GetOutputBytesPerSecondFromDriver(drv) /
+ sec2msFactor));
+ } else
+ {
+ TRACE("clientBytesInJack == 0\n");
+ }
+ }
+
+ /* add on the offset */
+ return_val += drv->position_byte_offset;
+
+ /* convert byte position to milliseconds value if necessary */
+ if(position == MILLISECONDS)
+ {
+ if(JACK_GetOutputBytesPerSecondFromDriver(drv) != 0)
+ {
+ return_val = (long) (((double) return_val /
+ (double) JACK_GetOutputBytesPerSecondFromDriver(drv)) *
+ (double) sec2msFactor);
+ } else
+ {
+ return_val = 0;
+ }
+ }
+
+ TRACE("drv->deviceID(%d), type(%s), return_val = %ld\n", drv->deviceID,
+ type_str, return_val);
+
+ return return_val;
+}
+
+/* Get the current position of the driver, either in bytes or */
+/* in milliseconds */
+/* NOTE: this is position relative to input bytes, output bytes may differ greatly due to input vs. output channel count */
+long
+JACK_GetPosition(int deviceID, enum pos_enum position, int type)
+{
+ jack_driver_t *drv = getDriver(deviceID);
+ long retval = JACK_GetPositionFromDriver(drv, position, type);
+ releaseDriver(drv);
+ TRACE("retval == %ld\n", retval);
+ return retval;
+}
+
+// Set position always applies to written bytes
+// NOTE: we must apply this instantly because if we pass this as a message
+// to the callback we risk the user sending us audio data in the mean time
+// and there is no need to send this as a message, we don't modify any
+// internal variables
+void
+JACK_SetPositionFromDriver(jack_driver_t * drv, enum pos_enum position,
+ long value)
+{
+ double sec2msFactor = 1000;
+#if TRACE_ENABLE
+ long input_value = value;
+#endif
+
+ /* convert the incoming value from milliseconds into bytes */
+ if(position == MILLISECONDS)
+ {
+ value = (long) (((double) value *
+ (double) JACK_GetOutputBytesPerSecondFromDriver(drv)) /
+ sec2msFactor);
+ }
+
+ /* ensure that if the user asks for the position */
+ /* they will at this instant get the correct position */
+ drv->position_byte_offset = value - drv->client_bytes;
+
+ TRACE("deviceID(%d) input_value of %ld %s, new value of %ld, setting position_byte_offset to %ld\n",
+ drv->deviceID, input_value, (position == MILLISECONDS) ? "ms" : "bytes",
+ value, drv->position_byte_offset);
+}
+
+// Set position always applies to written bytes
+// NOTE: we must apply this instantly because if we pass this as a message
+// to the callback we risk the user sending us audio data in the mean time
+// and there is no need to send this as a message, we don't modify any
+// internal variables
+void
+JACK_SetPosition(int deviceID, enum pos_enum position, long value)
+{
+ jack_driver_t *drv = getDriver(deviceID);
+ JACK_SetPositionFromDriver(drv, position, value);
+ releaseDriver(drv);
+
+ TRACE("deviceID(%d) value of %ld\n", drv->deviceID, value);
+}
+
+/* Return the number of bytes per frame, or (output_channels * bits_per_channel) / 8 */
+unsigned long
+JACK_GetBytesPerOutputFrame(int deviceID)
+{
+ jack_driver_t *drv = getDriver(deviceID);
+ long return_val = drv->bytes_per_output_frame;
+ releaseDriver(drv);
+ TRACE("deviceID(%d), return_val = %ld\n", deviceID, return_val);
+ return return_val;
+}
+
+/* Return the number of bytes per frame, or (input_channels * bits_per_channel) / 8 */
+unsigned long
+JACK_GetBytesPerInputFrame(int deviceID)
+{
+ jack_driver_t *drv = getDriver(deviceID);
+ long return_val = drv->bytes_per_input_frame;
+ releaseDriver(drv);
+ TRACE("deviceID(%d), return_val = %ld\n", deviceID, return_val);
+ return return_val;
+}
+
+/* Return the number of output bytes we buffer max */
+long
+JACK_GetMaxOutputBufferedBytes(int deviceID)
+{
+ jack_driver_t *drv = getDriver(deviceID);
+ long return_val;
+
+ if(drv->pPlayPtr == 0 || drv->bytes_per_jack_output_frame == 0) return_val = 0;
+
+ /* adjust from jack bytes to client bytes */
+ return_val =
+ (jack_ringbuffer_read_space(drv->pPlayPtr) +
+ jack_ringbuffer_write_space(drv->pPlayPtr)) /
+ drv->bytes_per_jack_output_frame * drv->bytes_per_output_frame;
+
+ releaseDriver(drv);
+
+ TRACE("return_val = %ld\n", return_val);
+
+ return return_val;
+}
+
+/* Return the number of input bytes we buffer max */
+long
+JACK_GetMaxInputBufferedBytes(int deviceID)
+{
+ jack_driver_t *drv = getDriver(deviceID);
+ long return_val;
+
+ if(drv->pRecPtr == 0 || drv->bytes_per_jack_input_frame == 0) return_val = 0;
+
+ /* adjust from jack bytes to client bytes */
+ return_val =
+ (jack_ringbuffer_read_space(drv->pRecPtr) +
+ jack_ringbuffer_write_space(drv->pRecPtr)) /
+ drv->bytes_per_jack_input_frame * drv->bytes_per_input_frame;
+
+ releaseDriver(drv);
+
+ TRACE("return_val = %ld\n", return_val);
+
+ return return_val;
+}
+
+/* Get the number of output channels */
+int
+JACK_GetNumOutputChannels(int deviceID)
+{
+ jack_driver_t *drv = getDriver(deviceID);
+ int return_val = drv->num_output_channels;
+ releaseDriver(drv);
+ TRACE("getting num_output_channels of %d\n", return_val);
+ return return_val;
+}
+
+/* Get the number of input channels */
+int
+JACK_GetNumInputChannels(int deviceID)
+{
+ jack_driver_t *drv = getDriver(deviceID);
+ int return_val = drv->num_input_channels;
+ releaseDriver(drv);
+ TRACE("getting num_input_channels of %d\n", return_val);
+ return return_val;
+}
+
+/* Get the number of samples per second, the sample rate */
+long
+JACK_GetSampleRate(int deviceID)
+{
+ jack_driver_t *drv = getDriver(deviceID);
+ int return_val = drv->client_sample_rate;
+ releaseDriver(drv);
+ TRACE("getting sample_rate of %d\n", return_val);
+ return return_val;
+}
+
+void
+JACK_CleanupDriver(jack_driver_t * drv)
+{
+ TRACE("\n");
+ /* things that need to be reset both in JACK_Init & JACK_CloseDevice */
+ drv->client = 0;
+ drv->in_use = FALSE;
+ drv->state = CLOSED;
+ drv->jack_sample_rate = 0;
+ drv->output_sample_rate_ratio = 1.0;
+ drv->input_sample_rate_ratio = 1.0;
+ drv->jackd_died = FALSE;
+ gettimeofday(&drv->previousTime, 0); /* record the current time */
+ gettimeofday(&drv->last_reconnect_attempt, 0);
+}
+
+/* Initialize the jack porting library to a clean state */
+void
+JACK_Init(void)
+{
+ jack_driver_t *drv;
+ int x, y;
+
+ if(init_done)
+ {
+ TRACE("not initing twice\n");
+ return;
+ }
+
+ init_done = 1;
+
+ TRACE("\n");
+
+ pthread_mutex_lock(&device_mutex);
+
+ /* initialize the device structures */
+ for(x = 0; x < MAX_OUTDEVICES; x++)
+ {
+ drv = &outDev[x];
+
+ pthread_mutex_init(&drv->mutex, NULL);
+
+ getDriver(x);
+
+ memset(drv, 0, sizeof(jack_driver_t));
+ drv->volumeEffectType = linear;
+ drv->deviceID = x;
+
+ for(y = 0; y < MAX_OUTPUT_PORTS; y++) /* make all volume 25% as a default */
+ drv->volume[y] = 25;
+
+ JACK_CleanupDriver(drv);
+ JACK_ResetFromDriver(drv);
+ releaseDriver(drv);
+ }
+
+ client_name = 0; /* initialize the name to null */
+ do_sample_rate_conversion = TRUE; /* default to on */
+ JACK_SetClientName("bio2jack");
+
+ pthread_mutex_unlock(&device_mutex);
+
+ TRACE("finished\n");
+}
+
+/* Get the latency, in frames, of jack */
+long
+JACK_GetJackOutputLatency(int deviceID)
+{
+ jack_driver_t *drv = getDriver(deviceID);
+ long return_val = 0;
+
+ if(drv->client && drv->num_output_channels)
+ return_val = jack_port_get_total_latency(drv->client, drv->output_port[0]);
+
+ TRACE("got latency of %ld frames\n", return_val);
+
+ releaseDriver(drv);
+ return return_val;
+}
+
+/* Get the latency, in frames, of jack */
+long
+JACK_GetJackInputLatency(int deviceID)
+{
+ jack_driver_t *drv = getDriver(deviceID);
+ long return_val = 0;
+
+ if(drv->client && drv->num_input_channels)
+ return_val = jack_port_get_total_latency(drv->client, drv->input_port[0]);
+
+ TRACE("got latency of %ld frames\n", return_val);
+
+ releaseDriver(drv);
+ return return_val;
+}
+
+/* bytes that jack requests during each callback */
+unsigned long
+JACK_GetJackBufferedBytes(int deviceID)
+{
+ jack_driver_t *drv = getDriver(deviceID);
+ long return_val;
+
+ if(drv->bytes_per_jack_output_frame == 0)
+ {
+ return_val = 0;
+ } else
+ {
+ /* adjust from jack bytes to client bytes */
+ return_val =
+ drv->jack_buffer_size / drv->bytes_per_jack_output_frame *
+ drv->bytes_per_output_frame * drv->num_output_channels;
+ }
+
+ releaseDriver(drv);
+ return return_val;
+}
+
+/* value = TRUE, perform sample rate conversion */
+void
+JACK_DoSampleRateConversion(bool value)
+{
+ do_sample_rate_conversion = value;
+}
+
+/* FIXME: put the filename of the resample library header file with the decoders in here */
+/* consider mapping them in the bio2jack.h header file since its useless to the user unless */
+/* they can figure out wtf the settings on */
+void
+JACK_SetSampleRateConversionFunction(int converter)
+{
+ preferred_src_converter = converter;
+}
+
+/* set the client name that will be reported to jack when we open a */
+/* connection via JACK_OpenDevice() */
+void
+JACK_SetClientName(char *name)
+{
+ if(name)
+ {
+ if(client_name) free(client_name);
+
+ /* jack_client_name_size() is the max length of a client name, including
+ the terminating null. */
+ int size = strlen(name) + 1; /* take into account the terminating null */
+ if(size > jack_client_name_size())
+ size = jack_client_name_size();
+
+ client_name = malloc(size);
+ if(client_name)
+ snprintf(client_name, size, "%s", name);
+ else
+ ERR("unable to allocate %d bytes for client_name\n", size);
+ }
+}
+
+void
+JACK_SetPortConnectionMode(enum JACK_PORT_CONNECTION_MODE mode)
+{
+ port_connection_mode = mode;
+}
diff --git a/src/plugins/Output/jack/bio2jack.h b/src/plugins/Output/jack/bio2jack.h
new file mode 100644
index 000000000..f81a7c777
--- /dev/null
+++ b/src/plugins/Output/jack/bio2jack.h
@@ -0,0 +1,145 @@
+/*
+ * Copyright 2003-2004 Chris Morgan <cmorgan@alum.wpi.edu>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _H_JACK_OUT_H
+#define _H_JACK_OUT_H
+
+#include <jack/jack.h>
+
+#ifdef __cplusplus
+extern "C" {
+#else
+#define bool long
+#endif
+
+#ifndef TRUE
+#define TRUE 1
+#endif
+
+#ifndef FALSE
+#define FALSE 0
+#endif
+
+#define ERR_SUCCESS 0
+#define ERR_OPENING_JACK 1
+#define ERR_RATE_MISMATCH 2
+#define ERR_BYTES_PER_OUTPUT_FRAME_INVALID 3
+#define ERR_BYTES_PER_INPUT_FRAME_INVALID 4
+#define ERR_TOO_MANY_OUTPUT_CHANNELS 5
+#define ERR_PORT_NAME_OUTPUT_CHANNEL_MISMATCH 6
+#define ERR_PORT_NOT_FOUND 7
+#define ERR_TOO_MANY_INPUT_CHANNELS 8
+#define ERR_PORT_NAME_INPUT_CHANNEL_MISMATCH 9
+
+enum status_enum { PLAYING, PAUSED, STOPPED, CLOSED, RESET };
+enum pos_enum { BYTES, MILLISECONDS };
+
+#define PLAYED 1 /* played out of the speakers(estimated value but should be close */
+#define WRITTEN_TO_JACK 2 /* amount written out to jack */
+#define WRITTEN 3 /* amount written to the bio2jack device */
+
+/**********************/
+/* External functions */
+void JACK_Init(void); /* call this before any other bio2jack calls */
+void JACK_DoSampleRateConversion(bool value); /* whether the next device that's Open()d should do
+ sample rate conversion if necessary */
+void JACK_SetSampleRateConversionFunction(int converter); /* which SRC converter function should be used
+ for the next Open()d device */
+int JACK_Open(int *deviceID, unsigned int bits_per_sample, unsigned long *rate, int channels); /* Note: defaults to 0 input channels
+ if you need input (record) use OpenEx
+ instead */
+int JACK_OpenEx(int *deviceID, unsigned int bits_per_channel,
+ unsigned long *rate,
+ unsigned int input_channels, unsigned int output_channels,
+ const char **jack_port_name, unsigned int jack_port_name_count,
+ unsigned long jack_port_flags);
+int JACK_Close(int deviceID); /* return 0 for success */
+void JACK_Reset(int deviceID); /* free all buffered data and reset several values in the device */
+long JACK_Write(int deviceID, unsigned char *data, unsigned long bytes); /* returns the number of bytes written */
+long JACK_Read(int deviceID, unsigned char *data, unsigned long bytes); /* returns the number of bytes read */
+
+/* state setting values */
+/* set/get the written/played/buffered value based on a byte or millisecond input value */
+long JACK_GetPosition(int deviceID, enum pos_enum position, int type);
+void JACK_SetPosition(int deviceID, enum pos_enum position, long value);
+
+long JACK_GetJackLatency(int deviceID); /* deprectated, you probably want JACK_GetJackOutputLatency */
+long JACK_GetJackOutputLatency(int deviceID); /* return the output latency in frames */
+long JACK_GetJackInputLatency(int deviceID); /* return the input latency in frames */
+
+int JACK_SetState(int deviceID, enum status_enum state); /* playing, paused, stopped */
+enum status_enum JACK_GetState(int deviceID);
+
+long JACK_GetMaxOutputBufferedBytes(int deviceID);
+long JACK_GetMaxInputBufferedBytes(int deviceID);
+
+/* bytes that jack requests during each callback */
+unsigned long JACK_GetJackBufferedBytes(int deviceID);
+
+/* Properties of the jack driver */
+
+/* linear means 0 volume is silence, 100 is full volume */
+/* dbAttenuation means 0 volume is 0dB attenuation */
+/* Bio2jack defaults to linear */
+/* Note: volume controls only effect output channels for now */
+enum JACK_VOLUME_TYPE { linear, dbAttenuation };
+enum JACK_VOLUME_TYPE JACK_SetVolumeEffectType(int deviceID,
+ enum JACK_VOLUME_TYPE type);
+
+int JACK_SetAllVolume(int deviceID, unsigned int volume); /* returns 0 on success */
+int JACK_SetVolumeForChannel(int deviceID, unsigned int channel, unsigned int volume);
+void JACK_GetVolumeForChannel(int deviceID, unsigned int channel, unsigned int *volume);
+
+
+unsigned long JACK_GetOutputBytesPerSecond(int deviceID); /* bytes_per_output_frame * sample_rate */
+unsigned long JACK_GetInputBytesPerSecond(int deviceID); /* bytes_per_input_frame * sample_rate */
+unsigned long JACK_GetBytesStored(int deviceID); /* bytes currently buffered in the output buffer */
+unsigned long JACK_GetBytesFreeSpace(int deviceID); /* bytes of free space in the output buffer */
+unsigned long JACK_GetBytesUsedSpace(int deviceID); /* bytes of space used in the input buffer */
+unsigned long JACK_GetBytesPerOutputFrame(int deviceID);
+unsigned long JACK_GetBytesPerInputFrame(int deviceID);
+
+/* Note: these will probably be removed in a future release */
+int JACK_GetNumInputChannels(int deviceID);
+int JACK_GetNumOutputChannels(int deviceID);
+
+long JACK_GetSampleRate(int deviceID); /* samples per second */
+
+void JACK_SetClientName(char *name); /* sets the name that bio2jack will use when
+ creating a new jack client. name_%pid%_%deviceID%%counter%
+ will be used
+ NOTE: this defaults to name = bio2jack
+ NOTE: we limit the size of the client name to
+ jack_client_name_size() */
+
+enum JACK_PORT_CONNECTION_MODE
+{
+ CONNECT_ALL, /* connect to all avaliable ports */
+ CONNECT_OUTPUT, /* connect only to the ports we need for output */
+ CONNECT_NONE /* don't connect to any ports */
+};
+
+/* set the mode for port connections */
+/* defaults to CONNECT_ALL */
+void JACK_SetPortConnectionMode(enum JACK_PORT_CONNECTION_MODE mode);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* #ifndef JACK_OUT_H */
diff --git a/src/plugins/Output/jack/jack.pro b/src/plugins/Output/jack/jack.pro
new file mode 100644
index 000000000..a943a5b3b
--- /dev/null
+++ b/src/plugins/Output/jack/jack.pro
@@ -0,0 +1,33 @@
+include(../../plugins.pri)
+
+HEADERS += outputjackfactory.h \
+ outputjack.h \
+ bio2jack.h
+
+SOURCES += outputjackfactory.cpp \
+ outputjack.cpp \
+ bio2jack.c
+
+TARGET=$$PLUGINS_PREFIX/Output/jack
+QMAKE_CLEAN =$$PLUGINS_PREFIX/Output/libjack.so
+
+
+INCLUDEPATH += ../../../qmmp
+QMAKE_LIBDIR += ../../../../lib
+CONFIG += release \
+warn_on \
+thread \
+plugin \
+link_pkgconfig
+TEMPLATE = lib
+LIBS += -lqmmp
+PKGCONFIG += jack samplerate
+#TRANSLATIONS = translations/jack_plugin_ru.ts
+#RESOURCES = translations/translations.qrc
+
+isEmpty (LIB_DIR){
+LIB_DIR = /lib
+}
+
+target.path = $$LIB_DIR/qmmp/Output
+INSTALLS += target
diff --git a/src/plugins/Output/jack/outputjack.cpp b/src/plugins/Output/jack/outputjack.cpp
new file mode 100644
index 000000000..c8d3ebe86
--- /dev/null
+++ b/src/plugins/Output/jack/outputjack.cpp
@@ -0,0 +1,208 @@
+#include <QObject>
+#include <QApplication>
+#include <QtGlobal>
+#include <QDir>
+#include <QSettings>
+
+#include "outputjack.h"
+#include "constants.h"
+#include "buffer.h"
+#include "visual.h"
+
+#include <stdio.h>
+#include <string.h>
+
+void OutputJACK::stop()
+{
+ m_userStop = TRUE;
+}
+
+void OutputJACK::status()
+{
+ long ct = (m_totalWritten - latency()) / m_bps;
+
+ if (ct < 0)
+ ct = 0;
+
+ if (ct > m_currentSeconds)
+ {
+ m_currentSeconds = ct;
+ dispatch(m_currentSeconds, m_totalWritten, m_rate,
+ m_frequency, m_precision, m_channels);
+ }
+}
+
+long OutputJACK::written()
+{
+ return m_totalWritten;
+}
+
+void OutputJACK::seek(long pos)
+{
+ recycler()->mutex()->lock ();
+ recycler()->clear();
+ recycler()->mutex()->unlock();
+
+ m_totalWritten = (pos * m_bps);
+ m_currentSeconds = -1;
+}
+
+OutputJACK::OutputJACK(QObject * parent, bool useVolume)
+ : Output(parent), m_inited(FALSE), m_pause(FALSE), m_play(FALSE),
+ m_userStop(FALSE), m_totalWritten(0), m_currentSeconds(-1),
+ m_bps(-1), m_frequency(-1), m_channels(-1), m_precision(-1)
+{
+ JACK_Init();
+}
+
+OutputJACK::~OutputJACK()
+{
+ uninitialize();
+}
+
+void OutputJACK::configure(long freq, int chan, int prec, int brate)
+{
+ qDebug("OutputJACK: configure");
+ m_precision = prec;
+ m_channels = chan;
+ m_frequency = freq;
+ m_bps = freq * chan * (prec / 8);
+ if(JACK_Open(&jack_device, prec, (unsigned long *)&freq, chan))
+ {
+ m_configure = FALSE;
+ return;
+ }
+ else
+ m_configure = TRUE;
+
+ qDebug("OutputJACK: configure end");
+}
+
+void OutputJACK::pause()
+{
+ m_pause = (m_pause) ? FALSE : TRUE;
+ {
+ int state = m_pause ? OutputState::Paused: OutputState::Playing;
+ dispatch(OutputState((OutputState::Type) state));
+ }
+
+}
+
+bool OutputJACK::initialize()
+{
+ m_inited = m_pause = m_play = m_userStop = FALSE;
+ m_currentSeconds = -1;
+ m_totalWritten = 0;
+ if (m_inited)
+ m_inited = TRUE;
+ m_inited = TRUE;
+ m_configure = FALSE;
+ jack_options_t options = JackNullOption;
+ jack_status_t status;
+ jack_client_t *client = jack_client_open ("test_qmmp", options, &status, NULL);
+ if (client == NULL)
+ {
+ qDebug("jack_client_open() failed.");
+ if (status & JackServerFailed)
+ {
+ qDebug("Unable to connect to JACK server.");
+ }
+ return FALSE;
+ }
+ jack_client_close (client);
+ return TRUE;
+}
+
+long OutputJACK::latency()
+{
+ ulong used = 0;
+ return used;
+}
+
+void OutputJACK::run()
+{
+ mutex()->lock ();
+ if (! m_inited)
+ {
+ mutex()->unlock();
+ return;
+ }
+
+ m_play = TRUE;
+ Buffer *b = 0;
+ bool done = FALSE;
+ long m = 0;
+ unsigned long l;
+
+ dispatch(OutputState::Playing);
+
+ mutex()->unlock();
+ while (!done&&m_configure)
+ {
+ mutex()->lock ();
+ recycler()->mutex()->lock ();
+ done = m_userStop;
+
+ while (! done && (recycler()->empty() || m_pause))
+ {
+ mutex()->unlock();
+ recycler()->cond()->wakeOne();
+ recycler()->cond()->wait(recycler()->mutex());
+ mutex()->lock ();
+ done = m_userStop;
+ status();
+ }
+
+ if (! b)
+ {
+ b = recycler()->next();
+ if (b->rate)
+ m_rate = b->rate;
+ }
+
+ recycler()->cond()->wakeOne();
+ recycler()->mutex()->unlock();
+
+ if (b)
+ {
+ l = int(b->nbytes);
+ unsigned char *buf = b->data;
+ m_totalWritten += l;
+ while (l > 0)
+ {
+ m = JACK_Write(jack_device, (unsigned char*)buf, l);
+ if (!m)
+ usleep(2000);
+ usleep(((m/m_channels)*100000)/m_frequency);
+ l-=m;
+ buf+=m;
+ }
+
+ status();
+ dispatchVisual(b, m_totalWritten, m_channels, m_precision);
+ }
+ recycler()->mutex()->lock ();
+ recycler()->done();
+ recycler()->mutex()->unlock();
+ b = 0;
+ mutex()->unlock();
+ }
+ mutex()->lock ();
+ m_play = FALSE;
+ dispatch(OutputState::Stopped);
+ mutex()->unlock();
+}
+
+void OutputJACK::uninitialize()
+{
+ if (!m_inited)
+ return;
+ m_inited = FALSE;
+ m_inited = m_pause = m_play = m_userStop = FALSE;
+ m_currentSeconds = -1;
+ m_totalWritten = 0;
+ if (m_configure)
+ JACK_Close(jack_device);
+ dispatch(OutputState::Stopped);
+}
+
diff --git a/src/plugins/Output/jack/outputjack.h b/src/plugins/Output/jack/outputjack.h
new file mode 100644
index 000000000..e01ced315
--- /dev/null
+++ b/src/plugins/Output/jack/outputjack.h
@@ -0,0 +1,49 @@
+#ifndef OUTPUTJACK_H
+#define OUTPUTJACK_H
+
+class OutputJACK;
+
+#include <output.h>
+#include <QObject>
+extern "C"
+{
+#include <jack/jack.h>
+}
+
+#include "bio2jack.h"
+
+class OutputJACK : public Output
+{
+ Q_OBJECT
+public:
+ OutputJACK(QObject * parent = 0, bool useVolume = TRUE);
+ ~OutputJACK();
+ bool initialize();
+ bool isInitialized() const
+ {
+ return m_inited;
+ }
+ void uninitialize();
+ void configure(long, int, int, int);
+ void stop();
+ void pause();
+ long written();
+ long latency();
+ void seek(long);
+
+private:
+ // thread run function
+ void run();
+ // helper functions
+ void status();
+ QString audio_device;
+ bool m_inited, m_configure, m_pause, m_play, m_userStop;
+ long m_totalWritten, m_currentSeconds, m_bps;
+ int m_rate, m_frequency, m_channels, m_precision, jack_device;
+ bool do_select;
+ int audio_fd;
+};
+
+
+#endif
+
diff --git a/src/plugins/Output/jack/outputjackfactory.cpp b/src/plugins/Output/jack/outputjackfactory.cpp
new file mode 100644
index 000000000..b41ba4487
--- /dev/null
+++ b/src/plugins/Output/jack/outputjackfactory.cpp
@@ -0,0 +1,60 @@
+/***************************************************************************
+ * Copyright (C) 2007 by Zhuravlev Uriy *
+ * stalkerg@gmail.com *
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program; if not, write to the *
+ * Free Software Foundation, Inc., *
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
+ ***************************************************************************/
+
+#include <QtGui>
+
+#include "outputjack.h"
+#include "outputjackfactory.h"
+
+
+const OutputProperties OutputJACKFactory::properties() const
+{
+ OutputProperties properties;
+ properties.name = tr("JACK Plugin");
+ properties.hasAbout = TRUE;
+ properties.hasSettings = TRUE;
+ return properties;
+}
+
+Output* OutputJACKFactory::create(QObject* parent, bool volume)
+{
+ return new OutputJACK(parent);
+}
+
+void OutputJACKFactory::showSettings(QWidget*)
+{
+}
+
+void OutputJACKFactory::showAbout(QWidget *parent)
+{
+QMessageBox::about (parent, tr("About Jack Output Plugin"),
+ tr("Qmmp Jack Output Plugin")+"\n"+
+ tr("Writen by: Yuriy Zhuravlev <slalkerg@gmail.com>"));
+}
+
+QTranslator *OutputJACKFactory::createTranslator(QObject *parent)
+{
+ QTranslator *translator = new QTranslator(parent);
+ QString locale = QLocale::system().name();
+ translator->load(QString(":/jack_plugin_") + locale);
+ return translator;
+}
+
+Q_EXPORT_PLUGIN(OutputJACKFactory)
diff --git a/src/plugins/Output/jack/outputjackfactory.h b/src/plugins/Output/jack/outputjackfactory.h
new file mode 100644
index 000000000..cc27990f1
--- /dev/null
+++ b/src/plugins/Output/jack/outputjackfactory.h
@@ -0,0 +1,48 @@
+/***************************************************************************
+ * Copyright (C) 2006 by Ilya Kotov *
+ * forkotov02@hotmail.ru *
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program; if not, write to the *
+ * Free Software Foundation, Inc., *
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
+ ***************************************************************************/
+#ifndef OUTPUTJACKFACTORY_H
+#define OUTPUTJACKFACTORY_H
+
+
+#include <QObject>
+#include <QString>
+#include <QIODevice>
+#include <QWidget>
+
+#include <output.h>
+#include <outputfactory.h>
+
+
+class OutputJACKFactory : public QObject,
+ OutputFactory
+{
+Q_OBJECT
+Q_INTERFACES(OutputFactory);
+
+public:
+ const OutputProperties properties() const;
+ Output* create(QObject* parent, bool volume);
+ void showSettings(QWidget* parent);
+ void showAbout(QWidget *parent);
+ QTranslator *createTranslator(QObject *parent);
+
+};
+
+#endif
diff --git a/src/plugins/Output/jack/translations/jack_plugin_ru.qm b/src/plugins/Output/jack/translations/jack_plugin_ru.qm
new file mode 100644
index 000000000..4050034bc
--- /dev/null
+++ b/src/plugins/Output/jack/translations/jack_plugin_ru.qm
Binary files differ
diff --git a/src/plugins/Output/jack/translations/jack_plugin_ru.ts b/src/plugins/Output/jack/translations/jack_plugin_ru.ts
new file mode 100644
index 000000000..6f5057529
--- /dev/null
+++ b/src/plugins/Output/jack/translations/jack_plugin_ru.ts
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS><TS version="1.1" language="ru">
+<context>
+ <name>OutputJACKFactory</name>
+ <message>
+ <location filename="../outputjackfactory.cpp" line="29"/>
+ <source>JACK Plugin</source>
+ <translation>Модуль JACK</translation>
+ </message>
+ <message>
+ <location filename="../outputjackfactory.cpp" line="44"/>
+ <source>About Jack Output Plugin</source>
+ <translation>О модуле вывода Jack</translation>
+ </message>
+ <message>
+ <location filename="../outputjackfactory.cpp" line="45"/>
+ <source>Qmmp Jack Output Plugin</source>
+ <translation>Модуль вывода Jack для Qmmp</translation>
+ </message>
+ <message>
+ <location filename="../outputjackfactory.cpp" line="46"/>
+ <source>Writen by: Yuriy Zhuravlev &lt;slalkerg@gmail.com&gt;</source>
+ <translation>Разработчик: Юрий Журавлёв &lt;slalkerg@gmail.com&gt;</translation>
+ </message>
+</context>
+</TS>
diff --git a/src/plugins/Output/jack/translations/translations.qrc b/src/plugins/Output/jack/translations/translations.qrc
new file mode 100644
index 000000000..af9447328
--- /dev/null
+++ b/src/plugins/Output/jack/translations/translations.qrc
@@ -0,0 +1,6 @@
+<!DOCTYPE RCC>
+<RCC version="1.0">
+ <qresource>
+ <file>jack_plugin_ru.qm</file>
+ </qresource>
+</RCC>
diff --git a/src/plugins/Output/oss/CMakeLists.txt b/src/plugins/Output/oss/CMakeLists.txt
new file mode 100644
index 000000000..8ab6f8d88
--- /dev/null
+++ b/src/plugins/Output/oss/CMakeLists.txt
@@ -0,0 +1,67 @@
+project(liboss)
+
+cmake_minimum_required(VERSION 2.4.0)
+
+
+INCLUDE(UsePkgConfig)
+INCLUDE(FindQt4)
+
+find_package(Qt4 REQUIRED) # find and setup Qt4 for this project
+include(${QT_USE_FILE})
+
+# qt plugin
+ADD_DEFINITIONS( -Wall )
+ADD_DEFINITIONS(${QT_DEFINITIONS})
+ADD_DEFINITIONS(-DQT_PLUGIN)
+ADD_DEFINITIONS(-DQT_NO_DEBUG)
+ADD_DEFINITIONS(-DQT_SHARED)
+ADD_DEFINITIONS(-DQT_THREAD)
+
+include_directories(${CMAKE_CURRENT_BINARY_DIR})
+
+SET(QT_INCLUDES
+ ${QT_INCLUDES}
+ ${CMAKE_CURRENT_BINARY_DIR}/../../../
+)
+
+# libqmmp
+include_directories(${CMAKE_CURRENT_BINARY_DIR}/../../../)
+link_directories(${CMAKE_CURRENT_BINARY_DIR}/../../../)
+
+
+
+include_directories(${JACK_INCLUDE_DIR} ${JACK_INCLUDE_DIR})
+link_directories(${SAMPLERATE_LINK_DIR} ${SAMPLERATE_LINK_DIR})
+
+ADD_DEFINITIONS(${JACK_CFLAGS})
+ADD_DEFINITIONS(${SAMPLERATE_CFLAGS})
+
+
+SET(liboss_SRCS
+ outputossfactory.cpp
+ outputoss.cpp
+ settingsdialog.cpp
+)
+
+SET(liboss_MOC_HDRS
+ outputossfactory.h
+ outputoss.h
+ settingsdialog.h
+)
+
+#SET(libjack_RCCS translations/translations.qrc)
+
+QT4_ADD_RESOURCES(libjack_RCC_SRCS ${libjack_RCCS})
+
+QT4_WRAP_CPP(liboss_MOC_SRCS ${liboss_MOC_HDRS})
+
+SET(liboss_UIS
+ settingsdialog.ui
+)
+
+QT4_WRAP_UI(liboss_UIS_H ${liboss_UIS})
+
+ADD_LIBRARY(oss SHARED ${liboss_SRCS} ${liboss_MOC_SRCS} ${liboss_UIS_H})
+target_link_libraries(oss ${QT_LIBRARIES} -lqmmp )
+install(TARGETS oss DESTINATION ${LIB_DIR}/qmmp/Output PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_EXECUTE GROUP_READ WORLD_EXECUTE WORLD_READ)
+
diff --git a/src/plugins/Output/oss/oss.pro b/src/plugins/Output/oss/oss.pro
new file mode 100644
index 000000000..e401f14b0
--- /dev/null
+++ b/src/plugins/Output/oss/oss.pro
@@ -0,0 +1,36 @@
+include(../../plugins.pri)
+FORMS += settingsdialog.ui
+
+HEADERS += outputossfactory.h \
+ outputoss.h \
+ settingsdialog.h
+
+
+SOURCES += outputossfactory.cpp \
+ outputoss.cpp \
+ settingsdialog.cpp
+
+TARGET=$$PLUGINS_PREFIX/Output/oss
+QMAKE_CLEAN =$$PLUGINS_PREFIX/Output/liboss.so
+
+
+INCLUDEPATH += ../../../qmmp
+QMAKE_LIBDIR += ../../../../lib
+CONFIG += release \
+warn_on \
+thread \
+plugin
+
+TEMPLATE = lib
+LIBS += -lqmmp
+
+#TRANSLATIONS = translations/oss_plugin_ru.ts \
+# translations/oss_plugin_uk_UA.ts \
+# translations/oss_plugin_zh_CN.ts
+#RESOURCES = translations/translations.qrc
+isEmpty (LIB_DIR){
+LIB_DIR = /lib
+}
+
+target.path = $$LIB_DIR/qmmp/Output
+INSTALLS += target
diff --git a/src/plugins/Output/oss/outputoss.cpp b/src/plugins/Output/oss/outputoss.cpp
new file mode 100644
index 000000000..9c52ac777
--- /dev/null
+++ b/src/plugins/Output/oss/outputoss.cpp
@@ -0,0 +1,505 @@
+/***************************************************************************
+ * Copyright (C) 2007 by Uriy Zhuravlev stalkerg@gmail.com *
+ * *
+ * Copyright (c) 2000-2001 Brad Hughes bhughes@trolltech.com *
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program; if not, write to the *
+ * Free Software Foundation, Inc., *
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
+ ***************************************************************************/
+
+#include <QApplication>
+
+#include "outputoss.h"
+#include "constants.h"
+#include "buffer.h"
+#include "visual.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <QtGlobal>
+#include <QSettings>
+#include <QDir>
+
+#include <iostream>
+
+//extern Q_EXPORT QApplication* qApp;
+
+
+void OutputOSS::stop()
+{
+ m_userStop = TRUE;
+}
+
+void OutputOSS::status()
+{
+ long ct = (m_totalWritten - latency()) / m_bps;
+
+ if (ct < 0)
+ ct = 0;
+
+ if (ct > m_currentSeconds)
+ {
+ m_currentSeconds = ct;
+ dispatch(m_currentSeconds, m_totalWritten, m_rate,
+ m_frequency, m_precision, m_channels);
+ }
+}
+
+long OutputOSS::written()
+{
+ return m_totalWritten;
+}
+
+void OutputOSS::seek(long pos)
+{
+ recycler()->mutex()->lock();
+ recycler()->clear();
+ recycler()->mutex()->unlock();
+
+ m_totalWritten = (pos * m_bps);
+ m_currentSeconds = -1;
+}
+
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <sys/time.h>
+
+#if defined(__FreeBSD__)
+# include <sys/soundcard.h>
+#elif defined(__linux__)
+# include <linux/soundcard.h>
+#elif defined(__bsdi__)
+# include <sys/soundcard.h>
+#endif
+
+
+OutputOSS::OutputOSS(QObject * parent)
+ : Output(parent), m_inited(FALSE), m_pause(FALSE), m_play(FALSE),
+ m_userStop(FALSE),
+ m_totalWritten(0), m_currentSeconds(-1),
+ m_bps(1), m_frequency(-1), m_channels(-1), m_precision(-1),
+ do_select(TRUE),
+ m_audio_fd(-1), m_mixer_fd(-1)
+{
+QSettings settings(QDir::homePath()+"/.qmmp/qmmprc", QSettings::IniFormat);
+m_master = true;
+m_audio_device = settings.value("OSS/device","/dev/dsp").toString();
+m_mixer_device = settings.value("OSS/mixer_device","/dev/mixer").toString();
+openMixer();
+}
+
+OutputOSS::~OutputOSS()
+{
+ if (m_audio_fd > 0)
+ {
+ close(m_audio_fd);
+ m_audio_fd = -1;
+ }
+ if (m_mixer_fd > 0)
+ {
+ close(m_mixer_fd);
+ m_mixer_fd = -1;
+ }
+}
+
+void OutputOSS::configure(long freq, int chan, int prec, int rate)
+{
+ // we need to configure
+ if (freq != m_frequency || chan != m_channels || prec != m_precision) {
+ // we have already configured, but are changing settings...
+ // reset the device
+ resetDSP();
+
+ m_frequency = freq;
+ m_channels = chan;
+ m_precision = prec;
+
+ m_bps = freq * chan * (prec / 8);
+
+ int p;
+ switch(prec) {
+ default:
+ case 16:
+#if defined(AFMT_S16_NE)
+ p = AFMT_S16_NE;
+#else
+ p = AFMT_S16_LE;
+#endif
+ break;
+
+ case 8:
+ p = AFMT_S8;
+ break;
+
+ }
+
+ ioctl(m_audio_fd, SNDCTL_DSP_SETFMT, &p);
+ ioctl(m_audio_fd, SNDCTL_DSP_SAMPLESIZE, &prec);
+ int stereo = (chan > 1) ? 1 : 0;
+ ioctl(m_audio_fd, SNDCTL_DSP_STEREO, &stereo);
+ ioctl(m_audio_fd, SNDCTL_DSP_SPEED, &freq);
+ }
+
+ m_rate = rate;
+}
+
+
+void OutputOSS::reset()
+{
+ if (m_audio_fd > 0)
+ {
+ close(m_audio_fd);
+ m_audio_fd = -1;
+ }
+
+ m_audio_fd = open(m_audio_device.toAscii(), O_WRONLY, 0);
+
+ if (m_audio_fd < 0)
+ {
+ error(QString("OSSOutput: failed to open output device '%1'").
+ arg(m_audio_device));
+ return;
+ }
+
+ int flags;
+ if ((flags = fcntl(m_audio_fd, F_GETFL, 0)) > 0)
+ {
+ flags &= O_NDELAY;
+ fcntl(m_audio_fd, F_SETFL, flags);
+ }
+
+ fd_set afd;
+ FD_ZERO(&afd);
+ FD_SET(m_audio_fd, &afd);
+ struct timeval tv;
+ tv.tv_sec = 0l;
+ tv.tv_usec = 50000l;
+ do_select = (select(m_audio_fd + 1, 0, &afd, 0, &tv) > 0);
+
+ if (m_audio_fd > 0)
+ {
+ close(m_mixer_fd);
+ m_mixer_fd = -1;
+ }
+ openMixer();
+}
+
+void OutputOSS::openMixer()
+{
+ if (m_mixer_fd != -1)
+ return;
+
+ if ((m_mixer_fd = open(m_mixer_device.toAscii(), O_RDWR)) == -1)
+ {
+ return;
+ }
+ if (m_audio_fd < 0)
+ {
+ error(QString("OSSOutput: failed to open mixer device '%1'").
+ arg(m_mixer_device));
+ return;
+ }
+}
+
+void OutputOSS::pause()
+{
+ m_pause = (m_pause) ? FALSE : TRUE;
+}
+
+void OutputOSS::post()
+{
+ if (m_audio_fd < 1)
+ return;
+
+ int unused;
+ ioctl(m_audio_fd, SNDCTL_DSP_POST, &unused);
+}
+
+void OutputOSS::sync()
+{
+ if (m_audio_fd < 1)
+ return;
+
+ int unused;
+ ioctl(m_audio_fd, SNDCTL_DSP_SYNC, &unused);
+}
+
+
+void OutputOSS::resetDSP()
+{
+ if (m_audio_fd < 1)
+ return;
+
+ int unused;
+ ioctl(m_audio_fd, SNDCTL_DSP_RESET, &unused);
+}
+
+
+bool OutputOSS::initialize()
+{
+ m_inited = m_pause = m_play = m_userStop = FALSE;
+
+
+ reset();
+ if (m_audio_fd < 0)
+ return FALSE;
+ if (m_mixer_fd < 0)
+ return FALSE;
+
+
+ m_currentSeconds = -1;
+ m_totalWritten = 0;
+ stat = OutputState::Stopped;
+
+ m_inited = TRUE;
+ return TRUE;
+}
+
+void OutputOSS::uninitialize()
+{
+ if (!m_inited)
+ return;
+ m_inited = FALSE;
+ m_pause = FALSE;
+ m_play = FALSE;
+ m_userStop = FALSE;
+ m_totalWritten = 0;
+ m_currentSeconds = -1;
+ m_bps = -1;
+ m_frequency = -1;
+ m_channels = -1;
+ m_precision = -1;
+ resetDSP();
+ if (m_audio_fd > 0)
+ {
+ close(m_audio_fd);
+ m_audio_fd = -1;
+ }
+ if (m_audio_fd > 0)
+ {
+ close(m_mixer_fd);
+ m_mixer_fd = -1;
+ }
+
+ qDebug("OutputOSS: uninitialize");
+ dispatch(OutputState::Stopped);
+}
+
+long OutputOSS::latency()
+{
+ ulong used = 0;
+
+ if (! m_pause)
+ {
+ if (ioctl(m_audio_fd, SNDCTL_DSP_GETODELAY, &used) == -1)
+ used = 0;
+ }
+
+ return used;
+}
+
+void OutputOSS::run()
+{
+ mutex()->lock();
+
+ if (! m_inited)
+ {
+ mutex()->unlock();
+
+ return;
+ }
+
+ m_play = TRUE;
+
+ mutex()->unlock();
+
+ fd_set afd;
+ struct timeval tv;
+ Buffer *b = 0;
+ bool done = FALSE;
+ unsigned long n = 0, m = 0, l = 0;
+
+ dispatch(OutputState::Playing);
+
+ FD_ZERO(&afd);
+
+ while (! done) {
+ mutex()->lock();
+
+ recycler()->mutex()->lock();
+
+ done = m_userStop;
+
+ while (! done && (recycler()->empty() || m_pause)) {
+ post();
+
+ mutex()->unlock();
+
+ {
+ stat = m_pause ? OutputState::Paused : OutputState::Buffering;
+ OutputState e((OutputState::Type) stat);
+ dispatch(e);
+ }
+
+ recycler()->cond()->wakeOne();
+ recycler()->cond()->wait(recycler()->mutex());
+
+ mutex()->lock();
+ done = m_userStop;
+ status();
+ }
+
+ if (! b) {
+ b = recycler()->next();
+ if (b->rate)
+ m_rate = b->rate;
+ }
+
+ recycler()->cond()->wakeOne();
+ recycler()->mutex()->unlock();
+
+ FD_ZERO(&afd);
+ FD_SET(m_audio_fd, &afd);
+ // nice long poll timeout
+ tv.tv_sec = 5l;
+ tv.tv_usec = 0l;
+
+ if (b &&
+ (! do_select || (select(m_audio_fd + 1, 0, &afd, 0, &tv) > 0 &&
+ FD_ISSET(m_audio_fd, &afd)))) {
+ l = qMin(int(2048), int(b->nbytes - n));
+ if (l > 0) {
+ m = write(m_audio_fd, b->data + n, l);
+ n += m;
+
+ status();
+ dispatchVisual(b, m_totalWritten, m_channels, m_precision);
+ } else {
+ // force buffer change
+ n = b->nbytes;
+ m = 0;
+ }
+ }
+
+ m_totalWritten += m;
+
+ if (n == b->nbytes) {
+ recycler()->mutex()->lock();
+ recycler()->done();
+ recycler()->mutex()->unlock();
+
+ b = 0;
+ n = 0;
+ }
+
+ mutex()->unlock();
+ }
+
+ mutex()->lock();
+
+ if (! m_userStop)
+ sync();
+ resetDSP();
+
+ m_play = FALSE;
+
+ dispatch(OutputState::Stopped);
+ mutex()->unlock();
+}
+
+
+void OutputOSS::setVolume(int l, int r)
+{
+ int v, devs;
+ long cmd;
+
+ ioctl(m_mixer_fd, SOUND_MIXER_READ_DEVMASK, &devs);
+ if ((devs & SOUND_MASK_PCM) && (m_master == false))
+ cmd = SOUND_MIXER_WRITE_PCM;
+ else if ((devs & SOUND_MASK_VOLUME) && (m_master == true))
+ cmd = SOUND_MIXER_WRITE_VOLUME;
+ else
+ {
+ //close(mifd);
+ return;
+ }
+ v = (r << 8) | l;
+ ioctl(m_mixer_fd, cmd, &v);
+}
+
+void OutputOSS::volume(int *ll,int *rr)
+{
+ *ll = 0;
+ *rr = 0;
+ int cmd;
+ int v, devs;
+
+ ioctl(m_mixer_fd, SOUND_MIXER_READ_DEVMASK, &devs);
+ if ((devs & SOUND_MASK_PCM) && (m_master == 0))
+ cmd = SOUND_MIXER_READ_PCM;
+ else if ((devs & SOUND_MASK_VOLUME) && (m_master == 1))
+ cmd = SOUND_MIXER_READ_VOLUME;
+ else
+ return;
+
+ ioctl(m_mixer_fd, cmd, &v);
+ *ll = (v & 0xFF00) >> 8;
+ *rr = (v & 0x00FF);
+
+ *ll = (*ll > 100) ? 100 : *ll;
+ *rr = (*rr > 100) ? 100 : *rr;
+ *ll = (*ll < 0) ? 0 : *ll;
+ *rr = (*rr < 0) ? 0 : *rr;
+}
+
+
+void OutputOSS::checkVolume()
+{
+ long ll = 0, lr = 0, cmd;
+ int v, devs;
+
+ /*
+ * We dont show any errors if this fails, as this is called
+ * rather often
+ */
+// if (!open_mixer_device()) {
+ ioctl(m_mixer_fd, SOUND_MIXER_READ_DEVMASK, &devs);
+ if ((devs & SOUND_MASK_PCM) && (m_master == 0))
+ cmd = SOUND_MIXER_READ_PCM;
+ else if ((devs & SOUND_MASK_VOLUME) && (m_master == 1))
+ cmd = SOUND_MIXER_READ_VOLUME;
+ else
+ return;
+
+ ioctl(m_mixer_fd, cmd, &v);
+ ll = (v & 0xFF00) >> 8;
+ lr = (v & 0x00FF);
+
+ ll = (ll > 100) ? 100 : ll;
+ lr = (lr > 100) ? 100 : lr;
+ ll = (ll < 0) ? 0 : ll;
+ lr = (lr < 0) ? 0 : lr;
+ if (bl!=ll || br!=lr)
+ {
+ bl = ll;
+ br = lr;
+ dispatchVolume(ll,lr);
+ }
+// }
+}
+
+
diff --git a/src/plugins/Output/oss/outputoss.h b/src/plugins/Output/oss/outputoss.h
new file mode 100644
index 000000000..a2f260057
--- /dev/null
+++ b/src/plugins/Output/oss/outputoss.h
@@ -0,0 +1,76 @@
+/***************************************************************************
+ * Copyright (C) 2007 by Uriy Zhuravlev stalkerg@gmail.com *
+ * *
+ * Copyright (c) 2000-2001 Brad Hughes bhughes@trolltech.com *
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program; if not, write to the *
+ * Free Software Foundation, Inc., *
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
+ ***************************************************************************/
+
+
+#ifndef OUTPUTOSS_H
+#define OUTPUTOSS_H
+
+class OutputOSS;
+
+#include <output.h>
+#include <QObject>
+
+class OutputOSS : public Output
+{
+Q_OBJECT
+public:
+ OutputOSS(QObject * parent = 0);
+ virtual ~OutputOSS();
+
+ bool isInitialized() const { return m_inited; }
+ bool initialize();
+ void uninitialize();
+ void configure(long, int, int, int);
+ void stop();
+ void pause();
+ long written();
+ long latency();
+ void seek(long);
+ void setVolume(int l, int r);
+ void volume(int* l,int* r);
+ void checkVolume();
+
+private:
+ // thread run function
+ void run();
+
+ // helper functions
+ void reset();
+ void resetDSP();
+ void status();
+ void post();
+ void sync();
+ void openMixer();
+
+ QString m_audio_device, m_mixer_device;
+
+ bool m_inited, m_pause, m_play, m_userStop, m_master;
+ long m_totalWritten, m_currentSeconds, m_bps;
+ int stat;
+ int m_rate, m_frequency, m_channels, m_precision;
+
+ bool do_select;
+ int m_audio_fd, m_mixer_fd;
+ long bl, br;
+};
+
+
+#endif
diff --git a/src/plugins/Output/oss/outputossfactory.cpp b/src/plugins/Output/oss/outputossfactory.cpp
new file mode 100644
index 000000000..3fa8c07a2
--- /dev/null
+++ b/src/plugins/Output/oss/outputossfactory.cpp
@@ -0,0 +1,70 @@
+/***************************************************************************
+ * Copyright (C) 2007 by Zhuravlev Uriy *
+ * stalkerg@gmail.com *
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program; if not, write to the *
+ * Free Software Foundation, Inc., *
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
+ ***************************************************************************/
+
+#include <QtGui>
+
+#include "settingsdialog.h"
+#include "outputoss.h"
+#include "outputossfactory.h"
+
+
+const QString& OutputOSSFactory::name() const
+{
+ static QString name(tr("OSS Plugin"));
+ return name;
+}
+
+Output* OutputOSSFactory::create(QObject* parent,bool)
+{
+ return new OutputOSS(parent);
+}
+
+const OutputProperties OutputOSSFactory::properties() const
+{
+ OutputProperties properties;
+ properties.name = name();
+ properties.hasAbout = TRUE;
+ properties.hasSettings = TRUE;
+ return properties;
+}
+
+void OutputOSSFactory::showSettings(QWidget* parent)
+{
+ SettingsDialog *s = new SettingsDialog(parent);
+ s -> show();
+}
+
+void OutputOSSFactory::showAbout(QWidget *parent)
+{
+QMessageBox::about (parent, tr("About OSS Output Plugin"),
+ tr("Qmmp OSS Output Plugin")+"\n"+
+ tr("Writen by: Yuriy Zhuravlev <slalkerg@gmail.com>")+"\n"+
+ tr("Based on code by:Brad Hughes <bhughes@trolltech.com>"));
+}
+
+QTranslator *OutputOSSFactory::createTranslator(QObject *parent)
+{
+ QTranslator *translator = new QTranslator(parent);
+ QString locale = QLocale::system().name();
+ translator->load(QString(":/oss_plugin_") + locale);
+ return translator;
+}
+
+Q_EXPORT_PLUGIN(OutputOSSFactory)
diff --git a/src/plugins/Output/oss/outputossfactory.h b/src/plugins/Output/oss/outputossfactory.h
new file mode 100644
index 000000000..03166bb94
--- /dev/null
+++ b/src/plugins/Output/oss/outputossfactory.h
@@ -0,0 +1,48 @@
+/***************************************************************************
+ * Copyright (C) 2006 by Ilya Kotov *
+ * forkotov02@hotmail.ru *
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program; if not, write to the *
+ * Free Software Foundation, Inc., *
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
+ ***************************************************************************/
+#ifndef OUTPUTOSSFACTORY_H
+#define OUTPUTOSSFACTORY_H
+
+
+#include <QObject>
+#include <QString>
+#include <QIODevice>
+#include <QWidget>
+
+#include <output.h>
+#include <outputfactory.h>
+
+
+class OutputOSSFactory : public QObject,
+ OutputFactory
+{
+Q_OBJECT
+Q_INTERFACES(OutputFactory);
+
+public:
+ const QString& name() const;
+ Output* create(QObject* parent,bool);
+ void showSettings(QWidget* parent);
+ void showAbout(QWidget *parent);
+ QTranslator *createTranslator(QObject *parent);
+ const OutputProperties properties() const;
+};
+
+#endif
diff --git a/src/plugins/Output/oss/settingsdialog.cpp b/src/plugins/Output/oss/settingsdialog.cpp
new file mode 100644
index 000000000..8d75b06eb
--- /dev/null
+++ b/src/plugins/Output/oss/settingsdialog.cpp
@@ -0,0 +1,60 @@
+/***************************************************************************
+ * Copyright (C) 2007 by Zhuravlev Uriy *
+ * stalkerg@gmail.com *
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program; if not, write to the *
+ * Free Software Foundation, Inc., *
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
+ ***************************************************************************/
+#include <QSettings>
+#include <QDir>
+
+#include "settingsdialog.h"
+
+SettingsDialog::SettingsDialog ( QWidget *parent )
+ : QDialog ( parent )
+{
+ ui.setupUi ( this );
+ setAttribute ( Qt::WA_DeleteOnClose );
+ connect(ui.okButton, SIGNAL(clicked()), SLOT(writeSettings()));
+ QSettings settings(QDir::homePath()+"/.qmmp/qmmprc", QSettings::IniFormat);
+ settings.beginGroup("OSS");
+ ui.lineEdit->insert(settings.value("device","/dev/dsp").toString());
+ ui.lineEdit_2->insert(settings.value("mixer_device","/dev/mixer").toString());
+ ui.bufferSpinBox->setValue(settings.value("buffer_time",500).toInt());
+ ui.periodSpinBox->setValue(settings.value("period_time",100).toInt());
+
+ settings.endGroup();
+}
+
+
+SettingsDialog::~SettingsDialog()
+{}
+
+
+
+void SettingsDialog::writeSettings()
+{
+ qDebug("SettingsDialog (OSS):: writeSettings()");
+ QSettings settings(QDir::homePath()+"/.qmmp/qmmprc", QSettings::IniFormat);
+ settings.beginGroup("OSS");
+ settings.setValue("device", ui.lineEdit->text());
+ settings.setValue("buffer_time",ui.bufferSpinBox->value());
+ settings.setValue("period_time",ui.periodSpinBox->value());
+ settings.setValue("mixer_device", ui.lineEdit_2->text());
+ settings.endGroup();
+ accept();
+}
+
+
diff --git a/src/plugins/Output/oss/settingsdialog.h b/src/plugins/Output/oss/settingsdialog.h
new file mode 100644
index 000000000..fd75c5698
--- /dev/null
+++ b/src/plugins/Output/oss/settingsdialog.h
@@ -0,0 +1,47 @@
+/***************************************************************************
+ * Copyright (C) 2007 by Zhuravlev Uriy *
+ * stalkerg@gmail.com *
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program; if not, write to the *
+ * Free Software Foundation, Inc., *
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
+ ***************************************************************************/
+#ifndef SETTINGSDIALOG_H
+#define SETTINGSDIALOG_H
+
+#include <QDialog>
+
+
+#include "ui_settingsdialog.h"
+
+/**
+ @author Yuriy Zhuravlev <stalkerg@gmail.com>
+*/
+class SettingsDialog : public QDialog
+{
+Q_OBJECT
+public:
+ SettingsDialog(QWidget *parent = 0);
+
+ ~SettingsDialog();
+
+private slots:
+ void writeSettings();
+
+private:
+ Ui::SettingsDialog ui;
+
+};
+
+#endif
diff --git a/src/plugins/Output/oss/settingsdialog.ui b/src/plugins/Output/oss/settingsdialog.ui
new file mode 100644
index 000000000..ce1c40894
--- /dev/null
+++ b/src/plugins/Output/oss/settingsdialog.ui
@@ -0,0 +1,309 @@
+<ui version="4.0" >
+ <class>SettingsDialog</class>
+ <widget class="QDialog" name="SettingsDialog" >
+ <property name="geometry" >
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>422</width>
+ <height>334</height>
+ </rect>
+ </property>
+ <property name="windowTitle" >
+ <string>OSS Plugin Settings</string>
+ </property>
+ <layout class="QGridLayout" >
+ <property name="leftMargin" >
+ <number>9</number>
+ </property>
+ <property name="topMargin" >
+ <number>9</number>
+ </property>
+ <property name="rightMargin" >
+ <number>9</number>
+ </property>
+ <property name="bottomMargin" >
+ <number>9</number>
+ </property>
+ <property name="horizontalSpacing" >
+ <number>6</number>
+ </property>
+ <property name="verticalSpacing" >
+ <number>6</number>
+ </property>
+ <item row="0" column="0" colspan="3" >
+ <widget class="QTabWidget" name="tabWidget" >
+ <property name="currentIndex" >
+ <number>0</number>
+ </property>
+ <widget class="QWidget" name="tab" >
+ <attribute name="title" >
+ <string>Device Settings</string>
+ </attribute>
+ <layout class="QVBoxLayout" >
+ <property name="spacing" >
+ <number>6</number>
+ </property>
+ <property name="leftMargin" >
+ <number>9</number>
+ </property>
+ <property name="topMargin" >
+ <number>9</number>
+ </property>
+ <property name="rightMargin" >
+ <number>9</number>
+ </property>
+ <property name="bottomMargin" >
+ <number>9</number>
+ </property>
+ <item>
+ <widget class="QGroupBox" name="groupBox" >
+ <property name="title" >
+ <string>Audio device</string>
+ </property>
+ <layout class="QVBoxLayout" >
+ <item>
+ <widget class="QLineEdit" name="lineEdit" >
+ <property name="text" >
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="groupBox_2" >
+ <property name="title" >
+ <string>Mixer device</string>
+ </property>
+ <layout class="QGridLayout" >
+ <property name="leftMargin" >
+ <number>9</number>
+ </property>
+ <property name="topMargin" >
+ <number>9</number>
+ </property>
+ <property name="rightMargin" >
+ <number>9</number>
+ </property>
+ <property name="bottomMargin" >
+ <number>9</number>
+ </property>
+ <property name="horizontalSpacing" >
+ <number>6</number>
+ </property>
+ <property name="verticalSpacing" >
+ <number>6</number>
+ </property>
+ <item row="0" column="0" >
+ <widget class="QLineEdit" name="lineEdit_2" >
+ <property name="text" >
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="tab_2" >
+ <attribute name="title" >
+ <string>Advanced Settings</string>
+ </attribute>
+ <layout class="QVBoxLayout" >
+ <property name="spacing" >
+ <number>6</number>
+ </property>
+ <property name="leftMargin" >
+ <number>9</number>
+ </property>
+ <property name="topMargin" >
+ <number>9</number>
+ </property>
+ <property name="rightMargin" >
+ <number>9</number>
+ </property>
+ <property name="bottomMargin" >
+ <number>9</number>
+ </property>
+ <item>
+ <widget class="QGroupBox" name="groupBox_3" >
+ <property name="title" >
+ <string>Soundcard</string>
+ </property>
+ <layout class="QGridLayout" >
+ <property name="leftMargin" >
+ <number>9</number>
+ </property>
+ <property name="topMargin" >
+ <number>9</number>
+ </property>
+ <property name="rightMargin" >
+ <number>9</number>
+ </property>
+ <property name="bottomMargin" >
+ <number>9</number>
+ </property>
+ <property name="horizontalSpacing" >
+ <number>6</number>
+ </property>
+ <property name="verticalSpacing" >
+ <number>6</number>
+ </property>
+ <item row="3" column="1" >
+ <spacer>
+ <property name="orientation" >
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" >
+ <size>
+ <width>20</width>
+ <height>111</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="0" column="2" >
+ <spacer>
+ <property name="orientation" >
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" >
+ <size>
+ <width>188</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="1" column="1" >
+ <widget class="QSpinBox" name="periodSpinBox" >
+ <property name="minimum" >
+ <number>20</number>
+ </property>
+ <property name="maximum" >
+ <number>5000</number>
+ </property>
+ <property name="value" >
+ <number>100</number>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1" >
+ <widget class="QSpinBox" name="bufferSpinBox" >
+ <property name="minimum" >
+ <number>200</number>
+ </property>
+ <property name="maximum" >
+ <number>10000</number>
+ </property>
+ <property name="value" >
+ <number>500</number>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0" >
+ <widget class="QLabel" name="label" >
+ <property name="text" >
+ <string>Buffer time (ms):</string>
+ </property>
+ <property name="alignment" >
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0" >
+ <widget class="QLabel" name="label_2" >
+ <property name="text" >
+ <string>Period time (ms):</string>
+ </property>
+ <property name="alignment" >
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="2" >
+ <spacer>
+ <property name="orientation" >
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" >
+ <size>
+ <width>188</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="2" column="1" >
+ <widget class="QCheckBox" name="checkBox" >
+ <property name="text" >
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0" >
+ <widget class="QLabel" name="label_3" >
+ <property name="text" >
+ <string>PCM over Master</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ </item>
+ <item row="1" column="0" >
+ <spacer>
+ <property name="orientation" >
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" >
+ <size>
+ <width>191</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="1" column="2" >
+ <widget class="QPushButton" name="cancelButton" >
+ <property name="text" >
+ <string>Cancel</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1" >
+ <widget class="QPushButton" name="okButton" >
+ <property name="text" >
+ <string>OK</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>cancelButton</sender>
+ <signal>clicked()</signal>
+ <receiver>SettingsDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>338</x>
+ <y>283</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>164</x>
+ <y>294</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/src/plugins/Output/oss/translations/oss_plugin_cs.ts b/src/plugins/Output/oss/translations/oss_plugin_cs.ts
new file mode 100644
index 000000000..424d320e7
--- /dev/null
+++ b/src/plugins/Output/oss/translations/oss_plugin_cs.ts
@@ -0,0 +1,90 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS><TS version="1.1" language="pl">
+<defaultcodec></defaultcodec>
+<context>
+ <name>OutputOSSFactory</name>
+ <message>
+ <location filename="../outputossfactory.cpp" line="30"/>
+ <source>OSS Plugin</source>
+ <translation>Plugin OSS</translation>
+ </message>
+ <message>
+ <location filename="../outputossfactory.cpp" line="47"/>
+ <source>About OSS Output Plugin</source>
+ <translation>O pluginu OSS</translation>
+ </message>
+ <message>
+ <location filename="../outputossfactory.cpp" line="48"/>
+ <source>Qmmp OSS Output Plugin</source>
+ <translation>Výstupní plugin Qmmp OSS</translation>
+ </message>
+ <message>
+ <location filename="../outputossfactory.cpp" line="49"/>
+ <source>Writen by: Yuriy Zhuravlev &lt;slalkerg@gmail.com&gt;</source>
+ <translation>Autor: Jurij Žuravljov &lt;slalkerg@gmail.com&gt;</translation>
+ </message>
+ <message>
+ <location filename="../outputossfactory.cpp" line="50"/>
+ <source>Based on code by:Brad Hughes &lt;bhughes@trolltech.com&gt;</source>
+ <translation>Založeno na kódu Brada Hughese &lt;bhughes@trolltech.com&gt;</translation>
+ </message>
+</context>
+<context>
+ <name>SettingsDialog</name>
+ <message>
+ <location filename="../ui_settingsdialog.h" line="202"/>
+ <source>OSS Plugin Settings</source>
+ <translation>Nastavení pluginu OSS</translation>
+ </message>
+ <message>
+ <location filename="../ui_settingsdialog.h" line="207"/>
+ <source>Device Settings</source>
+ <translation>Nastavení zařízení</translation>
+ </message>
+ <message>
+ <location filename="../ui_settingsdialog.h" line="203"/>
+ <source>Audio device</source>
+ <translation>Zvukové zařízení</translation>
+ </message>
+ <message>
+ <location filename="../ui_settingsdialog.h" line="205"/>
+ <source>Mixer device</source>
+ <translation>Ovládání hlasitosti</translation>
+ </message>
+ <message>
+ <location filename="../ui_settingsdialog.h" line="213"/>
+ <source>Advanced Settings</source>
+ <translation>Pokročilá nastavení</translation>
+ </message>
+ <message>
+ <location filename="../ui_settingsdialog.h" line="208"/>
+ <source>Soundcard</source>
+ <translation>Zvuková karta</translation>
+ </message>
+ <message>
+ <location filename="../ui_settingsdialog.h" line="209"/>
+ <source>Buffer time (ms):</source>
+ <translation>Velikost bufferu (ms):</translation>
+ </message>
+ <message>
+ <location filename="../ui_settingsdialog.h" line="210"/>
+ <source>Period time (ms):</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../ui_settingsdialog.h" line="212"/>
+ <source>PCM over Master</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../ui_settingsdialog.h" line="214"/>
+ <source>Cancel</source>
+ <translation>Zrušit</translation>
+ </message>
+ <message>
+ <location filename="../ui_settingsdialog.h" line="215"/>
+ <source>OK</source>
+ <translation>OK</translation>
+ </message>
+</context>
+</TS>