示例#1
0
bool
pulse_output_set_volume(struct pulse_output *po,
			const struct pa_cvolume *volume, GError **error_r)
{
	pa_operation *o;

	if (po->context == NULL || po->stream == NULL ||
	    pa_stream_get_state(po->stream) != PA_STREAM_READY) {
		g_set_error(error_r, pulse_output_quark(), 0, "disconnected");
		return false;
	}

	o = pa_context_set_sink_input_volume(po->context,
					     pa_stream_get_index(po->stream),
					     volume, NULL, NULL);
	if (o == NULL) {
		g_set_error(error_r, pulse_output_quark(), 0,
			    "failed to set PulseAudio volume: %s",
			    pa_strerror(pa_context_errno(po->context)));
		return false;
	}

	pa_operation_unref(o);
	return true;
}
示例#2
0
void CPulseAEStream::SetVolume(float volume)
{
  if (!m_Initialized)
    return;

  if (!pa_threaded_mainloop_in_thread(m_MainLoop))
    pa_threaded_mainloop_lock(m_MainLoop);

  if (volume > 0.f)
  {
    m_Volume = volume;
    pa_volume_t paVolume = pa_sw_volume_from_linear((double)(m_Volume * m_MaxVolume));

    pa_cvolume_set(&m_ChVolume, m_SampleSpec.channels, paVolume);
  } 
  else
    pa_cvolume_mute(&m_ChVolume,m_SampleSpec.channels);

  pa_operation *op = pa_context_set_sink_input_volume(m_Context, pa_stream_get_index(m_Stream), &m_ChVolume, NULL, NULL);

  if (op == NULL)
    CLog::Log(LOGERROR, "PulseAudio: Failed to set volume");
  else
    pa_operation_unref(op);

  if (!pa_threaded_mainloop_in_thread(m_MainLoop))
    pa_threaded_mainloop_unlock(m_MainLoop);
}
示例#3
0
void change_sel_vol(pa_context * c, vol_change_t vol_change)
{
    view_entry_t view_entry = view_selected_item();

    if(view_entry.type == VIEW_SINK) {
        sink_t * sink = (sink_t *) view_entry.ref;
        if(sink != NULL) {
            /* averge all channel volumes of local copy */
            pa_volume_t vol;
            vol = pa_cvolume_avg(&sink->volume);
            vol = modify_volume(vol, vol_change);
            pa_cvolume_set(&sink->volume, sink->volume.channels, vol);
            pa_context_set_sink_volume_by_index(c, sink->index, &sink->volume, NULL, NULL);
        }
    }

    if(view_entry.type == VIEW_SINK_INPUT) {
        sink_input_t * sink_input = (sink_input_t *) view_entry.ref;
        if(sink_input != NULL) {
            /* averge all channel volumes of local copy */
            pa_volume_t vol;
            vol = pa_cvolume_avg(&sink_input->volume);
            vol = modify_volume(vol, vol_change);
            pa_cvolume_set(&sink_input->volume, sink_input->volume.channels, vol);
            pa_context_set_sink_input_volume(c, sink_input->index, &sink_input->volume, NULL, NULL);
        }
    }
}
示例#4
0
void SinkInput::setVolume(pa_cvolume v)
{
    pa_operation *o;
    this->d->svolume = v;
    o = pa_context_set_sink_input_volume(d->context->cObject(),
			d->index, &d->svolume, SinkInput::volume_cb, this);
    pa_operation_unref(o);
}
示例#5
0
void PulseOutput::volume(FXfloat v) {
  if (pulse_context && stream) {
    pulsevolume = (pa_volume_t)(v*PA_VOLUME_NORM);
    pa_cvolume cvol;
    pa_cvolume_set(&cvol,af.channels,pulsevolume);
    pa_operation* operation = pa_context_set_sink_input_volume(pulse_context,pa_stream_get_index(stream),&cvol,nullptr,nullptr);
    pa_operation_unref(operation);
    }
  }
void AudioOutputPulseAudio::SetVolumeChannel(int channel, int volume)
{
    QString fn_log_tag = "SetVolumeChannel, ";

    if (channel < 0 || channel > PULSE_MAX_CHANNELS || volume < 0)
    {
        VBERROR(fn_log_tag + QString("bad volume params, channel %1, volume %2")
                             .arg(channel).arg(volume));
        return;
    }

    volume_control.values[channel] =
        (float)volume / 100.0f * (float)PA_VOLUME_NORM;

    volume = min(100, volume);
    volume = max(0, volume);

    if (gCoreContext->GetSetting("MixerControl", "PCM").toLower() == "pcm")
    {
        uint32_t stream_index = pa_stream_get_index(pstream);
        pa_threaded_mainloop_lock(mainloop);
        pa_operation *op =
            pa_context_set_sink_input_volume(pcontext, stream_index,
                                             &volume_control,
                                             OpCompletionCallback, this);
        pa_threaded_mainloop_unlock(mainloop);
        if (op)
            pa_operation_unref(op);
        else
            VBERROR(fn_log_tag +
                    QString("set stream volume operation failed, stream %1, "
                            "error %2 ")
                    .arg(stream_index)
                    .arg(pa_strerror(pa_context_errno(pcontext))));
    }
    else
    {
        uint32_t sink_index = pa_stream_get_device_index(pstream);
        pa_threaded_mainloop_lock(mainloop);
        pa_operation *op =
            pa_context_set_sink_volume_by_index(pcontext, sink_index,
                                                &volume_control,
                                                OpCompletionCallback, this);
        pa_threaded_mainloop_unlock(mainloop);
        if (op)
            pa_operation_unref(op);
        else
            VBERROR(fn_log_tag +
                    QString("set sink volume operation failed, sink %1, "
                            "error %2 ")
                    .arg(sink_index)
                    .arg(pa_strerror(pa_context_errno(pcontext))));
    }
}
示例#7
0
static void volume_time_cb(pa_mainloop_api *api, pa_time_event *e, const struct timeval *tv, void *userdata) {
    pa_operation *o;

    if (!(o = pa_context_set_sink_input_volume(context, pa_stream_get_index(stream), &volume, NULL, NULL)))
        AUDDBG("pa_context_set_sink_input_volume() failed: %s", pa_strerror(pa_context_errno(context)));
    else
        pa_operation_unref(o);

    /* We don't wait for completion of this command */

    api->time_free(volume_time_event);
    volume_time_event = NULL;
}
示例#8
0
static int qpa_ctl_out (HWVoiceOut *hw, int cmd, ...)
{
    PAVoiceOut *pa = (PAVoiceOut *) hw;
    pa_operation *op;
    pa_cvolume v;
    paaudio *g = pa->g;

#ifdef PA_CHECK_VERSION    /* macro is present in 0.9.16+ */
    pa_cvolume_init (&v);  /* function is present in 0.9.13+ */
#endif

    switch (cmd) {
    case VOICE_VOLUME:
        {
            SWVoiceOut *sw;
            va_list ap;

            va_start (ap, cmd);
            sw = va_arg (ap, SWVoiceOut *);
            va_end (ap);

            v.channels = 2;
            v.values[0] = ((PA_VOLUME_NORM - PA_VOLUME_MUTED) * sw->vol.l) / UINT32_MAX;
            v.values[1] = ((PA_VOLUME_NORM - PA_VOLUME_MUTED) * sw->vol.r) / UINT32_MAX;

            pa_threaded_mainloop_lock (g->mainloop);

            op = pa_context_set_sink_input_volume (g->context,
                pa_stream_get_index (pa->stream),
                &v, NULL, NULL);
            if (!op)
                qpa_logerr (pa_context_errno (g->context),
                            "set_sink_input_volume() failed\n");
            else
                pa_operation_unref (op);

            op = pa_context_set_sink_input_mute (g->context,
                pa_stream_get_index (pa->stream),
               sw->vol.mute, NULL, NULL);
            if (!op) {
                qpa_logerr (pa_context_errno (g->context),
                            "set_sink_input_mute() failed\n");
            } else {
                pa_operation_unref (op);
            }

            pa_threaded_mainloop_unlock (g->mainloop);
        }
    }
    return 0;
}
static int control(int cmd, void *arg) {
    switch (cmd) {
        case AOCONTROL_GET_VOLUME: {
            ao_control_vol_t *vol = arg;
            uint32_t devidx = pa_stream_get_index(stream);
            pa_threaded_mainloop_lock(mainloop);
            if (!waitop(pa_context_get_sink_input_info(context, devidx, info_func, NULL))) {
                GENERIC_ERR_MSG(context, "pa_stream_get_sink_input_info() failed");
                return CONTROL_ERROR;
            }

            if (volume.channels != 2)
                vol->left = vol->right = pa_cvolume_avg(&volume)*100/PA_VOLUME_NORM;
            else {
                vol->left = volume.values[0]*100/PA_VOLUME_NORM;
                vol->right = volume.values[1]*100/PA_VOLUME_NORM;
            }

            return CONTROL_OK;
        }

        case AOCONTROL_SET_VOLUME: {
            const ao_control_vol_t *vol = arg;
            pa_operation *o;

            if (volume.channels != 2)
                pa_cvolume_set(&volume, volume.channels, (pa_volume_t)vol->left*PA_VOLUME_NORM/100);
            else {
                volume.values[0] = (pa_volume_t)vol->left*PA_VOLUME_NORM/100;
                volume.values[1] = (pa_volume_t)vol->right*PA_VOLUME_NORM/100;
            }

            pa_threaded_mainloop_lock(mainloop);
            o = pa_context_set_sink_input_volume(context, pa_stream_get_index(stream), &volume, NULL, NULL);
            if (!o) {
                pa_threaded_mainloop_unlock(mainloop);
                GENERIC_ERR_MSG(context, "pa_context_set_sink_input_volume() failed");
                return CONTROL_ERROR;
            }
            /* We don't wait for completion here */
            pa_operation_unref(o);
            pa_threaded_mainloop_unlock(mainloop);
            return CONTROL_OK;
        }

        default:
            return CONTROL_UNKNOWN;
    }
}
示例#10
0
void pulseaudio_volume(menu_info_item_t* mii, int inc)
{
    g_debug("pulseaudio_volume(%s, %i)", mii->name, inc);

    /* increment/decrement in 2% steps */
    pa_cvolume* volume;
    if(inc < 0)
        volume = pa_cvolume_dec(mii->volume, -inc * PA_VOLUME_NORM / 50);
    else if(inc > 0)
    {
        int volume_max = mii->menu_info->menu_infos->settings.volume_max;
        if(volume_max > 0)
            volume = pa_cvolume_inc_clamp(mii->volume, inc * PA_VOLUME_NORM / 50,
                    PA_VOLUME_NORM * volume_max / 100);
        else
            volume = pa_cvolume_inc(mii->volume, inc * PA_VOLUME_NORM / 50);
    }
    else
        return;

    pa_operation* o = NULL;

    switch(mii->menu_info->type)
    {
        case MENU_SERVER:
        case MENU_MODULE:
            /* nothing to do here */
            break;
        case MENU_SINK:
            o = pa_context_set_sink_volume_by_index(context, mii->index,
                    volume, pulseaudio_set_volume_success_cb, mii);
            break;
        case MENU_SOURCE:
            o = pa_context_set_source_volume_by_index(context, mii->index,
                    volume, pulseaudio_set_volume_success_cb, mii);
            break;
        case MENU_INPUT:
            o = pa_context_set_sink_input_volume(context, mii->index,
                    volume, pulseaudio_set_volume_success_cb, mii);
            break;
        case MENU_OUTPUT:
            o = pa_context_set_source_output_volume(context, mii->index,
                    volume, pulseaudio_set_volume_success_cb, mii);
            break;
    }
    if(o)
        pa_operation_unref(o);
}
int pa_simple_set_volume(pa_simple *p, int volume, int *rerror) {
    pa_operation *o = NULL;
    pa_stream *s = NULL;
    uint32_t idx;
    pa_cvolume cv;
    pa_volume_t v;

    pa_assert(p);

    CHECK_VALIDITY_RETURN_ANY(rerror, p->direction == PA_STREAM_PLAYBACK, PA_ERR_BADSTATE, -1);
    CHECK_VALIDITY_RETURN_ANY(rerror, volume >= 0, PA_ERR_INVALID, -1);
    CHECK_VALIDITY_RETURN_ANY(rerror, volume <= 65535, PA_ERR_INVALID, -1);

    pa_threaded_mainloop_lock(p->mainloop);
    CHECK_DEAD_GOTO(p, rerror, unlock_and_fail);

    CHECK_SUCCESS_GOTO(p, rerror, ((idx = pa_stream_get_index (p->stream)) != PA_INVALID_INDEX), unlock_and_fail);

    s = p->stream;
    pa_assert(s);
    pa_cvolume_set(&cv, s->sample_spec.channels, volume);

    o = pa_context_set_sink_input_volume (p->context, idx, &cv, success_context_cb, p);

    CHECK_SUCCESS_GOTO(p, rerror, o, unlock_and_fail);

    p->operation_success = 0;
    while (pa_operation_get_state(o) == PA_OPERATION_RUNNING) {
        pa_threaded_mainloop_wait(p->mainloop);
        CHECK_DEAD_GOTO(p, rerror, unlock_and_fail);
    }
    CHECK_SUCCESS_GOTO(p, rerror, p->operation_success, unlock_and_fail);

    pa_operation_unref(o);
    pa_threaded_mainloop_unlock(p->mainloop);

    return 0;

unlock_and_fail:

    if (o) {
        pa_operation_cancel(o);
        pa_operation_unref(o);
    }

    pa_threaded_mainloop_unlock(p->mainloop);
    return -1;
}
JNIEXPORT jlong JNICALL
Java_org_jitsi_impl_neomedia_pulseaudio_PA_context_1set_1sink_1input_1volume
    (JNIEnv *env, jclass clazz, jlong c, jint idx, jlong volume, jobject cb)
{
    pa_context *c_ = (pa_context *) (intptr_t) c;
    uint32_t idx_ = (uint32_t) idx;

    return
        (intptr_t)
            pa_context_set_sink_input_volume(
                    c_,
                    idx_,
                    (const pa_cvolume *) (intptr_t) volume,
                    NULL,
                    NULL);
}
示例#13
0
/** Issue special libao controls on the device */
static int control(int cmd, void *arg) {
    
    if (!context || !stream)
        return CONTROL_ERROR;
    
    switch (cmd) {

        case AOCONTROL_SET_DEVICE:
            /* Change the playback device */
            sink = (char*)arg;
            return CONTROL_OK;

        case AOCONTROL_GET_DEVICE:
            /* Return the playback device */
            *(char**)arg = sink;
            return CONTROL_OK;
        
        case AOCONTROL_GET_VOLUME: {
            /* Return the current volume of the playback stream */
            ao_control_vol_t *vol = (ao_control_vol_t*) arg;
                
            volume = PA_VOLUME_NORM;
            wait_for_operation(pa_context_get_sink_input_info(context, pa_stream_get_index(stream), info_func, NULL));
            vol->left = vol->right = (int) (pa_volume_to_user(volume)*100);
            return CONTROL_OK;
        }
            
        case AOCONTROL_SET_VOLUME: {
            /* Set the playback volume of the stream */
            const ao_control_vol_t *vol = (ao_control_vol_t*) arg;
            int v = vol->left;
            if (vol->right > v)
                v = vol->left;
            
            wait_for_operation(pa_context_set_sink_input_volume(context, pa_stream_get_index(stream), pa_volume_from_user((double)v/100), NULL, NULL));
            
            return CONTROL_OK;
        }
            
        default:
            /* Unknown CONTROL command */
            return CONTROL_UNKNOWN;
    }
}
示例#14
0
void PulseAudioSystem::volume_sink_input_list_callback(pa_context *c, const pa_sink_input_info *i, int eol, void *userdata) {
	PulseAudioSystem *pas = reinterpret_cast<PulseAudioSystem *>(userdata);

	if (eol == 0) {
		// If we're using the default of "enable attenuation on all ouputs" and output from an application is loopbacked,
		// both the loopback and the application will be attenuated leading to double attenuation.
		if (!g.s.bOnlyAttenuateSameOutput && pas->iSinkId > -1 && !strcmp(i->driver, "module-loopback.c")) {
			return;
		}
		// If we're not attenuating different sinks and the input is not on this sink, don't attenuate. Or,
		// if the input is a loopback module and connected to Mumble's sink, also ignore it (loopbacks are used to connect
		// sinks). An attenuated loopback means an indirect application attenuation.
		if (g.s.bOnlyAttenuateSameOutput && pas->iSinkId > -1) {
			if (int(i->sink) != pas->iSinkId || (int(i->sink) == pas->iSinkId && !strcmp(i->driver, "module-loopback.c") && !g.s.bAttenuateLoopbacks)) {
				return;
			}
		}
		// ensure we're not attenuating ourselves!
		if (strcmp(i->name, mumble_sink_input) != 0) {
			// create a new entry
			PulseAttenuation patt;
			patt.index = i->index;
			patt.name = QLatin1String(i->name);
			patt.stream_restore_id = QLatin1String(pa_proplist_gets(i->proplist, "module-stream-restore.id"));
			patt.normal_volume = i->volume;

			// calculate the attenuated volume
			pa_volume_t adj = static_cast<pa_volume_t>(PA_VOLUME_NORM * g.s.fOtherVolume);
			pa_sw_cvolume_multiply_scalar(&patt.attenuated_volume, &i->volume, adj);

			// set it on the sink input
			pa_operation_unref(pa_context_set_sink_input_volume(c, i->index, &patt.attenuated_volume, NULL, NULL));

			// store it
			pas->qhVolumes[i->index] = patt;
		}

	} else if (eol < 0) {
		qWarning("PulseAudio: Sink input introspection error.");
	}
}
示例#15
0
文件: backend.c 项目: chrippa/xmms2
int xmms_pulse_backend_volume_set (xmms_pulse *p, unsigned int vol)
{
	pa_operation *o;
	pa_cvolume cvol;
	int idx, res = 0;

	if (p == NULL) {
		return FALSE;
	}

	pa_threaded_mainloop_lock (p->mainloop);

	if (p->stream != NULL) {
		pa_cvolume_set (&cvol, p->sample_spec.channels,
		                PA_VOLUME_NORM * vol / 100);

		idx = pa_stream_get_index (p->stream);

		o = pa_context_set_sink_input_volume (p->context, idx, &cvol,
		                                      volume_set_cb, &res);
		if (o) {
			/* wait for result to land */
			while (pa_operation_get_state (o) != PA_OPERATION_DONE) {
				pa_threaded_mainloop_wait (p->mainloop);
			}

			pa_operation_unref (o);

			/* The cb set res to 1 or 0 depending on success */
			if (res) {
				p->volume = vol;
			}
		}

	}

	pa_threaded_mainloop_unlock (p->mainloop);

	return res;
}
示例#16
0
/**
 * Volume adjusted (from within showtime), send update to PA
 *
 * Lock for PA mainloop is already held
 */
static void
set_mastervol(void *opaque, float value)
{
  pa_audio_mode_t *pam = opaque;
  pa_operation *o;
  pa_cvolume cv;

  pam->mastervol = pa_sw_volume_from_dB(value);

  if(pam->stream == NULL ||
     pa_stream_get_state(pam->stream) != PA_STREAM_READY)
    return;
  
  memset(&cv, 0, sizeof(cv));
  pa_cvolume_set(&cv, pam->ss.channels, pam->mastervol);

  o = pa_context_set_sink_input_volume(pam->context, 
				       pa_stream_get_index(pam->stream),
				       &cv, NULL, NULL);
  if(o != NULL)
    pa_operation_unref(o);
}
示例#17
0
void backend_volume_setall(context_t *c, backend_entry_type type, uint32_t idx, int *v, int chnum) {
    pa_cvolume volume;
    volume.channels = chnum;
    for(int i = 0; i < chnum; ++i) {
        volume.values[i] = v[i];
    }
    switch(type) {
        case SINK:
            pa_context_set_sink_volume_by_index(c->context, idx, &volume, NULL, NULL);
            break;
        case SINK_INPUT:
            pa_context_set_sink_input_volume(c->context, idx, &volume, NULL, NULL);
            break;
        case SOURCE:
            pa_context_set_source_volume_by_index(c->context, idx, &volume, NULL, NULL);
            break;
        case SOURCE_OUTPUT:
            pa_context_set_source_output_volume(c->context, idx, &volume, NULL, NULL);
            break;
        default:
            break;
    }
}
示例#18
0
void CAESinkPULSE::SetVolume(float volume)
{
  if (m_IsAllocated && !m_passthrough)
  {
    pa_threaded_mainloop_lock(m_MainLoop);
    // clamp possibly too large / low values
    float per_cent_volume = std::max(0.0f, std::min(volume, 1.0f));
    
    if (m_volume_needs_update)
    {
       m_volume_needs_update = false;
       pa_volume_t n_vol = pa_cvolume_avg(&m_Volume); 
       n_vol = std::min(n_vol, PA_VOLUME_NORM);
       per_cent_volume = (float) n_vol / PA_VOLUME_NORM; 
       // only update internal volume
       pa_threaded_mainloop_unlock(m_MainLoop);
       g_application.SetVolume(per_cent_volume, false);
       return;
    }
    
    pa_volume_t pavolume = per_cent_volume * PA_VOLUME_NORM;
    unsigned int sink_input_idx = pa_stream_get_index(m_Stream);
    
    if ( pavolume <= 0 )
      pa_cvolume_mute(&m_Volume, m_Channels);
    else
      pa_cvolume_set(&m_Volume, m_Channels, pavolume);
        
      pa_operation *op = pa_context_set_sink_input_volume(m_Context, sink_input_idx, &m_Volume, NULL, NULL);
      if (op == NULL)
        CLog::Log(LOGERROR, "PulseAudio: Failed to set volume");
      else
        pa_operation_unref(op);

    pa_threaded_mainloop_unlock(m_MainLoop);
  }
}
示例#19
0
void PulseAudioSystem::restore_sink_input_list_callback(pa_context *c, const pa_sink_input_info *i, int eol, void *userdata) {
	PulseAudioSystem *pas = reinterpret_cast<PulseAudioSystem *>(userdata);

	if (eol == 0) {
		// if we were tracking this specific sink previously
		if (pas->qhVolumes.contains(i->index)) {
			// and if it has the attenuated volume we applied to it
			if (pa_cvolume_equal(&i->volume, &pas->qhVolumes[i->index].attenuated_volume) != 0) {
				// mark it as matched
				pas->qlMatchedSinks.append(i->index);

				// reset the volume to normal
				pas->iRemainingOperations++;
				pa_operation_unref(pa_context_set_sink_input_volume(c, i->index, &pas->qhVolumes[i->index].normal_volume, restore_volume_success_callback, pas));
			}

		// otherwise, save for matching at the end of iteration
		} else {
			QString restore_id = QLatin1String(pa_proplist_gets(i->proplist, "module-stream-restore.id"));
			PulseAttenuation patt;
			patt.index = i->index;
			patt.normal_volume = i->volume;
			pas->qhUnmatchedSinks[restore_id] = patt;
		}

	} else if (eol < 0) {
		qWarning("PulseAudio: Sink input introspection error.");

	} else {
		// build a list of missing streams by iterating our active list
		QHash<uint32_t, PulseAttenuation>::const_iterator it;
		for (it = pas->qhVolumes.constBegin(); it != pas->qhVolumes.constEnd(); ++it) {
			// skip if previously matched
			if (pas->qlMatchedSinks.contains(it.key())) {
				continue;
			}

			// check if the restore id matches. the only case where this would
			// happen is if the application was reopened during attenuation.
			if (pas->qhUnmatchedSinks.contains(it.value().stream_restore_id)) {
				PulseAttenuation active_sink = pas->qhUnmatchedSinks[it.value().stream_restore_id];
				// if the volume wasn't changed from our attenuation
				if (pa_cvolume_equal(&active_sink.normal_volume, &it.value().attenuated_volume) != 0) {
					// reset the volume to normal
					pas->iRemainingOperations++;
					pa_operation_unref(pa_context_set_sink_input_volume(c, active_sink.index, &it.value().normal_volume, restore_volume_success_callback, pas));
				}
				continue;
			}

			// at this point, we don't know what happened to the sink. add
			// it to a list to check the stream restore database for.
			pas->qhMissingSinks[it.value().stream_restore_id] = it.value();
		}

		// clean up
		pas->qlMatchedSinks.clear();
		pas->qhUnmatchedSinks.clear();
		pas->qhVolumes.clear();

		// if we had missing sinks, check the stream restore database
		// to see if we can find and update them.
		if (pas->qhMissingSinks.count() > 0) {
			pas->iRemainingOperations++;
			pa_operation_unref(pa_ext_stream_restore_read(c, stream_restore_read_callback, pas));
		}

		// trigger the volume completion callback;
		// necessary so that shutdown actions are called
		restore_volume_success_callback(c, 1, pas);
	}
}
示例#20
0
static int control(struct ao *ao, enum aocontrol cmd, void *arg)
{
    struct priv *priv = ao->priv;
    switch (cmd) {
    case AOCONTROL_GET_MUTE:
    case AOCONTROL_GET_VOLUME: {
        uint32_t devidx = pa_stream_get_index(priv->stream);
        pa_threaded_mainloop_lock(priv->mainloop);
        if (!waitop(priv, pa_context_get_sink_input_info(priv->context, devidx,
                                                         info_func, ao))) {
            GENERIC_ERR_MSG(priv->context,
                            "pa_stream_get_sink_input_info() failed");
            return CONTROL_ERROR;
        }
        // Warning: some information in pi might be unaccessible, because
        // we naively copied the struct, without updating pointers etc.
        // Pointers might point to invalid data, accessors might fail.
        if (cmd == AOCONTROL_GET_VOLUME) {
            ao_control_vol_t *vol = arg;
            if (priv->pi.volume.channels != 2)
                vol->left = vol->right =
                    pa_cvolume_avg(&priv->pi.volume) * 100 / PA_VOLUME_NORM;
            else {
                vol->left = priv->pi.volume.values[0] * 100 / PA_VOLUME_NORM;
                vol->right = priv->pi.volume.values[1] * 100 / PA_VOLUME_NORM;
            }
        } else if (cmd == AOCONTROL_GET_MUTE) {
            bool *mute = arg;
            *mute = priv->pi.mute;
        }
        return CONTROL_OK;
    }

    case AOCONTROL_SET_MUTE:
    case AOCONTROL_SET_VOLUME: {
        pa_operation *o;

        pa_threaded_mainloop_lock(priv->mainloop);
        uint32_t stream_index = pa_stream_get_index(priv->stream);
        if (cmd == AOCONTROL_SET_VOLUME) {
            const ao_control_vol_t *vol = arg;
            struct pa_cvolume volume;

            pa_cvolume_reset(&volume, ao->channels);
            if (volume.channels != 2)
                pa_cvolume_set(&volume, volume.channels,
                               vol->left * PA_VOLUME_NORM / 100);
            else {
                volume.values[0] = vol->left * PA_VOLUME_NORM / 100;
                volume.values[1] = vol->right * PA_VOLUME_NORM / 100;
            }
            o = pa_context_set_sink_input_volume(priv->context, stream_index,
                                                 &volume, NULL, NULL);
            if (!o) {
                pa_threaded_mainloop_unlock(priv->mainloop);
                GENERIC_ERR_MSG(priv->context,
                                "pa_context_set_sink_input_volume() failed");
                return CONTROL_ERROR;
            }
        } else if (cmd == AOCONTROL_SET_MUTE) {
            const bool *mute = arg;
            o = pa_context_set_sink_input_mute(priv->context, stream_index,
                                               *mute, NULL, NULL);
            if (!o) {
                pa_threaded_mainloop_unlock(priv->mainloop);
                GENERIC_ERR_MSG(priv->context,
                                "pa_context_set_sink_input_mute() failed");
                return CONTROL_ERROR;
            }
        } else
            abort();
        /* We don't wait for completion here */
        pa_operation_unref(o);
        pa_threaded_mainloop_unlock(priv->mainloop);
        return CONTROL_OK;
    }
    default:
        return CONTROL_UNKNOWN;
    }
}