📄 sound_oss.cxx
字号:
dsp.SetAt(0, devname);
}
// Look for dspN entries. Insert at position N + 1
// and look for dspN.M entries. Insert at position N + 1 (ignoring M)
if ((filename.GetLength() > 3) && (filename.Left(3) == "dsp")) {
PString numbers = filename.Mid(3); // get everything after 'dsp'
if (IsNumericString(numbers)) {
PINDEX cardnum = numbers.AsInteger(); //dspN.M is truncated to dspN.
// If we have not yet inserted something for this cardnum, insert it
if (dsp.GetAt(cardnum+1) == NULL) {
dsp.SetAt(cardnum+1, devname);
}
}
}
#else
// Look for audio on NetBSD
if (filename == "audio") {
dsp.SetAt(0, devname);
}
// Look for audioN. Insert at position cardnum + 1
if ((filename.GetLength() > 5) && (filename.Left(5) == "audio")) {
PString numbers = filename.Mid(5); // get everything after 'audio'
if (IsNumericString(numbers)) {
PINDEX cardnum = numbers.AsInteger();
dsp.SetAt(cardnum+1, devname);
}
}
#endif
// Look for mixer
if (filename == "mixer") {
mixer.SetAt(0, devname);
}
// Look for mixerN. Insert at position cardnum + 1
if ((filename.GetLength() > 5) && (filename.Left(5) == "mixer")) {
PString numbers = filename.Mid(5); // get everything after 'mixer'
if (IsNumericString(numbers)) {
PINDEX cardnum = numbers.AsInteger();
mixer.SetAt(cardnum+1, devname);
}
}
}
}
} while (devdir.Next());
}
PStringArray PSoundChannelOSS::GetDeviceNames(Directions /*dir*/)
{
// First locate sound cards. On Linux with devfs and on the other platforms
// (eg FreeBSD), we search for filenames with dspN or mixerN.
// On linux without devfs we scan all of the devices and look for ones
// with major device numbers corresponding to OSS compatible drivers.
POrdinalToString dsp, mixer;
#ifdef P_LINUX
PDirectory devdir = "/dev/sound";
if (devdir.Open()) {
CollectSoundDevices("/dev/sound", dsp, mixer, TRUE); // use names (devfs)
} else {
CollectSoundDevices("/dev", dsp, mixer, FALSE); // use major numbers
}
#else
CollectSoundDevices("/dev", dsp, mixer, TRUE); // use names
#endif
// Now we go through the collected devices and see if any have a phyisical reality
PStringList devices;
for (PINDEX i = 0; i < dsp.GetSize(); i++) {
PINDEX cardnum = dsp.GetKeyAt(i);
// Try and open mixer if have one as this is unlikely to fail for any
// reason other than there not being a physical device
if (mixer.Contains(cardnum)) {
int fd = ::open(mixer[cardnum], O_RDONLY);
if (fd >= 0) {
// Do something with the mixer to be sure it is there
int dummy;
if (::ioctl(fd, SOUND_MIXER_READ_DEVMASK, &dummy) >= 0)
devices.AppendString(dsp[cardnum]);
::close(fd);
}
else {
// mixer failed but this could still be a valid dsp...
// warning this is just a hack to make this work on strange mixer and dsp configurations
// on my machine the first sound card registers 1 mixer and 2 dsp, so when my webcam
// registers itself as dsp2 this test would fail...
int fd = ::open(dsp[cardnum], O_RDONLY | O_NONBLOCK);
if (fd >= 0 || errno == EBUSY) {
devices.AppendString(dsp[cardnum]);
::close(fd);
}
}
}
else {
// No mixer available, try and open it directly, this could fail if
// the device happens to be open already
int fd = ::open(dsp[cardnum], O_RDONLY | O_NONBLOCK);
if (fd >= 0 || errno == EBUSY) {
devices.AppendString(dsp[cardnum]);
::close(fd);
}
}
}
return devices;
}
PString PSoundChannelOSS::GetDefaultDevice(Directions dir)
{
// Normally /dev/dsp points to the default sound device. If this is not
// present, probe /dev for sound devices and return the first detected device.
// return the first dsp device detected
PStringArray devicenames;
devicenames = PSoundChannelOSS::GetDeviceNames(dir);
return devicenames[0];
}
BOOL PSoundChannelOSS::Open(const PString & _device,
Directions _dir,
unsigned _numChannels,
unsigned _sampleRate,
unsigned _bitsPerSample)
{
Close();
// lock the dictionary
PWaitAndSignal mutex(dictMutex);
// make the direction value 1 or 2
int dir = _dir + 1;
// if this device is in the dictionary
if (handleDict().Contains(_device)) {
SoundHandleEntry & entry = handleDict()[_device];
// see if the sound channel is already open in this direction
if ((entry.direction & dir) != 0) {
return FALSE;
}
// flag this entry as open in this direction
entry.direction |= dir;
os_handle = entry.handle;
} else {
// this is the first time this device has been used
// open the device in read/write mode always
// open the device in non-blocking mode to avoid hang if already open
os_handle = ::open((const char *)_device, O_RDWR | O_NONBLOCK);
if ((os_handle < 0) && (errno != EWOULDBLOCK))
return ConvertOSError(os_handle);
// switch to blocking mode
DWORD cmd = 0;
::ioctl(os_handle, FIONBIO, &cmd);
// add the device to the dictionary
SoundHandleEntry * entry = PNEW SoundHandleEntry;
handleDict().SetAt(_device, entry);
// save the information into the dictionary entry
entry->handle = os_handle;
entry->direction = dir;
entry->numChannels = mNumChannels = _numChannels;
entry->sampleRate = actualSampleRate = mSampleRate = _sampleRate;
entry->bitsPerSample = mBitsPerSample = _bitsPerSample;
entry->isInitialised = FALSE;
entry->fragmentValue = 0x7fff0008;
entry->resampleRate = 0;
}
// save the direction and device
direction = _dir;
device = _device;
isInitialised = FALSE;
return TRUE;
}
BOOL PSoundChannelOSS::Setup()
{
PWaitAndSignal mutex(dictMutex);
if (os_handle < 0) {
PTRACE(6, "OSS\tSkipping setup of " << device << " as not open");
return FALSE;
}
if (isInitialised) {
PTRACE(6, "OSS\tSkipping setup of " << device << " as instance already initialised");
return TRUE;
}
// the device must always be in the dictionary
PAssertOS(handleDict().Contains(device));
// get record for the device
SoundHandleEntry & entry = handleDict()[device];
// set default return status
BOOL stat = TRUE;
// do not re-initialise initialised devices
if (entry.isInitialised) {
PTRACE(6, "OSS\tSkipping setup for " << device << " as already initialised");
resampleRate = entry.resampleRate;
} else {
PTRACE(6, "OSS\tInitialising " << device << "(" << (void *)(&entry) << ")");
#if defined(P_LINUX)
// enable full duplex (maybe).
::ioctl(os_handle, SNDCTL_DSP_SETDUPLEX, 0);
#endif
stat = FALSE;
// must always set paramaters in the following order:
// buffer paramaters
// sample format (number of bits)
// number of channels (mon/stereo)
// speed (sampling rate)
int arg, val;
// reset the device first so it will accept the new parms
if (ConvertOSError(::ioctl(os_handle, SNDCTL_DSP_RESET, &arg))) {
// set the write fragment size (applies to sound output only)
arg = val = entry.fragmentValue;
::ioctl(os_handle, SNDCTL_DSP_SETFRAGMENT, &arg);
mBitsPerSample = entry.bitsPerSample;
#if PBYTE_ORDER == PLITTLE_ENDIAN
arg = val = (entry.bitsPerSample == 16) ? AFMT_S16_LE : AFMT_S8;
#else
arg = val = (entry.bitsPerSample == 16) ? AFMT_S16_BE : AFMT_S8;
#endif
if (ConvertOSError(::ioctl(os_handle, SNDCTL_DSP_SETFMT, &arg)) || (arg != val)) {
mNumChannels = entry.numChannels;
arg = val = (entry.numChannels == 2) ? 1 : 0;
if (ConvertOSError(::ioctl(os_handle, SNDCTL_DSP_STEREO, &arg)) || (arg != val)) {
mSampleRate = entry.sampleRate;
arg = val = entry.sampleRate;
if (ConvertOSError(::ioctl(os_handle, SNDCTL_DSP_SPEED, &arg))) {
stat = TRUE;
// detect cases where the hardware can't do the actual rate we need, but can do a simple multiple
if (arg != (int)entry.sampleRate) {
if (((arg / entry.sampleRate) * entry.sampleRate) == (unsigned)arg) {
PTRACE(3, "Resampling data at " << entry.sampleRate << " to match hardware rate of " << arg);
resampleRate = entry.resampleRate = arg / entry.sampleRate;
} else {
PTRACE_IF(4, actualSampleRate != (unsigned)val, "Actual sample rate selected is " << actualSampleRate << ", not " << entry.sampleRate);
actualSampleRate = arg;
}
}
}
}
}
#if PTRACING
audio_buf_info info;
::ioctl(os_handle, SNDCTL_DSP_GETOSPACE, &info);
PTRACE(4, "OSS\tOutput: fragments = " << info.fragments
<< ", total frags = " << info.fragstotal
<< ", frag size = " << info.fragsize
<< ", bytes = " << info.bytes);
::ioctl(os_handle, SNDCTL_DSP_GETISPACE, &info);
PTRACE(4, "OSS\tInput: fragments = " << info.fragments
<< ", total frags = " << info.fragstotal
<< ", frag size = " << info.fragsize
<< ", bytes = " << info.bytes);
#endif
}
}
// ensure device is marked as initialised
isInitialised = TRUE;
entry.isInitialised = TRUE;
return stat;
}
BOOL PSoundChannelOSS::Close()
{
// if the channel isn't open, do nothing
if (os_handle < 0)
return TRUE;
// the device must be in the dictionary
dictMutex.Wait();
SoundHandleEntry * entry;
PAssert((entry = handleDict().GetAt(device)) != NULL, "Unknown sound device \"" + device + "\" found");
// modify the directions bit mask in the dictionary
entry->direction ^= (direction+1);
// if this is the last usage of this entry, then remove it
if (entry->direction == 0) {
handleDict().RemoveAt(device);
dictMutex.Signal();
return PChannel::Close();
}
// flag this channel as closed
dictMutex.Signal();
os_handle = -1;
return TRUE;
}
BOOL PSoundChannelOSS::IsOpen() const
{
return os_handle >= 0;
}
BOOL PSoundChannelOSS::Write(const void * buf, PINDEX len)
{
lastWriteCount = 0;
if (!Setup() || os_handle < 0)
return FALSE;
if (resampleRate == 0) {
while (!ConvertOSError(::write(os_handle, (void *)buf, len)))
if (GetErrorCode() != Interrupted)
return FALSE;
lastWriteCount += len;
}
else {
// cut the data into 1K blocks and upsample it
lastWriteCount = 0;
BYTE resampleBuffer[1024];
const BYTE * src = (const BYTE *)buf;
const BYTE * srcEnd = src + len;
while (src < srcEnd) {
// expand the data by the appropriate sample ratio
BYTE * dst = resampleBuffer;
const BYTE * srcStart = src;
unsigned j;
while ((src < srcEnd) && (dst < (resampleBuffer + sizeof(resampleBuffer) - resampleRate*2))) {
for (j = 0; j < resampleRate; ++j) {
memcpy(dst, src, 2);
dst += 2 ;
}
src += 2;
}
lastWriteCount += src - srcStart;
while (!ConvertOSError(::write(os_handle, resampleBuffer, dst - resampleBuffer))) {
if (GetErrorCode() != Interrupted)
return FALSE;
}
}
}
return TRUE;
}
BOOL PSoundChannelOSS::Read(void * buf, PINDEX len)
{
lastReadCount = 0;
if (!Setup() || os_handle < 0)
return FALSE;
if (resampleRate == 0) {
PINDEX total = 0;
while (total < len) {
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -