/** * @author Gary P. SCAVONE * * @copyright 2001-2013 Gary P. Scavone, all right reserved * * @license like MIT (see license file) */ #if defined(__LINUX_ALSA__) #include #include #include #include #include airtaudio::Api* airtaudio::api::Alsa::Create(void) { return new airtaudio::api::Alsa(); } // A structure to hold various information related to the ALSA API // implementation. struct AlsaHandle { snd_pcm_t *handles[2]; bool synchronized; bool xrun[2]; std::condition_variable runnable_cv; bool runnable; AlsaHandle(void) : synchronized(false), runnable(false) { xrun[0] = false; xrun[1] = false; } }; static void alsaCallbackHandler(void * _ptr); airtaudio::api::Alsa::Alsa(void) { // Nothing to do here. } airtaudio::api::Alsa::~Alsa(void) { if (m_stream.state != STREAM_CLOSED) { closeStream(); } } uint32_t airtaudio::api::Alsa::getDeviceCount(void) { unsigned nDevices = 0; int32_t result, subdevice, card; char name[64]; snd_ctl_t *handle; // Count cards and devices card = -1; snd_card_next(&card); while (card >= 0) { sprintf(name, "hw:%d", card); result = snd_ctl_open(&handle, name, 0); if (result < 0) { ATA_ERROR("airtaudio::api::Alsa::getDeviceCount: control open, card = " << card << ", " << snd_strerror(result) << "."); // TODO : Return error airtaudio::errorWarning; goto nextcard; } subdevice = -1; while(1) { result = snd_ctl_pcm_next_device(handle, &subdevice); if (result < 0) { ATA_ERROR("airtaudio::api::Alsa::getDeviceCount: control next device, card = " << card << ", " << snd_strerror(result) << "."); // TODO : Return error airtaudio::errorWarning; break; } if (subdevice < 0) { break; } nDevices++; } nextcard: snd_ctl_close(handle); snd_card_next(&card); } result = snd_ctl_open(&handle, "default", 0); if (result == 0) { nDevices++; snd_ctl_close(handle); } return nDevices; } airtaudio::DeviceInfo airtaudio::api::Alsa::getDeviceInfo(uint32_t _device) { airtaudio::DeviceInfo info; info.probed = false; unsigned nDevices = 0; int32_t result, subdevice, card; char name[64]; snd_ctl_t *chandle; // Count cards and devices card = -1; snd_card_next(&card); while (card >= 0) { sprintf(name, "hw:%d", card); result = snd_ctl_open(&chandle, name, SND_CTL_NONBLOCK); if (result < 0) { ATA_WARNING("airtaudio::api::Alsa::getDeviceInfo: control open, card = " << card << ", " << snd_strerror(result) << "."); goto nextcard; } subdevice = -1; while(1) { result = snd_ctl_pcm_next_device(chandle, &subdevice); if (result < 0) { ATA_WARNING("airtaudio::api::Alsa::getDeviceInfo: control next device, card = " << card << ", " << snd_strerror(result) << "."); break; } if (subdevice < 0) { break; } if (nDevices == _device) { sprintf(name, "hw:%d,%d", card, subdevice); goto foundDevice; } nDevices++; } nextcard: snd_ctl_close(chandle); snd_card_next(&card); } result = snd_ctl_open(&chandle, "default", SND_CTL_NONBLOCK); if (result == 0) { if (nDevices == _device) { strcpy(name, "default"); goto foundDevice; } nDevices++; } if (nDevices == 0) { ATA_ERROR("airtaudio::api::Alsa::getDeviceInfo: no devices found!"); // TODO : airtaudio::errorInvalidUse; return info; } if (_device >= nDevices) { ATA_ERROR("airtaudio::api::Alsa::getDeviceInfo: device ID is invalid!"); // TODO : airtaudio::errorInvalidUse; return info; } foundDevice: // If a stream is already open, we cannot probe the stream devices. // Thus, use the saved results. if ( m_stream.state != STREAM_CLOSED && ( m_stream.device[0] == _device || m_stream.device[1] == _device)) { snd_ctl_close(chandle); if (_device >= m_devices.size()) { ATA_ERROR("airtaudio::api::Alsa::getDeviceInfo: device ID was not present before stream was opened."); // TODO : return airtaudio::errorWarning; return info; } return m_devices[ _device ]; } int32_t openMode = SND_PCM_ASYNC; snd_pcm_stream_t stream; snd_pcm_info_t *pcminfo; snd_pcm_info_alloca(&pcminfo); snd_pcm_t *phandle; snd_pcm_hw_params_t *params; snd_pcm_hw_params_alloca(¶ms); // First try for playback unless default _device (which has subdev -1) stream = SND_PCM_STREAM_PLAYBACK; snd_pcm_info_set_stream(pcminfo, stream); if (subdevice != -1) { snd_pcm_info_set_device(pcminfo, subdevice); snd_pcm_info_set_subdevice(pcminfo, 0); result = snd_ctl_pcm_info(chandle, pcminfo); if (result < 0) { // Device probably doesn't support playback. goto captureProbe; } } result = snd_pcm_open(&phandle, name, stream, openMode | SND_PCM_NONBLOCK); if (result < 0) { ATA_ERROR("airtaudio::api::Alsa::getDeviceInfo: snd_pcm_open error for device (" << name << "), " << snd_strerror(result) << "."); // TODO : Return airtaudio::errorWarning; goto captureProbe; } // The device is open ... fill the parameter structure. result = snd_pcm_hw_params_any(phandle, params); if (result < 0) { snd_pcm_close(phandle); ATA_ERROR("airtaudio::api::Alsa::getDeviceInfo: snd_pcm_hw_params error for device (" << name << "), " << snd_strerror(result) << "."); // TODO : Return airtaudio::errorWarning; goto captureProbe; } // Get output channel information. uint32_t value; result = snd_pcm_hw_params_get_channels_max(params, &value); if (result < 0) { snd_pcm_close(phandle); ATA_ERROR("airtaudio::api::Alsa::getDeviceInfo: error getting device (" << name << ") output channels, " << snd_strerror(result) << "."); // TODO : Return airtaudio::errorWarning; goto captureProbe; } info.outputChannels = value; snd_pcm_close(phandle); captureProbe: stream = SND_PCM_STREAM_CAPTURE; snd_pcm_info_set_stream(pcminfo, stream); // Now try for capture unless default device (with subdev = -1) if (subdevice != -1) { result = snd_ctl_pcm_info(chandle, pcminfo); snd_ctl_close(chandle); if (result < 0) { // Device probably doesn't support capture. if (info.outputChannels == 0) { return info; } goto probeParameters; } } result = snd_pcm_open(&phandle, name, stream, openMode | SND_PCM_NONBLOCK); if (result < 0) { ATA_ERROR("airtaudio::api::Alsa::getDeviceInfo: snd_pcm_open error for device (" << name << "), " << snd_strerror(result) << "."); // TODO : Return airtaudio::errorWarning; if (info.outputChannels == 0) { return info; } goto probeParameters; } // The device is open ... fill the parameter structure. result = snd_pcm_hw_params_any(phandle, params); if (result < 0) { snd_pcm_close(phandle); ATA_ERROR("airtaudio::api::Alsa::getDeviceInfo: snd_pcm_hw_params error for device (" << name << "), " << snd_strerror(result) << "."); // TODO : Return airtaudio::errorWarning; if (info.outputChannels == 0) { return info; } goto probeParameters; } result = snd_pcm_hw_params_get_channels_max(params, &value); if (result < 0) { snd_pcm_close(phandle); ATA_ERROR("airtaudio::api::Alsa::getDeviceInfo: error getting device (" << name << ") input channels, " << snd_strerror(result) << "."); // TODO : Return airtaudio::errorWarning; if (info.outputChannels == 0) { return info; } goto probeParameters; } info.inputChannels = value; snd_pcm_close(phandle); // If device opens for both playback and capture, we determine the channels. if (info.outputChannels > 0 && info.inputChannels > 0) { info.duplexChannels = (info.outputChannels > info.inputChannels) ? info.inputChannels : info.outputChannels; } // ALSA doesn't provide default devices so we'll use the first available one. if (_device == 0 && info.outputChannels > 0) { info.isDefaultOutput = true; } if (_device == 0 && info.inputChannels > 0) { info.isDefaultInput = true; } probeParameters: // At this point, we just need to figure out the supported data // formats and sample rates. We'll proceed by opening the device in // the direction with the maximum number of channels, or playback if // they are equal. This might limit our sample rate options, but so // be it. if (info.outputChannels >= info.inputChannels) { stream = SND_PCM_STREAM_PLAYBACK; } else { stream = SND_PCM_STREAM_CAPTURE; } snd_pcm_info_set_stream(pcminfo, stream); result = snd_pcm_open(&phandle, name, stream, openMode | SND_PCM_NONBLOCK); if (result < 0) { ATA_ERROR("airtaudio::api::Alsa::getDeviceInfo: snd_pcm_open error for device (" << name << "), " << snd_strerror(result) << "."); // TODO : Return airtaudio::errorWarning; return info; } // The device is open ... fill the parameter structure. result = snd_pcm_hw_params_any(phandle, params); if (result < 0) { snd_pcm_close(phandle); ATA_ERROR("airtaudio::api::Alsa::getDeviceInfo: snd_pcm_hw_params error for device (" << name << "), " << snd_strerror(result) << "."); // TODO : Return airtaudio::errorWarning; return info; } // Test our discrete set of sample rate values. info.sampleRates.clear(); for (uint32_t i=0; i= 0) { sprintf(name, "hw:%s,%d", cardname, subdevice); } info.name = name; // That's all ... close the device and return snd_pcm_close(phandle); info.probed = true; return info; } void airtaudio::api::Alsa::saveDeviceInfo(void) { m_devices.clear(); uint32_t nDevices = getDeviceCount(); m_devices.resize(nDevices); for (uint32_t iii=0; iiiflags & airtaudio::ALSA_USE_DEFAULT) { snprintf(name, sizeof(name), "%s", "default"); } else { // Count cards and devices card = -1; snd_card_next(&card); while (card >= 0) { sprintf(name, "hw:%d", card); result = snd_ctl_open(&chandle, name, SND_CTL_NONBLOCK); if (result < 0) { ATA_ERROR("airtaudio::api::Alsa::probeDeviceOpen: control open, card = " << card << ", " << snd_strerror(result) << "."); return false; } subdevice = -1; while(1) { result = snd_ctl_pcm_next_device(chandle, &subdevice); if (result < 0) break; if (subdevice < 0) break; if (nDevices == _device) { sprintf(name, "hw:%d,%d", card, subdevice); snd_ctl_close(chandle); goto foundDevice; } nDevices++; } snd_ctl_close(chandle); snd_card_next(&card); } result = snd_ctl_open(&chandle, "default", SND_CTL_NONBLOCK); if (result == 0) { if (nDevices == _device) { strcpy(name, "default"); goto foundDevice; } nDevices++; } if (nDevices == 0) { // This should not happen because a check is made before this function is called. ATA_ERROR("airtaudio::api::Alsa::probeDeviceOpen: no devices found!"); return false; } if (_device >= nDevices) { // This should not happen because a check is made before this function is called. ATA_ERROR("airtaudio::api::Alsa::probeDeviceOpen: device ID is invalid!"); return false; } } foundDevice: // The getDeviceInfo() function will not work for a device that is // already open. Thus, we'll probe the system before opening a // stream and save the results for use by getDeviceInfo(). if ( _mode == OUTPUT || ( _mode == INPUT && m_stream.mode != OUTPUT)) { // only do once this->saveDeviceInfo(); } snd_pcm_stream_t stream; if (_mode == OUTPUT) { stream = SND_PCM_STREAM_PLAYBACK; } else { stream = SND_PCM_STREAM_CAPTURE; } snd_pcm_t *phandle; int32_t openMode = SND_PCM_ASYNC; result = snd_pcm_open(&phandle, name, stream, openMode); if (result < 0) { if (_mode == OUTPUT) { ATA_ERROR("airtaudio::api::Alsa::probeDeviceOpen: pcm device (" << name << ") won't open for output."); } else { ATA_ERROR("airtaudio::api::Alsa::probeDeviceOpen: pcm device (" << name << ") won't open for input."); } return false; } // Fill the parameter structure. snd_pcm_hw_params_t *hw_params; snd_pcm_hw_params_alloca(&hw_params); result = snd_pcm_hw_params_any(phandle, hw_params); if (result < 0) { snd_pcm_close(phandle); ATA_ERROR("airtaudio::api::Alsa::probeDeviceOpen: error getting pcm device (" << name << ") parameters, " << snd_strerror(result) << "."); return false; } // Set access ... check user preference. if ( _options != NULL && _options->flags & airtaudio::NONINTERLEAVED) { m_stream.userInterleaved = false; result = snd_pcm_hw_params_set_access(phandle, hw_params, SND_PCM_ACCESS_RW_NONINTERLEAVED); if (result < 0) { result = snd_pcm_hw_params_set_access(phandle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED); m_stream.deviceInterleaved[_mode] = true; } else { m_stream.deviceInterleaved[_mode] = false; } } else { m_stream.userInterleaved = true; result = snd_pcm_hw_params_set_access(phandle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED); if (result < 0) { result = snd_pcm_hw_params_set_access(phandle, hw_params, SND_PCM_ACCESS_RW_NONINTERLEAVED); m_stream.deviceInterleaved[_mode] = false; } else { m_stream.deviceInterleaved[_mode] = true; } } if (result < 0) { snd_pcm_close(phandle); ATA_ERROR("airtaudio::api::Alsa::probeDeviceOpen: error setting pcm device (" << name << ") access, " << snd_strerror(result) << "."); return false; } // Determine how to set the device format. m_stream.userFormat = _format; snd_pcm_format_t deviceFormat = SND_PCM_FORMAT_UNKNOWN; if (_format == airtaudio::SINT8) { deviceFormat = SND_PCM_FORMAT_S8; } else if (_format == airtaudio::SINT16) { deviceFormat = SND_PCM_FORMAT_S16; } else if (_format == airtaudio::SINT24) { deviceFormat = SND_PCM_FORMAT_S24; } else if (_format == airtaudio::SINT32) { deviceFormat = SND_PCM_FORMAT_S32; } else if (_format == airtaudio::FLOAT32) { deviceFormat = SND_PCM_FORMAT_FLOAT; } else if (_format == airtaudio::FLOAT64) { deviceFormat = SND_PCM_FORMAT_FLOAT64; } if (snd_pcm_hw_params_test_format(phandle, hw_params, deviceFormat) == 0) { m_stream.deviceFormat[_mode] = _format; goto setFormat; } // The user requested format is not natively supported by the device. deviceFormat = SND_PCM_FORMAT_FLOAT64; if (snd_pcm_hw_params_test_format(phandle, hw_params, deviceFormat) == 0) { m_stream.deviceFormat[_mode] = airtaudio::FLOAT64; goto setFormat; } deviceFormat = SND_PCM_FORMAT_FLOAT; if (snd_pcm_hw_params_test_format(phandle, hw_params, deviceFormat) == 0) { m_stream.deviceFormat[_mode] = airtaudio::FLOAT32; goto setFormat; } deviceFormat = SND_PCM_FORMAT_S32; if (snd_pcm_hw_params_test_format(phandle, hw_params, deviceFormat) == 0) { m_stream.deviceFormat[_mode] = airtaudio::SINT32; goto setFormat; } deviceFormat = SND_PCM_FORMAT_S24; if (snd_pcm_hw_params_test_format(phandle, hw_params, deviceFormat) == 0) { m_stream.deviceFormat[_mode] = airtaudio::SINT24; goto setFormat; } deviceFormat = SND_PCM_FORMAT_S16; if (snd_pcm_hw_params_test_format(phandle, hw_params, deviceFormat) == 0) { m_stream.deviceFormat[_mode] = airtaudio::SINT16; goto setFormat; } deviceFormat = SND_PCM_FORMAT_S8; if (snd_pcm_hw_params_test_format(phandle, hw_params, deviceFormat) == 0) { m_stream.deviceFormat[_mode] = airtaudio::SINT8; goto setFormat; } // If we get here, no supported format was found. snd_pcm_close(phandle); ATA_ERROR("airtaudio::api::Alsa::probeDeviceOpen: pcm device " << _device << " data format not supported by RtAudio."); return false; setFormat: result = snd_pcm_hw_params_set_format(phandle, hw_params, deviceFormat); if (result < 0) { snd_pcm_close(phandle); ATA_ERROR("airtaudio::api::Alsa::probeDeviceOpen: error setting pcm device (" << name << ") data format, " << snd_strerror(result) << "."); return false; } // Determine whether byte-swaping is necessary. m_stream.doByteSwap[_mode] = false; if (deviceFormat != SND_PCM_FORMAT_S8) { result = snd_pcm_format_cpu_endian(deviceFormat); if (result == 0) { m_stream.doByteSwap[_mode] = true; } else if (result < 0) { snd_pcm_close(phandle); ATA_ERROR("airtaudio::api::Alsa::probeDeviceOpen: error getting pcm device (" << name << ") endian-ness, " << snd_strerror(result) << "."); return false; } } // Set the sample rate. result = snd_pcm_hw_params_set_rate_near(phandle, hw_params, (uint32_t*) &_sampleRate, 0); if (result < 0) { snd_pcm_close(phandle); ATA_ERROR("airtaudio::api::Alsa::probeDeviceOpen: error setting sample rate on device (" << name << "), " << snd_strerror(result) << "."); return false; } // Determine the number of channels for this device. We support a possible // minimum device channel number > than the value requested by the user. m_stream.nUserChannels[_mode] = _channels; uint32_t value; result = snd_pcm_hw_params_get_channels_max(hw_params, &value); uint32_t deviceChannels = value; if ( result < 0 || deviceChannels < _channels + _firstChannel) { snd_pcm_close(phandle); ATA_ERROR("airtaudio::api::Alsa::probeDeviceOpen: requested channel parameters not supported by device (" << name << "), " << snd_strerror(result) << "."); return false; } result = snd_pcm_hw_params_get_channels_min(hw_params, &value); if (result < 0) { snd_pcm_close(phandle); ATA_ERROR("airtaudio::api::Alsa::probeDeviceOpen: error getting minimum channels for device (" << name << "), " << snd_strerror(result) << "."); return false; } deviceChannels = value; if (deviceChannels < _channels + _firstChannel) { deviceChannels = _channels + _firstChannel; } m_stream.nDeviceChannels[_mode] = deviceChannels; // Set the device channels. result = snd_pcm_hw_params_set_channels(phandle, hw_params, deviceChannels); if (result < 0) { snd_pcm_close(phandle); ATA_ERROR("airtaudio::api::Alsa::probeDeviceOpen: error setting channels for device (" << name << "), " << snd_strerror(result) << "."); return false; } // Set the buffer (or period) size. int32_t dir = 0; snd_pcm_uframes_t periodSize = *_bufferSize; result = snd_pcm_hw_params_set_period_size_near(phandle, hw_params, &periodSize, &dir); if (result < 0) { snd_pcm_close(phandle); ATA_ERROR("airtaudio::api::Alsa::probeDeviceOpen: error setting period size for device (" << name << "), " << snd_strerror(result) << "."); return false; } *_bufferSize = periodSize; // Set the buffer number, which in ALSA is referred to as the "period". uint32_t periods = 0; if (_options && _options->flags & airtaudio::MINIMIZE_LATENCY) periods = 2; if (_options && _options->numberOfBuffers > 0) periods = _options->numberOfBuffers; if (periods < 2) periods = 4; // a fairly safe default value result = snd_pcm_hw_params_set_periods_near(phandle, hw_params, &periods, &dir); if (result < 0) { snd_pcm_close(phandle); ATA_ERROR("airtaudio::api::Alsa::probeDeviceOpen: error setting periods for device (" << name << "), " << snd_strerror(result) << "."); return false; } // If attempting to setup a duplex stream, the bufferSize parameter // MUST be the same in both directions! if (m_stream.mode == OUTPUT && _mode == INPUT && *_bufferSize != m_stream.bufferSize) { snd_pcm_close(phandle); ATA_ERROR("airtaudio::api::Alsa::probeDeviceOpen: system error setting buffer size for duplex stream on device (" << name << ")."); return false; } m_stream.bufferSize = *_bufferSize; // Install the hardware configuration result = snd_pcm_hw_params(phandle, hw_params); if (result < 0) { snd_pcm_close(phandle); ATA_ERROR("airtaudio::api::Alsa::probeDeviceOpen: error installing hardware configuration on device (" << name << "), " << snd_strerror(result) << "."); return false; } // Set the software configuration to fill buffers with zeros and prevent device stopping on xruns. snd_pcm_sw_params_t *sw_params = NULL; snd_pcm_sw_params_alloca(&sw_params); snd_pcm_sw_params_current(phandle, sw_params); snd_pcm_sw_params_set_start_threshold(phandle, sw_params, *_bufferSize); snd_pcm_sw_params_set_stop_threshold(phandle, sw_params, ULONG_MAX); snd_pcm_sw_params_set_silence_threshold(phandle, sw_params, 0); // The following two settings were suggested by Theo Veenker //snd_pcm_sw_params_set_avail_min(phandle, sw_params, *_bufferSize); //snd_pcm_sw_params_set_xfer_align(phandle, sw_params, 1); // here are two options for a fix //snd_pcm_sw_params_set_silence_size(phandle, sw_params, ULONG_MAX); snd_pcm_uframes_t val; snd_pcm_sw_params_get_boundary(sw_params, &val); snd_pcm_sw_params_set_silence_size(phandle, sw_params, val); result = snd_pcm_sw_params(phandle, sw_params); if (result < 0) { snd_pcm_close(phandle); ATA_ERROR("airtaudio::api::Alsa::probeDeviceOpen: error installing software configuration on device (" << name << "), " << snd_strerror(result) << "."); return false; } // Set flags for buffer conversion m_stream.doConvertBuffer[_mode] = false; if (m_stream.userFormat != m_stream.deviceFormat[_mode]) { m_stream.doConvertBuffer[_mode] = true; } if (m_stream.nUserChannels[_mode] < m_stream.nDeviceChannels[_mode]) { m_stream.doConvertBuffer[_mode] = true; } if ( m_stream.userInterleaved != m_stream.deviceInterleaved[_mode] && m_stream.nUserChannels[_mode] > 1) { m_stream.doConvertBuffer[_mode] = true; } // Allocate the ApiHandle if necessary and then save. AlsaHandle *apiInfo = 0; if (m_stream.apiHandle == 0) { apiInfo = (AlsaHandle *) new AlsaHandle; if (apiInfo == NULL) { ATA_ERROR("airtaudio::api::Alsa::probeDeviceOpen: error allocating AlsaHandle memory."); goto error; } m_stream.apiHandle = (void *) apiInfo; apiInfo->handles[0] = 0; apiInfo->handles[1] = 0; } else { apiInfo = (AlsaHandle *) m_stream.apiHandle; } apiInfo->handles[_mode] = phandle; phandle = 0; // Allocate necessary internal buffers. uint64_t bufferBytes; bufferBytes = m_stream.nUserChannels[_mode] * *_bufferSize * formatBytes(m_stream.userFormat); m_stream.userBuffer[_mode] = (char *) calloc(bufferBytes, 1); if (m_stream.userBuffer[_mode] == NULL) { ATA_ERROR("airtaudio::api::Alsa::probeDeviceOpen: error allocating user buffer memory."); goto error; } if (m_stream.doConvertBuffer[_mode]) { bool makeBuffer = true; bufferBytes = m_stream.nDeviceChannels[_mode] * formatBytes(m_stream.deviceFormat[_mode]); if (_mode == INPUT) { if (m_stream.mode == OUTPUT && m_stream.deviceBuffer) { uint64_t bytesOut = m_stream.nDeviceChannels[0] * formatBytes(m_stream.deviceFormat[0]); if (bufferBytes <= bytesOut) { makeBuffer = false; } } } if (makeBuffer) { bufferBytes *= *_bufferSize; if (m_stream.deviceBuffer) { free(m_stream.deviceBuffer); m_stream.deviceBuffer = NULL; } m_stream.deviceBuffer = (char *) calloc(bufferBytes, 1); if (m_stream.deviceBuffer == NULL) { ATA_ERROR("airtaudio::api::Alsa::probeDeviceOpen: error allocating device buffer memory."); goto error; } } } m_stream.sampleRate = _sampleRate; m_stream.nBuffers = periods; m_stream.device[_mode] = _device; m_stream.state = STREAM_STOPPED; // Setup the buffer conversion information structure. if (m_stream.doConvertBuffer[_mode]) { setConvertInfo(_mode, _firstChannel); } // Setup thread if necessary. if ( m_stream.mode == OUTPUT && _mode == INPUT) { // We had already set up an output stream. m_stream.mode = DUPLEX; // Link the streams if possible. apiInfo->synchronized = false; if (snd_pcm_link(apiInfo->handles[0], apiInfo->handles[1]) == 0) { apiInfo->synchronized = true; } else { ATA_ERROR("airtaudio::api::Alsa::probeDeviceOpen: unable to synchronize input and output devices."); // TODO : airtaudio::errorWarning; } } else { m_stream.mode = _mode; // Setup callback thread. m_stream.callbackInfo.object = (void *) this; m_stream.callbackInfo.isRunning = true; m_stream.callbackInfo.thread = new std::thread(alsaCallbackHandler, &m_stream.callbackInfo); if (m_stream.callbackInfo.thread == NULL) { m_stream.callbackInfo.isRunning = false; ATA_ERROR("airtaudio::api::Alsa::error creating callback thread!"); goto error; } } return true; error: if (apiInfo != NULL) { if (apiInfo->handles[0]) { snd_pcm_close(apiInfo->handles[0]); } if (apiInfo->handles[1]) { snd_pcm_close(apiInfo->handles[1]); } delete apiInfo; apiInfo = NULL; m_stream.apiHandle = 0; } if (phandle) { snd_pcm_close(phandle); } for (int32_t iii=0; iii<2; ++iii) { if (m_stream.userBuffer[iii]) { free(m_stream.userBuffer[iii]); m_stream.userBuffer[iii] = 0; } } if (m_stream.deviceBuffer) { free(m_stream.deviceBuffer); m_stream.deviceBuffer = 0; } m_stream.state = STREAM_CLOSED; return false; } enum airtaudio::errorType airtaudio::api::Alsa::closeStream(void) { if (m_stream.state == STREAM_CLOSED) { ATA_ERROR("airtaudio::api::Alsa::closeStream(): no open stream to close!"); return airtaudio::errorWarning; } AlsaHandle *apiInfo = (AlsaHandle *) m_stream.apiHandle; m_stream.callbackInfo.isRunning = false; m_stream.mutex.lock(); if (m_stream.state == STREAM_STOPPED) { apiInfo->runnable = true; apiInfo->runnable_cv.notify_one(); } m_stream.mutex.unlock(); if (m_stream.callbackInfo.thread != NULL) { m_stream.callbackInfo.thread->join(); } if (m_stream.state == STREAM_RUNNING) { m_stream.state = STREAM_STOPPED; if ( m_stream.mode == OUTPUT || m_stream.mode == DUPLEX) { snd_pcm_drop(apiInfo->handles[0]); } if ( m_stream.mode == INPUT || m_stream.mode == DUPLEX) { snd_pcm_drop(apiInfo->handles[1]); } } if (apiInfo != NULL) { if (apiInfo->handles[0]) { snd_pcm_close(apiInfo->handles[0]); } if (apiInfo->handles[1]) { snd_pcm_close(apiInfo->handles[1]); } delete apiInfo; apiInfo = NULL; m_stream.apiHandle = 0; } for (int32_t iii=0; iii<2; ++iii) { if (m_stream.userBuffer[iii] != NULL) { free(m_stream.userBuffer[iii]); m_stream.userBuffer[iii] = 0; } } if (m_stream.deviceBuffer) { free(m_stream.deviceBuffer); m_stream.deviceBuffer = 0; } m_stream.mode = UNINITIALIZED; m_stream.state = STREAM_CLOSED; return airtaudio::errorNone; } enum airtaudio::errorType airtaudio::api::Alsa::startStream(void) { // This method calls snd_pcm_prepare if the device isn't already in that state. if (verifyStream() != airtaudio::errorNone) { return airtaudio::errorFail; } if (m_stream.state == STREAM_RUNNING) { ATA_ERROR("airtaudio::api::Alsa::startStream(): the stream is already running!"); return airtaudio::errorWarning; } std::unique_lock lck(m_stream.mutex); int32_t result = 0; snd_pcm_state_t state; AlsaHandle *apiInfo = (AlsaHandle *) m_stream.apiHandle; snd_pcm_t **handle = (snd_pcm_t **) apiInfo->handles; if (m_stream.mode == OUTPUT || m_stream.mode == DUPLEX) { state = snd_pcm_state(handle[0]); if (state != SND_PCM_STATE_PREPARED) { result = snd_pcm_prepare(handle[0]); if (result < 0) { ATA_ERROR("airtaudio::api::Alsa::startStream: error preparing output pcm device, " << snd_strerror(result) << "."); goto unlock; } } } if ( ( m_stream.mode == INPUT || m_stream.mode == DUPLEX) && !apiInfo->synchronized) { state = snd_pcm_state(handle[1]); if (state != SND_PCM_STATE_PREPARED) { result = snd_pcm_prepare(handle[1]); if (result < 0) { ATA_ERROR("airtaudio::api::Alsa::startStream: error preparing input pcm device, " << snd_strerror(result) << "."); goto unlock; } } } m_stream.state = STREAM_RUNNING; unlock: apiInfo->runnable = true; apiInfo->runnable_cv.notify_one(); if (result >= 0) { return airtaudio::errorNone; } return airtaudio::errorSystemError; } enum airtaudio::errorType airtaudio::api::Alsa::stopStream(void) { if (verifyStream() != airtaudio::errorNone) { return airtaudio::errorFail; } if (m_stream.state == STREAM_STOPPED) { ATA_ERROR("airtaudio::api::Alsa::stopStream(): the stream is already stopped!"); return airtaudio::errorWarning; } m_stream.state = STREAM_STOPPED; std::unique_lock lck(m_stream.mutex); int32_t result = 0; AlsaHandle *apiInfo = (AlsaHandle *) m_stream.apiHandle; snd_pcm_t **handle = (snd_pcm_t **) apiInfo->handles; if ( m_stream.mode == OUTPUT || m_stream.mode == DUPLEX) { if (apiInfo->synchronized) { result = snd_pcm_drop(handle[0]); } else { result = snd_pcm_drain(handle[0]); } if (result < 0) { ATA_ERROR("airtaudio::api::Alsa::stopStream: error draining output pcm device, " << snd_strerror(result) << "."); goto unlock; } } if ( ( m_stream.mode == INPUT || m_stream.mode == DUPLEX) && !apiInfo->synchronized) { result = snd_pcm_drop(handle[1]); if (result < 0) { ATA_ERROR("airtaudio::api::Alsa::stopStream: error stopping input pcm device, " << snd_strerror(result) << "."); goto unlock; } } unlock: if (result >= 0) { return airtaudio::errorNone; } return airtaudio::errorSystemError; } enum airtaudio::errorType airtaudio::api::Alsa::abortStream(void) { if (verifyStream() != airtaudio::errorNone) { return airtaudio::errorFail; } if (m_stream.state == STREAM_STOPPED) { ATA_ERROR("airtaudio::api::Alsa::abortStream(): the stream is already stopped!"); return airtaudio::errorWarning; } m_stream.state = STREAM_STOPPED; std::unique_lock lck(m_stream.mutex); int32_t result = 0; AlsaHandle *apiInfo = (AlsaHandle *) m_stream.apiHandle; snd_pcm_t **handle = (snd_pcm_t **) apiInfo->handles; if ( m_stream.mode == OUTPUT || m_stream.mode == DUPLEX) { result = snd_pcm_drop(handle[0]); if (result < 0) { ATA_ERROR("airtaudio::api::Alsa::abortStream: error aborting output pcm device, " << snd_strerror(result) << "."); goto unlock; } } if ( ( m_stream.mode == INPUT || m_stream.mode == DUPLEX) && !apiInfo->synchronized) { result = snd_pcm_drop(handle[1]); if (result < 0) { ATA_ERROR("airtaudio::api::Alsa::abortStream: error aborting input pcm device, " << snd_strerror(result) << "."); goto unlock; } } unlock: if (result >= 0) { return airtaudio::errorNone; } return airtaudio::errorSystemError; } void airtaudio::api::Alsa::callbackEvent(void) { AlsaHandle *apiInfo = (AlsaHandle *) m_stream.apiHandle; if (m_stream.state == STREAM_STOPPED) { std::unique_lock lck(m_stream.mutex); // TODO : Set this back .... /* while (!apiInfo->runnable) { apiInfo->runnable_cv.wait(lck); } */ if (m_stream.state != STREAM_RUNNING) { return; } } if (m_stream.state == STREAM_CLOSED) { ATA_CRITICAL("airtaudio::api::Alsa::callbackEvent(): the stream is closed ... this shouldn't happen!"); return; // TODO : notify appl: airtaudio::errorWarning; } int32_t doStopStream = 0; airtaudio::AirTAudioCallback callback = (airtaudio::AirTAudioCallback) m_stream.callbackInfo.callback; double streamTime = getStreamTime(); airtaudio::streamStatus status = 0; if (m_stream.mode != INPUT && apiInfo->xrun[0] == true) { status |= airtaudio::OUTPUT_UNDERFLOW; apiInfo->xrun[0] = false; } if (m_stream.mode != OUTPUT && apiInfo->xrun[1] == true) { status |= airtaudio::INPUT_OVERFLOW; apiInfo->xrun[1] = false; } doStopStream = callback(m_stream.userBuffer[0], m_stream.userBuffer[1], m_stream.bufferSize, streamTime, status, m_stream.callbackInfo.userData); if (doStopStream == 2) { abortStream(); return; } std::unique_lock lck(m_stream.mutex); // The state might change while waiting on a mutex. if (m_stream.state == STREAM_STOPPED) { goto unlock; } int32_t result; char *buffer; int32_t channels; snd_pcm_t **handle; snd_pcm_sframes_t frames; airtaudio::format format; handle = (snd_pcm_t **) apiInfo->handles; if ( m_stream.mode == airtaudio::api::INPUT || m_stream.mode == airtaudio::api::DUPLEX) { // Setup parameters. if (m_stream.doConvertBuffer[1]) { buffer = m_stream.deviceBuffer; channels = m_stream.nDeviceChannels[1]; format = m_stream.deviceFormat[1]; } else { buffer = m_stream.userBuffer[1]; channels = m_stream.nUserChannels[1]; format = m_stream.userFormat; } // Read samples from device in interleaved/non-interleaved format. if (m_stream.deviceInterleaved[1]) { result = snd_pcm_readi(handle[1], buffer, m_stream.bufferSize); } else { void *bufs[channels]; size_t offset = m_stream.bufferSize * formatBytes(format); for (int32_t i=0; ixrun[1] = true; result = snd_pcm_prepare(handle[1]); if (result < 0) { ATA_ERROR("airtaudio::api::Alsa::callbackEvent: error preparing device after overrun, " << snd_strerror(result) << "."); } } else { ATA_ERROR("airtaudio::api::Alsa::callbackEvent: error, current state is " << snd_pcm_state_name(state) << ", " << snd_strerror(result) << "."); } } else { ATA_ERROR("airtaudio::api::Alsa::callbackEvent: audio read error, " << snd_strerror(result) << "."); } // TODO : Notify application ... airtaudio::errorWarning; goto tryOutput; } // Do byte swapping if necessary. if (m_stream.doByteSwap[1]) { byteSwapBuffer(buffer, m_stream.bufferSize * channels, format); } // Do buffer conversion if necessary. if (m_stream.doConvertBuffer[1]) { convertBuffer(m_stream.userBuffer[1], m_stream.deviceBuffer, m_stream.convertInfo[1]); } // Check stream latency result = snd_pcm_delay(handle[1], &frames); if (result == 0 && frames > 0) { m_stream.latency[1] = frames; } } tryOutput: if ( m_stream.mode == airtaudio::api::OUTPUT || m_stream.mode == airtaudio::api::DUPLEX) { // Setup parameters and do buffer conversion if necessary. if (m_stream.doConvertBuffer[0]) { buffer = m_stream.deviceBuffer; convertBuffer(buffer, m_stream.userBuffer[0], m_stream.convertInfo[0]); channels = m_stream.nDeviceChannels[0]; format = m_stream.deviceFormat[0]; } else { buffer = m_stream.userBuffer[0]; channels = m_stream.nUserChannels[0]; format = m_stream.userFormat; } // Do byte swapping if necessary. if (m_stream.doByteSwap[0]) { byteSwapBuffer(buffer, m_stream.bufferSize * channels, format); } // Write samples to device in interleaved/non-interleaved format. if (m_stream.deviceInterleaved[0]) { result = snd_pcm_writei(handle[0], buffer, m_stream.bufferSize); } else { void *bufs[channels]; size_t offset = m_stream.bufferSize * formatBytes(format); for (int32_t i=0; ixrun[0] = true; result = snd_pcm_prepare(handle[0]); if (result < 0) { ATA_ERROR("airtaudio::api::Alsa::callbackEvent: error preparing device after underrun, " << snd_strerror(result) << "."); } } else { ATA_ERROR("airtaudio::api::Alsa::callbackEvent: error, current state is " << snd_pcm_state_name(state) << ", " << snd_strerror(result) << "."); } } else { ATA_ERROR("airtaudio::api::Alsa::callbackEvent: audio write error, " << snd_strerror(result) << "."); } // TODO : Notuify application airtaudio::errorWarning; goto unlock; } // Check stream latency result = snd_pcm_delay(handle[0], &frames); if (result == 0 && frames > 0) { m_stream.latency[0] = frames; } } unlock: airtaudio::Api::tickStreamTime(); if (doStopStream == 1) { this->stopStream(); } } static void alsaCallbackHandler(void *_ptr) { airtaudio::CallbackInfo *info = (airtaudio::CallbackInfo*)_ptr; airtaudio::api::Alsa *object = (airtaudio::api::Alsa *) info->object; bool *isRunning = &info->isRunning; while (*isRunning == true) { // TODO : pthread_testcancel(); object->callbackEvent(); } } #endif