summaryrefslogtreecommitdiffstats
path: root/kmix/mixer_oss.cpp
blob: 6991c1aa40c56e93fbf987d865684a2d5f6f330a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
/*
 *              KMix -- KDE's full featured mini mixer
 *
 *              Copyright (C) 1996-2000 Christian Esken
 *                        esken@kde.org
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library 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 <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>

// Since we're guaranteed an OSS setup here, let's make life easier
#if !defined(__NetBSD__) && !defined(__OpenBSD__)
	#include <sys/soundcard.h>
#else
	#include <soundcard.h>
#endif

#include "mixer_oss.h"
#include <klocale.h>

/*
  I am using a fixed MAX_MIXDEVS #define here.
   People might argue, that I should rather use the SOUND_MIXER_NRDEVICES
   #define used by OSS. But using this #define is not good, because it is
   evaluated during compile time. Compiling on one platform and running
   on another with another version of OSS with a different value of
   SOUND_MIXER_NRDEVICES is very bad. Because of this, usage of
   SOUND_MIXER_NRDEVICES should be discouraged.

   The #define below is only there for internal reasons.
   In other words: Don't play around with this value
 */
#define MAX_MIXDEVS 32

const char* MixerDevNames[32]={
    I18N_NOOP("Volume"), I18N_NOOP("Bass"), I18N_NOOP("Treble"),
    I18N_NOOP("Synth"), I18N_NOOP("Pcm"), I18N_NOOP("Speaker"),
    I18N_NOOP("Line"), I18N_NOOP("Microphone"), I18N_NOOP("CD"),
    I18N_NOOP("Mix"), I18N_NOOP("Pcm2"), I18N_NOOP("RecMon"),
    I18N_NOOP("IGain"), I18N_NOOP("OGain"), I18N_NOOP("Line1"),
    I18N_NOOP("Line2"), I18N_NOOP("Line3"), I18N_NOOP("Digital1"),
    I18N_NOOP("Digital2"), I18N_NOOP("Digital3"), I18N_NOOP("PhoneIn"),
    I18N_NOOP("PhoneOut"), I18N_NOOP("Video"), I18N_NOOP("Radio"),
    I18N_NOOP("Monitor"), I18N_NOOP("3D-depth"), I18N_NOOP("3D-center"),
    I18N_NOOP("unknown"), I18N_NOOP("unknown"), I18N_NOOP("unknown"),
    I18N_NOOP("unknown") , I18N_NOOP("unused") };

const MixDevice::ChannelType MixerChannelTypes[32] = {
  MixDevice::VOLUME,   MixDevice::BASS,       MixDevice::TREBLE,   MixDevice::MIDI,
  MixDevice::AUDIO,    MixDevice::EXTERNAL,   MixDevice::EXTERNAL, MixDevice::MICROPHONE,
  MixDevice::CD,       MixDevice::VOLUME,     MixDevice::AUDIO,    MixDevice::RECMONITOR,
  MixDevice::VOLUME,   MixDevice::RECMONITOR, MixDevice::EXTERNAL, MixDevice::EXTERNAL,
  MixDevice::EXTERNAL, MixDevice::AUDIO,      MixDevice::AUDIO,    MixDevice::AUDIO,
  MixDevice::EXTERNAL, MixDevice::EXTERNAL,   MixDevice::EXTERNAL, MixDevice::EXTERNAL,
  MixDevice::EXTERNAL, MixDevice::VOLUME,     MixDevice::VOLUME,   MixDevice::UNKNOWN,
  MixDevice::UNKNOWN,  MixDevice::UNKNOWN,    MixDevice::UNKNOWN,  MixDevice::UNKNOWN };

Mixer_Backend* OSS_getMixer( int device )
{
  Mixer_Backend *l_mixer;
  l_mixer = new Mixer_OSS( device );
  return l_mixer;
}

Mixer_OSS::Mixer_OSS(int device) : Mixer_Backend(device)
{
  if( device == -1 ) m_devnum = 0;
}

Mixer_OSS::~Mixer_OSS()
{
  close();
}

int Mixer_OSS::open()
{
  if ((m_fd= ::open( deviceName( m_devnum ).latin1(), O_RDWR)) < 0)
    {
      if ( errno == EACCES )
        return Mixer::ERR_PERM;
      else {
		  if ((m_fd= ::open( deviceNameDevfs( m_devnum ).latin1(),
						  O_RDWR)) < 0)
		    {
      			if ( errno == EACCES )
        			return Mixer::ERR_PERM;
				else
					return Mixer::ERR_OPEN;
		    }
	  }
    }

      int devmask, recmask, i_recsrc, stereodevs;
      // Mixer is open. Now define properties
      if (ioctl(m_fd, SOUND_MIXER_READ_DEVMASK, &devmask) == -1)
        return Mixer::ERR_READ;
      if (ioctl(m_fd, SOUND_MIXER_READ_RECMASK, &recmask) == -1)
        return Mixer::ERR_READ;
      if (ioctl(m_fd, SOUND_MIXER_READ_RECSRC, &i_recsrc) == -1)
        return Mixer::ERR_READ;
      if (ioctl(m_fd, SOUND_MIXER_READ_STEREODEVS, &stereodevs) == -1)
        return Mixer::ERR_READ;
      if (!devmask)
        return Mixer::ERR_NODEV;
      int maxVolume =100;

      if( m_mixDevices.isEmpty() )
        {
          int idx = 0;
          while( devmask && idx < MAX_MIXDEVS )
            {
              if( devmask & ( 1 << idx ) ) // device active?
                {
                  Volume vol( stereodevs & ( 1 << idx ) ? 2 : 1, maxVolume);
                  readVolumeFromHW( idx, vol );
                  MixDevice* md =
                    new MixDevice( idx, vol, recmask & ( 1 << idx ), true,
                                   i18n(MixerDevNames[idx]),
                                   MixerChannelTypes[idx]);
                  md->setRecSource( isRecsrcHW( idx ) );
                  m_mixDevices.append( md );
                }
              idx++;
            }
        }
      else
        for( unsigned int idx = 0; idx < m_mixDevices.count(); idx++ )
          {
            MixDevice* md = m_mixDevices.at( idx );
            if( !md )
              return Mixer::ERR_INCOMPATIBLESET;
            writeVolumeToHW( idx, md->getVolume() );
          }

#if !defined(__FreeBSD__)
      struct mixer_info l_mix_info;
      if (ioctl(m_fd, SOUND_MIXER_INFO, &l_mix_info) != -1)
        {
          m_mixerName = l_mix_info.name;
        }
      else
#endif

        m_mixerName = "OSS Audio Mixer";

      m_isOpen = true;
      return 0;
}

int Mixer_OSS::close()
{
  m_isOpen = false;
  int l_i_ret = ::close(m_fd);
  m_mixDevices.clear();
  return l_i_ret;
}


QString Mixer_OSS::deviceName(int devnum)
{
  switch (devnum) {
  case 0:
    return QString("/dev/mixer");
    break;

  default:
    QString devname("/dev/mixer");
    devname += ('0'+devnum);
    return devname;
  }
}

QString Mixer_OSS::deviceNameDevfs(int devnum)
{
  switch (devnum) {
  case 0:
    return QString("/dev/sound/mixer");
    break;

  default:
    QString devname("/dev/sound/mixer");
    devname += ('0'+devnum);
    return devname;
  }
}

QString Mixer_OSS::errorText(int mixer_error)
{
  QString l_s_errmsg;
  switch (mixer_error)
    {
    case Mixer::ERR_PERM:
      l_s_errmsg = i18n("kmix: You do not have permission to access the mixer device.\n" \
                        "Login as root and do a 'chmod a+rw /dev/mixer*' to allow the access.");
      break;
    case Mixer::ERR_OPEN:
      l_s_errmsg = i18n("kmix: Mixer cannot be found.\n" \
                        "Please check that the soundcard is installed and the\n" \
                        "soundcard driver is loaded.\n" \
                        "On Linux you might need to use 'insmod' to load the driver.\n" \
                        "Use 'soundon' when using commercial OSS.");
      break;
    default:
      l_s_errmsg = Mixer_Backend::errorText(mixer_error);
    }
  return l_s_errmsg;
}


bool Mixer_OSS::setRecsrcHW( int devnum, bool on )
{
  int i_recsrc, oldrecsrc;
  if (ioctl(m_fd, SOUND_MIXER_READ_RECSRC, &i_recsrc) == -1)
    errormsg(Mixer::ERR_READ);

  oldrecsrc = i_recsrc = on ?
             (i_recsrc | (1 << devnum )) :
             (i_recsrc & ~(1 << devnum ));

  // Change status of record source(s)
  if (ioctl(m_fd, SOUND_MIXER_WRITE_RECSRC, &i_recsrc) == -1)
    errormsg (Mixer::ERR_WRITE);
  // Re-read status of record source(s). Just in case, OSS does not like
  // my settings. And with this line mix->recsrc gets its new value. :-)
  if (ioctl(m_fd, SOUND_MIXER_READ_RECSRC, &i_recsrc) == -1)
    errormsg(Mixer::ERR_READ);

  /* The following if {} patch was submitted by Tim McCormick <tim@pcbsd.org>. */
  /*   Comment (cesken): This patch fixes an issue with mutual exclusive recording sources.
       Actually the kernel soundcard driver *could* "do the right thing" by examining the change
       (old-recsrc XOR new-recsrc), and knowing which sources are mutual exclusive.
       The OSS v3 API docs indicate that the behaviour is undefined for this case, and it is not
       clearly documented how and whether SOUND_MIXER_CAP_EXCL_INPUT is evaluated in the OSS driver.
       Evaluating that in the application (KMix) could help, but the patch will work independent
       on whether SOUND_MIXER_CAP_EXCL_INPUT ist set or not.

       In any case this patch is a superb workaround for a shortcoming of the OSS v3 API.
   */
  // If the record source is supposed to be on, but wasn't set, explicitly
  // set the record source. Not all cards support multiple record sources.
  // As a result, we also need to do the read & write again.
  if (((i_recsrc & ( 1<<devnum)) == 0) && on)
  {
     // Setting the new device failed => Try to enable it *exclusively*
     oldrecsrc = i_recsrc = 1 << devnum;
     if (ioctl(m_fd, SOUND_MIXER_WRITE_RECSRC, &i_recsrc) == -1)
       errormsg (Mixer::ERR_WRITE);
     if (ioctl(m_fd, SOUND_MIXER_READ_RECSRC, &i_recsrc) == -1)
       errormsg(Mixer::ERR_READ);
  }

  // PORTING: Hint: Do not forget to set i_recsrc to the new valid
  //                record source mask.

  return i_recsrc == oldrecsrc;
}

bool Mixer_OSS::isRecsrcHW( int devnum )
{
	bool isRecsrc = false;
	int recsrcMask;
	if (ioctl(m_fd, SOUND_MIXER_READ_RECSRC, &recsrcMask) == -1)
		errormsg(Mixer::ERR_READ);
	else {
		// test if device bit is set in record bit mask
		isRecsrc =  ( (recsrcMask & ( 1<<devnum)) != 0 );
	}
	return isRecsrc;
}

int Mixer_OSS::readVolumeFromHW( int devnum, Volume &vol )
{
  if( vol.isMuted() ) return 0; // Don't alter volume when muted

  int volume;
  if (ioctl(m_fd, MIXER_READ( devnum ), &volume) == -1)
    {
      /* Oops, can't read mixer */
      return(Mixer::ERR_READ);
    }
  else
    {
      vol.setVolume( Volume::LEFT, (volume & 0x7f));
      if( vol.count() > 1 )
        vol.setVolume( Volume::RIGHT, ((volume>>8) & 0x7f));
      //fprintf(stderr, "Mixer_OSS::readVolumeFromHW(%i,vol) set vol %i %i\n", devnum,  vol.getVolume(Volume::LEFT), vol.getVolume(Volume::RIGHT));
	return 0;
    }
}



int Mixer_OSS::writeVolumeToHW( int devnum, Volume &vol )
{
  int volume;
  if( vol.isMuted() ) volume = 0;
  else
    if ( vol.count() > 1 )
      volume = (vol[ Volume::LEFT ]) + ((vol[ Volume::RIGHT ])<<8);
    else
      volume = vol[ Volume::LEFT ];

  if (ioctl(m_fd, MIXER_WRITE( devnum ), &volume) == -1)
    return Mixer::ERR_WRITE;

  return 0;
}

QString OSS_getDriverName() {
	return "OSS";
}