mirror of
https://git.jami.net/savoirfairelinux/jami-daemon.git
synced 2025-08-12 22:09:25 +08:00
#5607: Use same api for ALSA and PULSEAUDIO to manage audio device list
This commit is contained in:
@ -78,7 +78,7 @@ AlsaLayer::AlsaLayer()
|
||||
, ringtoneHandle_(NULL)
|
||||
, captureHandle_(NULL)
|
||||
, audioPlugin_(audioPref.getPlugin())
|
||||
, IDSoundCards_()
|
||||
// , IDSoundCards_()
|
||||
, is_playback_prepared_(false)
|
||||
, is_capture_prepared_(false)
|
||||
, is_playback_running_(false)
|
||||
@ -160,7 +160,7 @@ AlsaLayer::startStream()
|
||||
if (not is_playback_open_)
|
||||
Manager::instance().getDbusManager()->getConfigurationManager()->errorAlert(ALSA_PLAYBACK_DEVICE);
|
||||
|
||||
if (getIndexOut() != getIndexRing())
|
||||
if (getIndexPlayback() != getIndexRingtone())
|
||||
if (!openDevice(&ringtoneHandle_, pcmr, SND_PCM_STREAM_PLAYBACK))
|
||||
Manager::instance().getDbusManager()->getConfigurationManager()->errorAlert(ALSA_PLAYBACK_DEVICE);
|
||||
}
|
||||
@ -432,7 +432,23 @@ AlsaLayer::buildDeviceTopo(const std::string &plugin, int card)
|
||||
}
|
||||
|
||||
std::vector<std::string>
|
||||
AlsaLayer::getSoundCardsInfo(int stream)
|
||||
AlsaLayer::getAudioDeviceList(AudioStreamDirection dir) const
|
||||
{
|
||||
std::vector<HwIDPair> deviceMap;
|
||||
std::vector<std::string> audioDeviceList;
|
||||
|
||||
deviceMap = getAudioDeviceIndexMap(dir);
|
||||
|
||||
for(std::vector<HwIDPair>::const_iterator iter = deviceMap.begin(); iter != deviceMap.end(); iter++) {
|
||||
audioDeviceList.push_back(iter->second);
|
||||
}
|
||||
|
||||
return audioDeviceList;
|
||||
}
|
||||
|
||||
|
||||
std::vector<HwIDPair>
|
||||
AlsaLayer::getAudioDeviceIndexMap(AudioStreamDirection dir) const
|
||||
{
|
||||
snd_ctl_t* handle;
|
||||
snd_ctl_card_info_t *info;
|
||||
@ -442,10 +458,10 @@ AlsaLayer::getSoundCardsInfo(int stream)
|
||||
|
||||
int numCard = -1 ;
|
||||
|
||||
std::vector<std::string> cards_id;
|
||||
std::vector<HwIDPair> audioDevice;
|
||||
|
||||
if (snd_card_next(&numCard) < 0 || numCard < 0)
|
||||
return cards_id;
|
||||
return audioDevice;
|
||||
|
||||
do {
|
||||
std::stringstream ss;
|
||||
@ -455,10 +471,11 @@ AlsaLayer::getSoundCardsInfo(int stream)
|
||||
if (snd_ctl_open(&handle, name.c_str(), 0) == 0) {
|
||||
if (snd_ctl_card_info(handle, info) == 0) {
|
||||
snd_pcm_info_set_device(pcminfo , 0);
|
||||
snd_pcm_info_set_stream(pcminfo, (stream == SFL_PCM_CAPTURE) ? SND_PCM_STREAM_CAPTURE : SND_PCM_STREAM_PLAYBACK);
|
||||
snd_pcm_info_set_stream(pcminfo, (dir == AUDIO_STREAM_CAPTURE) ? SND_PCM_STREAM_CAPTURE : SND_PCM_STREAM_PLAYBACK);
|
||||
|
||||
if (snd_ctl_pcm_info(handle ,pcminfo) < 0)
|
||||
if (snd_ctl_pcm_info(handle ,pcminfo) < 0) {
|
||||
DEBUG(" Cannot get info");
|
||||
}
|
||||
else {
|
||||
DEBUG("card %i : %s [%s]",
|
||||
numCard,
|
||||
@ -467,9 +484,9 @@ AlsaLayer::getSoundCardsInfo(int stream)
|
||||
std::string description = snd_ctl_card_info_get_name(info);
|
||||
description.append(" - ");
|
||||
description.append(snd_pcm_info_get_name(pcminfo));
|
||||
cards_id.push_back(description);
|
||||
|
||||
// The number of the sound card is associated with a string description
|
||||
IDSoundCards_.push_back(HwIDPair(numCard , description));
|
||||
audioDevice.push_back(HwIDPair(numCard , description));
|
||||
}
|
||||
}
|
||||
|
||||
@ -478,7 +495,7 @@ AlsaLayer::getSoundCardsInfo(int stream)
|
||||
} while (snd_card_next(&numCard) >= 0 && numCard >= 0);
|
||||
|
||||
|
||||
return cards_id;
|
||||
return audioDevice;
|
||||
}
|
||||
|
||||
|
||||
@ -503,9 +520,17 @@ AlsaLayer::soundCardIndexExists(int card, int stream)
|
||||
}
|
||||
|
||||
int
|
||||
AlsaLayer::soundCardGetIndex(const std::string &description)
|
||||
AlsaLayer::getAudioDeviceIndex(const std::string &description) const
|
||||
{
|
||||
for (std::vector<HwIDPair>::const_iterator iter = IDSoundCards_.begin(); iter != IDSoundCards_.end(); ++iter)
|
||||
std::vector<HwIDPair> audioDeviceIndexMap;
|
||||
|
||||
std::vector<HwIDPair> captureDevice = getAudioDeviceIndexMap(AUDIO_STREAM_CAPTURE);
|
||||
std::vector<HwIDPair> playbackDevice = getAudioDeviceIndexMap(AUDIO_STREAM_PLAYBACK);
|
||||
|
||||
audioDeviceIndexMap.insert(audioDeviceIndexMap.end(), captureDevice.begin(), captureDevice.end());
|
||||
audioDeviceIndexMap.insert(audioDeviceIndexMap.end(), playbackDevice.begin(), playbackDevice.end());
|
||||
|
||||
for (std::vector<HwIDPair>::const_iterator iter = audioDeviceIndexMap.begin(); iter != audioDeviceIndexMap.end(); ++iter)
|
||||
if (iter->second == description)
|
||||
return iter->first;
|
||||
|
||||
|
@ -65,14 +65,14 @@ class AlsaLayer : public AudioLayer {
|
||||
* The playback starts accordingly to its threshold
|
||||
* ALSA Library API
|
||||
*/
|
||||
void startStream();
|
||||
virtual void startStream();
|
||||
|
||||
/**
|
||||
* Stop the playback and capture streams.
|
||||
* Drops the pending frames and put the capture and playback handles to PREPARED state
|
||||
* ALSA Library API
|
||||
*/
|
||||
void stopStream();
|
||||
virtual void stopStream();
|
||||
|
||||
/**
|
||||
* Concatenate two strings. Used to build a valid pcm device name.
|
||||
@ -90,7 +90,12 @@ class AlsaLayer : public AudioLayer {
|
||||
* SFL_PCM_BOTH
|
||||
* @return std::vector<std::string> The vector containing the string description of the card
|
||||
*/
|
||||
std::vector<std::string> getSoundCardsInfo(int stream);
|
||||
virtual std::vector<std::string> getAudioDeviceList(AudioStreamDirection dir) const;
|
||||
|
||||
/**
|
||||
* Returns a map of audio device hardware description and index
|
||||
*/
|
||||
std::vector<HwIDPair> getAudioDeviceIndexMap(AudioStreamDirection dir) const;
|
||||
|
||||
/**
|
||||
* Check if the given index corresponds to an existing sound card and supports the specified streaming mode
|
||||
@ -109,7 +114,7 @@ class AlsaLayer : public AudioLayer {
|
||||
* @param description The string description
|
||||
* @return int Its index
|
||||
*/
|
||||
int soundCardGetIndex(const std::string &description);
|
||||
int getAudioDeviceIndex(const std::string &description) const;
|
||||
|
||||
void playback(int maxSamples);
|
||||
void capture();
|
||||
@ -121,7 +126,7 @@ class AlsaLayer : public AudioLayer {
|
||||
* @return int The index of the card used for capture
|
||||
* 0 for the first available card on the system, 1 ...
|
||||
*/
|
||||
int getIndexIn() const {
|
||||
int getIndexCapture() const {
|
||||
return indexIn_;
|
||||
}
|
||||
|
||||
@ -130,7 +135,7 @@ class AlsaLayer : public AudioLayer {
|
||||
* @return int The index of the card used for playback
|
||||
* 0 for the first available card on the system, 1 ...
|
||||
*/
|
||||
int getIndexOut() const {
|
||||
int getIndexPlayback() const {
|
||||
return indexOut_;
|
||||
}
|
||||
|
||||
@ -139,7 +144,7 @@ class AlsaLayer : public AudioLayer {
|
||||
* @return int The index of the card used for ringtone
|
||||
* 0 for the first available card on the system, 1 ...
|
||||
*/
|
||||
int getIndexRing() const {
|
||||
int getIndexRingtone() const {
|
||||
return indexRing_;
|
||||
}
|
||||
|
||||
@ -227,7 +232,7 @@ class AlsaLayer : public AudioLayer {
|
||||
std::string audioPlugin_;
|
||||
|
||||
/** Vector to manage all soundcard index - description association of the system */
|
||||
std::vector<HwIDPair> IDSoundCards_;
|
||||
// std::vector<HwIDPair> IDSoundCards_;
|
||||
|
||||
bool is_playback_prepared_;
|
||||
bool is_capture_prepared_;
|
||||
|
@ -54,6 +54,8 @@ namespace ost {
|
||||
class Time;
|
||||
}
|
||||
|
||||
enum AudioStreamDirection { AUDIO_STREAM_CAPTURE, AUDIO_STREAM_PLAYBACK };
|
||||
|
||||
class AudioLayer {
|
||||
private:
|
||||
NON_COPYABLE(AudioLayer);
|
||||
@ -62,6 +64,8 @@ class AudioLayer {
|
||||
AudioLayer();
|
||||
virtual ~AudioLayer();
|
||||
|
||||
virtual std::vector<std::string> getAudioDeviceList(AudioStreamDirection dir) const = 0;
|
||||
|
||||
/**
|
||||
* Start the capture stream and prepare the playback stream.
|
||||
* The playback starts accordingly to its threshold
|
||||
@ -76,6 +80,9 @@ class AudioLayer {
|
||||
*/
|
||||
virtual void stopStream() = 0;
|
||||
|
||||
/**
|
||||
* Determine wether or not the audio layer is active (i.e. stream opened)
|
||||
*/
|
||||
bool isStarted() const {
|
||||
return isStarted_;
|
||||
}
|
||||
@ -88,11 +95,18 @@ class AudioLayer {
|
||||
*/
|
||||
void putUrgent(void* buffer, int toCopy);
|
||||
|
||||
/**
|
||||
* Flush main buffer
|
||||
*/
|
||||
void flushMain();
|
||||
|
||||
/**
|
||||
* Flush urgent buffer
|
||||
*/
|
||||
void flushUrgent();
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Get the sample rate of the audio layer
|
||||
* @return unsigned int The sample rate
|
||||
@ -109,6 +123,9 @@ class AudioLayer {
|
||||
return &mutex_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit an audio notification on incoming calls
|
||||
*/
|
||||
void notifyincomingCall();
|
||||
|
||||
protected:
|
||||
|
@ -31,7 +31,7 @@
|
||||
#include <audiostream.h>
|
||||
#include "pulselayer.h"
|
||||
|
||||
AudioStream::AudioStream(pa_context *c, pa_threaded_mainloop *m, const char *desc, int type, int smplrate, std::string *deviceName)
|
||||
AudioStream::AudioStream(pa_context *c, pa_threaded_mainloop *m, const char *desc, int type, int smplrate, std::string& deviceName)
|
||||
: audiostream_(0), mainloop_(m)
|
||||
{
|
||||
static const pa_channel_map channel_map = {
|
||||
@ -62,14 +62,14 @@ AudioStream::AudioStream(pa_context *c, pa_threaded_mainloop *m, const char *des
|
||||
attributes.fragsize = pa_usec_to_bytes(80 * PA_USEC_PER_MSEC, &sample_spec);
|
||||
attributes.minreq = (uint32_t) -1;
|
||||
|
||||
const char *name = deviceName ? deviceName->c_str() : NULL;
|
||||
|
||||
pa_threaded_mainloop_lock(mainloop_);
|
||||
|
||||
if (type == PLAYBACK_STREAM || type == RINGTONE_STREAM)
|
||||
pa_stream_connect_playback(audiostream_, name, &attributes, (pa_stream_flags_t)(PA_STREAM_ADJUST_LATENCY|PA_STREAM_AUTO_TIMING_UPDATE), NULL, NULL);
|
||||
pa_stream_connect_playback(audiostream_, deviceName == "" ? NULL : deviceName.c_str(), &attributes,
|
||||
(pa_stream_flags_t)(PA_STREAM_ADJUST_LATENCY|PA_STREAM_AUTO_TIMING_UPDATE), NULL, NULL);
|
||||
else if (type == CAPTURE_STREAM)
|
||||
pa_stream_connect_record(audiostream_, name, &attributes, (pa_stream_flags_t)(PA_STREAM_ADJUST_LATENCY|PA_STREAM_AUTO_TIMING_UPDATE));
|
||||
pa_stream_connect_record(audiostream_, deviceName == "" ? NULL : deviceName.c_str(), &attributes,
|
||||
(pa_stream_flags_t)(PA_STREAM_ADJUST_LATENCY|PA_STREAM_AUTO_TIMING_UPDATE));
|
||||
|
||||
pa_threaded_mainloop_unlock(mainloop_);
|
||||
|
||||
|
@ -55,7 +55,7 @@ class AudioStream {
|
||||
* @param audio sampling rate
|
||||
* @param device name
|
||||
*/
|
||||
AudioStream(pa_context *, pa_threaded_mainloop *, const char *, int, int, std::string *);
|
||||
AudioStream(pa_context *, pa_threaded_mainloop *, const char *, int, int, std::string&);
|
||||
|
||||
~AudioStream();
|
||||
|
||||
|
@ -65,7 +65,6 @@ void stream_moved_callback(pa_stream *s, void *userdata UNUSED)
|
||||
|
||||
} // end anonymous namespace
|
||||
|
||||
|
||||
PulseLayer::PulseLayer()
|
||||
: playback_(0)
|
||||
, record_(0)
|
||||
@ -148,6 +147,7 @@ void PulseLayer::context_state_callback(pa_context* c, void* user_data)
|
||||
PA_SUBSCRIPTION_MASK_SOURCE), NULL, pulse);
|
||||
pa_context_set_subscribe_callback(c, context_changed_callback, pulse);
|
||||
pulse->updateSinkList();
|
||||
pulse->updateSourceList();
|
||||
break;
|
||||
|
||||
case PA_CONTEXT_TERMINATED:
|
||||
@ -187,30 +187,40 @@ bool PulseLayer::inSourceList(const std::string &deviceName) const
|
||||
return std::find(sourceList_.begin(), sourceList_.end(), deviceName) != sourceList_.end();
|
||||
}
|
||||
|
||||
std::vector<std::string> PulseLayer::getAudioDeviceList(AudioStreamDirection dir) const
|
||||
{
|
||||
if(AUDIO_STREAM_CAPTURE == dir) {
|
||||
return sinkList_;
|
||||
}
|
||||
if(AUDIO_STREAM_PLAYBACK) {
|
||||
return sourceList_;
|
||||
}
|
||||
}
|
||||
|
||||
void PulseLayer::createStreams(pa_context* c)
|
||||
{
|
||||
std::string playbackDevice(audioPref.getDevicePlayback());
|
||||
std::string recordDevice(audioPref.getDeviceRecord());
|
||||
std::string captureDevice(audioPref.getDeviceRecord());
|
||||
std::string ringtoneDevice(audioPref.getDeviceRingtone());
|
||||
std::string defaultDevice = "";
|
||||
|
||||
DEBUG("PulseAudio: Devices: playback %s , record %s , ringtone %s",
|
||||
playbackDevice.c_str(), recordDevice.c_str(), ringtoneDevice.c_str());
|
||||
DEBUG("PulseAudio: Devices:\n playback: %s\n record: %s\n ringtone: %s",
|
||||
playbackDevice.c_str(), captureDevice.c_str(), ringtoneDevice.c_str());
|
||||
|
||||
playback_ = new AudioStream(c, mainloop_, "SFLphone playback", PLAYBACK_STREAM, audioSampleRate_,
|
||||
inSinkList(playbackDevice) ? &playbackDevice : NULL);
|
||||
inSourceList(playbackDevice) ? playbackDevice : defaultDevice);
|
||||
|
||||
pa_stream_set_write_callback(playback_->pulseStream(), playback_callback, this);
|
||||
pa_stream_set_moved_callback(playback_->pulseStream(), stream_moved_callback, this);
|
||||
|
||||
record_ = new AudioStream(c, mainloop_, "SFLphone capture", CAPTURE_STREAM, audioSampleRate_,
|
||||
inSourceList(recordDevice) ? &recordDevice : NULL);
|
||||
inSinkList(captureDevice) ? captureDevice : defaultDevice);
|
||||
|
||||
pa_stream_set_read_callback(record_->pulseStream() , capture_callback, this);
|
||||
pa_stream_set_moved_callback(record_->pulseStream(), stream_moved_callback, this);
|
||||
|
||||
ringtone_ = new AudioStream(c, mainloop_, "SFLphone ringtone", RINGTONE_STREAM, audioSampleRate_,
|
||||
inSourceList(ringtoneDevice) ? &ringtoneDevice : NULL);
|
||||
inSourceList(ringtoneDevice) ? ringtoneDevice : defaultDevice);
|
||||
|
||||
pa_stream_set_write_callback(ringtone_->pulseStream(), ringtone_callback, this);
|
||||
pa_stream_set_moved_callback(ringtone_->pulseStream(), stream_moved_callback, this);
|
||||
@ -512,7 +522,7 @@ void PulseLayer::source_input_info_callback(pa_context *c UNUSED, const pa_sourc
|
||||
{
|
||||
char s[PA_SAMPLE_SPEC_SNPRINT_MAX], cv[PA_CVOLUME_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX];
|
||||
|
||||
if (!eol)
|
||||
if (eol)
|
||||
return;
|
||||
|
||||
DEBUG("Sink %u\n"
|
||||
|
@ -62,9 +62,11 @@ class PulseLayer : public AudioLayer {
|
||||
|
||||
bool inSourceList(const std::string &deviceName) const;
|
||||
|
||||
void startStream();
|
||||
virtual std::vector<std::string> getAudioDeviceList(AudioStreamDirection dir) const;
|
||||
|
||||
void stopStream();
|
||||
virtual void startStream();
|
||||
|
||||
virtual void stopStream();
|
||||
|
||||
private:
|
||||
static void context_state_callback(pa_context* c, void* user_data);
|
||||
@ -106,8 +108,15 @@ class PulseLayer : public AudioLayer {
|
||||
*/
|
||||
AudioStream* ringtone_;
|
||||
|
||||
std::list<std::string> sinkList_;
|
||||
std::list<std::string> sourceList_;
|
||||
/**
|
||||
* Contain the list of playback devices
|
||||
*/
|
||||
std::vector<std::string> sinkList_;
|
||||
|
||||
/**
|
||||
* Contain the list of capture devices
|
||||
*/
|
||||
std::vector<std::string> sourceList_;
|
||||
|
||||
/*
|
||||
* Buffers used to avoid doing malloc/free in the audio thread
|
||||
|
@ -1978,8 +1978,7 @@ std::vector<std::string> ManagerImpl::getAudioOutputDeviceList()
|
||||
AlsaLayer *alsalayer = dynamic_cast<AlsaLayer*>(audiodriver_);
|
||||
|
||||
if (alsalayer)
|
||||
devices = alsalayer->getSoundCardsInfo(SFL_PCM_PLAYBACK);
|
||||
|
||||
devices = alsalayer->getAudioDeviceList(AUDIO_STREAM_PLAYBACK);
|
||||
audioLayerMutexUnlock();
|
||||
|
||||
return devices;
|
||||
@ -1998,7 +1997,7 @@ std::vector<std::string> ManagerImpl::getAudioInputDeviceList()
|
||||
AlsaLayer *alsalayer = dynamic_cast<AlsaLayer *>(audiodriver_);
|
||||
|
||||
if (alsalayer)
|
||||
devices = alsalayer->getSoundCardsInfo(SFL_PCM_CAPTURE);
|
||||
devices = alsalayer->getAudioDeviceList(AUDIO_STREAM_CAPTURE);
|
||||
|
||||
audioLayerMutexUnlock();
|
||||
|
||||
@ -2018,11 +2017,11 @@ std::vector<std::string> ManagerImpl::getCurrentAudioDevicesIndex()
|
||||
|
||||
if (alsa) {
|
||||
std::stringstream ssi, sso, ssr;
|
||||
sso << alsa->getIndexOut();
|
||||
sso << alsa->getIndexPlayback();
|
||||
v.push_back(sso.str());
|
||||
ssi << alsa->getIndexIn();
|
||||
ssi << alsa->getIndexCapture();
|
||||
v.push_back(ssi.str());
|
||||
ssr << alsa->getIndexRing();
|
||||
ssr << alsa->getIndexRingtone();
|
||||
v.push_back(ssr.str());
|
||||
}
|
||||
|
||||
@ -2236,7 +2235,7 @@ int ManagerImpl::getAudioDeviceIndex(const std::string &name)
|
||||
AlsaLayer *alsalayer = dynamic_cast<AlsaLayer *>(audiodriver_);
|
||||
|
||||
if (alsalayer)
|
||||
soundCardIndex = alsalayer -> soundCardGetIndex(name);
|
||||
soundCardIndex = alsalayer -> getAudioDeviceIndex(name);
|
||||
|
||||
audioLayerMutexUnlock();
|
||||
|
||||
|
@ -152,32 +152,37 @@ preferences_dialog_fill_ringtone_audio_device_list()
|
||||
void
|
||||
select_active_output_audio_device()
|
||||
{
|
||||
if (must_show_alsa_conf()) {
|
||||
// Select active output device on server
|
||||
gchar **devices = dbus_get_current_audio_devices_index();
|
||||
int currentDeviceIndex = atoi(devices[0]);
|
||||
DEBUG("audio device index for output = %d", currentDeviceIndex);
|
||||
GtkTreeModel *model = gtk_combo_box_get_model(GTK_COMBO_BOX(output));
|
||||
gboolean show_alsa = must_show_alsa_conf();
|
||||
|
||||
// Find the currently set output device
|
||||
GtkTreeIter iter;
|
||||
gtk_tree_model_get_iter_first(model, &iter);
|
||||
if(!show_alsa)
|
||||
return;
|
||||
|
||||
do {
|
||||
int deviceIndex;
|
||||
gtk_tree_model_get(model, &iter, 1, &deviceIndex, -1);
|
||||
// Select active output device on server
|
||||
gchar **devices = dbus_get_current_audio_devices_index();
|
||||
|
||||
|
||||
if (deviceIndex == currentDeviceIndex) {
|
||||
// Set current iteration the active one
|
||||
gtk_combo_box_set_active_iter(GTK_COMBO_BOX(output), &iter);
|
||||
return;
|
||||
}
|
||||
} while (gtk_tree_model_iter_next(model, &iter));
|
||||
int currentDeviceIndex = atoi(devices[0]);
|
||||
DEBUG("audio device index for output = %d", currentDeviceIndex);
|
||||
GtkTreeModel *model = gtk_combo_box_get_model(GTK_COMBO_BOX(output));
|
||||
|
||||
// No index was found, select first one
|
||||
WARN("Warning : No active output device found");
|
||||
gtk_combo_box_set_active(GTK_COMBO_BOX(output), 0);
|
||||
}
|
||||
// Find the currently set output device
|
||||
GtkTreeIter iter;
|
||||
gtk_tree_model_get_iter_first(model, &iter);
|
||||
|
||||
do {
|
||||
int deviceIndex;
|
||||
gtk_tree_model_get(model, &iter, 1, &deviceIndex, -1);
|
||||
|
||||
if (deviceIndex == currentDeviceIndex) {
|
||||
// Set current iteration the active one
|
||||
gtk_combo_box_set_active_iter(GTK_COMBO_BOX(output), &iter);
|
||||
return;
|
||||
}
|
||||
} while (gtk_tree_model_iter_next(model, &iter));
|
||||
|
||||
// No index was found, select first one
|
||||
WARN("Warning : No active output device found");
|
||||
gtk_combo_box_set_active(GTK_COMBO_BOX(output), 0);
|
||||
}
|
||||
|
||||
|
||||
|
Reference in New Issue
Block a user