diff options
| author | trialuser02 <trialuser02@90c681e8-e032-0410-971d-27865f9a5e38> | 2018-11-10 14:41:38 +0000 |
|---|---|---|
| committer | trialuser02 <trialuser02@90c681e8-e032-0410-971d-27865f9a5e38> | 2018-11-10 14:41:38 +0000 |
| commit | f75b313569a44f191c1214842a4f93c3319521ce (patch) | |
| tree | 2e18e0f6ffe76c63b8b8c0abd9f957010b074fe6 /src/plugins/Output/pulseaudio/outputpulseaudio.cpp | |
| parent | e7cbfaf4e7b7573c25e440fbd17057dabe6e1b47 (diff) | |
| download | qmmp-f75b313569a44f191c1214842a4f93c3319521ce.tar.gz qmmp-f75b313569a44f191c1214842a4f93c3319521ce.tar.bz2 qmmp-f75b313569a44f191c1214842a4f93c3319521ce.zip | |
added PulseAudio volume control (#497)
git-svn-id: http://svn.code.sf.net/p/qmmp-dev/code/trunk/qmmp@8413 90c681e8-e032-0410-971d-27865f9a5e38
Diffstat (limited to 'src/plugins/Output/pulseaudio/outputpulseaudio.cpp')
| -rw-r--r-- | src/plugins/Output/pulseaudio/outputpulseaudio.cpp | 345 |
1 files changed, 302 insertions, 43 deletions
diff --git a/src/plugins/Output/pulseaudio/outputpulseaudio.cpp b/src/plugins/Output/pulseaudio/outputpulseaudio.cpp index 690dd6b63..fbe42aaa2 100644 --- a/src/plugins/Output/pulseaudio/outputpulseaudio.cpp +++ b/src/plugins/Output/pulseaudio/outputpulseaudio.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright (C) 2006-2014 by Ilya Kotov * + * Copyright (C) 2006-2018 by Ilya Kotov * * forkotov02@ya.ru * * * * This program is free software; you can redistribute it and/or modify * @@ -21,11 +21,20 @@ extern "C"{ #include <pulse/error.h> } +#include <math.h> +#include <QThread> +#include <QMetaObject> #include "outputpulseaudio.h" +OutputPulseAudio *OutputPulseAudio::instance = 0; +VolumePulseAudio *OutputPulseAudio::volumeControl = 0; + OutputPulseAudio::OutputPulseAudio(): Output() { - m_connection = 0; + //m_connection = 0; + m_loop = 0; + m_ctx = 0; + m_stream = 0; m_pa_channels[Qmmp::CHAN_NULL] = PA_CHANNEL_POSITION_INVALID; m_pa_channels[Qmmp::CHAN_FRONT_CENTER] = PA_CHANNEL_POSITION_MONO; @@ -38,15 +47,48 @@ OutputPulseAudio::OutputPulseAudio(): Output() m_pa_channels[Qmmp::CHAN_SIDE_LEFT] = PA_CHANNEL_POSITION_SIDE_LEFT; m_pa_channels[Qmmp::CHAN_SIDE_RIGHT] = PA_CHANNEL_POSITION_SIDE_RIGHT; m_pa_channels[Qmmp::CHAN_REAR_CENTER] = PA_CHANNEL_POSITION_REAR_CENTER; + + instance = this; } OutputPulseAudio::~OutputPulseAudio() { uninitialize(); + instance = 0; } bool OutputPulseAudio::initialize(quint32 freq, ChannelMap map, Qmmp::AudioFormat format) { + if(!(m_loop = pa_mainloop_new())) + { + qWarning("OutputPulseAudio: unable to allocate a new main loop object"); + return false; + } + + if(!(m_ctx = pa_context_new(pa_mainloop_get_api(m_loop), "Qmmp"))) + { + qWarning("OutputPulseAudio: unable to instantiate a new connection context"); + return false; + } + + if(pa_context_connect(m_ctx, 0, (pa_context_flags_t)0, 0) < 0) + { + qWarning("OutputPulseAudio: unable to connect the context: %s", pa_strerror(pa_context_errno(m_ctx))); + return false; + } + + //waiting context connection + pa_context_state_t context_state = PA_CONTEXT_UNCONNECTED; + while((context_state = pa_context_get_state(m_ctx)) != PA_CONTEXT_READY) + { + if (context_state == PA_CONTEXT_TERMINATED || context_state == PA_CONTEXT_FAILED) + { + qWarning("OutputPulseAudio: unable to connect the context: %s", pa_strerror(pa_context_errno(m_ctx))); + return false; + } + poll(); + } + pa_sample_spec ss; switch (format) @@ -69,82 +111,299 @@ bool OutputPulseAudio::initialize(quint32 freq, ChannelMap map, Qmmp::AudioForma ss.channels = map.count(); ss.rate = freq; - int error = 0; pa_channel_map pa_map; pa_map.channels = map.count(); for(int i = 0; i < map.count(); i++) - { pa_map.map[i] = m_pa_channels[map.value(i)]; + + if(!(m_stream = pa_stream_new(m_ctx, "Qmmp", &ss, &pa_map))) + { + qWarning("OutputPulseAudio: unable to create stream: %s", pa_strerror(pa_context_errno(m_ctx))); + return false; } - m_connection = pa_simple_new(NULL, // Use the default server. - "Qmmp", // Our application's name. - PA_STREAM_PLAYBACK, - NULL, // Use the default device. - "Music", // Description of our stream. - &ss, // Our sample format. - &pa_map, // Our channel map - NULL, // Use default buffering attributes. - &error // Error code. - ); - if (!m_connection) + pa_buffer_attr attr; + attr.maxlength = (uint32_t) -1; + attr.tlength = (uint32_t) -1;//pa_usec_to_bytes((pa_usec_t)50 * 1000, &ss); //50 ms + attr.prebuf = (uint32_t) -1; + attr.minreq = (uint32_t) -1; + attr.fragsize = attr.tlength; + + pa_stream_flags_t flags = pa_stream_flags_t(PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_AUTO_TIMING_UPDATE); + if(pa_stream_connect_playback(m_stream, 0, &attr, flags, 0, 0) < 0) { - qWarning("OutputPulseAudio: pa_simple_new() failed: %s", pa_strerror(error)); + qWarning("OutputPulseAudio: unable to connect playback: %s", pa_strerror(pa_context_errno(m_ctx))); return false; } + + //waiting stream connection + pa_stream_state_t stream_state = PA_STREAM_UNCONNECTED; + while((stream_state = pa_stream_get_state(m_stream)) != PA_STREAM_READY) + { + if (stream_state == PA_STREAM_FAILED || stream_state == PA_STREAM_TERMINATED) + { + qWarning("OutputPulseAudio: unable to connect playback: %s", pa_strerror(pa_context_errno(m_ctx))); + return false; + } + poll(); + } + + pa_context_set_subscribe_callback(m_ctx, OutputPulseAudio::subscribe_cb, this); + + bool success = false; + pa_operation *op = pa_context_subscribe(m_ctx, PA_SUBSCRIPTION_MASK_SINK_INPUT, OutputPulseAudio::context_success_cb, &success); + if(!op) + { + qWarning("OutputPulseAudio: pa_context_subscribe failed: %s", pa_strerror(pa_context_errno(m_ctx))); + return false; + } + + pa_operation_state_t op_state; + while((op_state = pa_operation_get_state(op)) != PA_OPERATION_DONE && isReady()) + { + poll(); + } + pa_operation_unref(op); + + if(op_state != PA_OPERATION_DONE || !success) + { + qWarning("OutputPulseAudio: pa_context_subscribe failed: %s", pa_strerror(pa_context_errno(m_ctx))); + return false; + } + success = false; + if(!(op = pa_context_get_sink_input_info(m_ctx, pa_stream_get_index(m_stream), OutputPulseAudio::info_cb, &success))) + { + qWarning("OutputPulseAudio: pa_context_get_sink_input_info failed: %s", pa_strerror(pa_context_errno(m_ctx))); + return false; + } + + while((op_state = pa_operation_get_state(op)) != PA_OPERATION_DONE && isReady()) + { + poll(); + } + pa_operation_unref(op); + + if(op_state != PA_OPERATION_DONE || !success) + { + qWarning("OutputPulseAudio: pa_context_get_sink_input_info failed: %s", pa_strerror(pa_context_errno(m_ctx))); + return false; + } + Output::configure(freq, map, format); return true; } qint64 OutputPulseAudio::latency() { - if (!m_connection) - return 0; - int error = 0; - qint64 delay = pa_simple_get_latency(m_connection, &error)/1000; - if (error) - { - qWarning("OutputPulseAudio: %s", pa_strerror (error)); - delay = 0; - } - return delay; + pa_usec_t usec; + int negative; + + int r = pa_stream_get_latency(m_stream, &usec, &negative); + return (r == PA_OK && negative == 0) ? (usec / 1000) : 0; } qint64 OutputPulseAudio::writeAudio(unsigned char *data, qint64 maxSize) { - int error; - if (!m_connection) - return -1; - int i = 0; - if ((i = pa_simple_write(m_connection, data, maxSize, &error)) < 0) + /*bool success = false; + pa_operation *op = pa_stream_trigger(m_stream, OutputPulseAudio::stream_success_cb, &success); + + while(pa_operation_get_state (op) != PA_OPERATION_DONE && isReady()) + { + pa_mainloop_prepare(m_loop, -1); + pa_mainloop_poll(m_loop); + pa_mainloop_dispatch(m_loop); + + } + pa_operation_unref (op);*/ + + while(!pa_stream_writable_size(m_stream) || !isReady()) + { + poll(); + } + + size_t length = qMin(size_t(maxSize), pa_stream_writable_size(m_stream)); + if(pa_stream_write(m_stream, data, length, 0, 0, PA_SEEK_RELATIVE) < 0) { - qWarning("OutputPulseAudio: pa_simple_write() failed: %s", pa_strerror(error)); + qWarning("OutputPulseAudio: pa_stream_write failed: %s", pa_strerror(pa_context_errno(m_ctx))); return -1; } - return maxSize; + return qint64(length); } void OutputPulseAudio::drain() { - int error; - if (m_connection) - pa_simple_drain(m_connection, &error); + pa_operation *op = pa_stream_drain(m_stream, OutputPulseAudio::stream_success_cb, 0); + + while(pa_operation_get_state(op) != PA_OPERATION_DONE && isReady()) + { + poll(); + } + pa_operation_unref(op); } void OutputPulseAudio::reset() { - int error; - if (m_connection) - pa_simple_flush(m_connection, &error); + pa_operation *op = pa_stream_flush(m_stream, OutputPulseAudio::stream_success_cb, 0); + + while(pa_operation_get_state(op) != PA_OPERATION_DONE && isReady()) + { + poll(); + } + pa_operation_unref(op); +} + +void OutputPulseAudio::setVolume(const VolumeSettings &v) +{ + pa_cvolume volume; + + if(audioParameters().channels() == 2) + { + volume.values[0] = v.left * PA_VOLUME_NORM / 100; + volume.values[1] = v.right * PA_VOLUME_NORM / 100; + volume.channels = 2; + } + else + { + for(int i = 0; i < audioParameters().channels(); ++i) + volume.values[i] = qMax(v.left, v.right) * PA_VOLUME_NORM / 100; + volume.channels = audioParameters().channels(); + } + + pa_operation *op = pa_context_set_sink_input_volume(m_ctx, pa_stream_get_index(m_stream), &volume, OutputPulseAudio::context_success_cb, 0); + + /*while(pa_operation_get_state(op) != PA_OPERATION_DONE && isReady()) + { + pa_mainloop_prepare(m_loop, -1); + pa_mainloop_poll(m_loop); + pa_mainloop_dispatch(m_loop); + }*/ + pa_operation_unref(op); } void OutputPulseAudio::uninitialize() +{ + if(m_stream) + { + pa_stream_disconnect(m_stream); + pa_stream_unref(m_stream); + m_stream = 0; + } + + if(m_ctx) + { + pa_context_disconnect(m_ctx); + pa_context_unref(m_ctx); + m_ctx = 0; + } + + if(m_loop) + { + pa_mainloop_free(m_loop); + m_loop = 0; + } +} + +bool OutputPulseAudio::isReady() const +{ + return m_ctx && m_stream && + pa_context_get_state(m_ctx) == PA_CONTEXT_READY && + pa_stream_get_state(m_stream) == PA_STREAM_READY; +} + +void OutputPulseAudio::poll() +{ + pa_mainloop_prepare(m_loop, -1); + pa_mainloop_poll(m_loop); + pa_mainloop_dispatch(m_loop); +} +//callbacks +void OutputPulseAudio::subscribe_cb(pa_context *ctx, pa_subscription_event_type t, uint32_t index, void *data) +{ + pa_operation *op; + OutputPulseAudio *output = (OutputPulseAudio *)data; + + if (!output || !output->m_stream || index != pa_stream_get_index(output->m_stream) || + (t != (PA_SUBSCRIPTION_EVENT_SINK_INPUT | PA_SUBSCRIPTION_EVENT_NEW) && + t != (PA_SUBSCRIPTION_EVENT_SINK_INPUT | PA_SUBSCRIPTION_EVENT_CHANGE))) + return; + + if(!(op = pa_context_get_sink_input_info (ctx, index, OutputPulseAudio::info_cb, 0))) + { + qWarning("OutputPulseAudio: pa_context_get_sink_input_info failed: %s", pa_strerror(pa_context_errno(ctx))); + return; + } + + pa_operation_unref(op); +} + +void OutputPulseAudio::info_cb(pa_context *, const pa_sink_input_info *info, int, void *data) { - if (m_connection) + if(!info) + return; + + if(volumeControl) + volumeControl->updateVolume(info->volume); + + if(data) + *(bool *) data = true; +} + +void OutputPulseAudio::context_success_cb(pa_context *, int success, void *data) +{ + if(data) + *(bool *)data = success != 0; +} + +void OutputPulseAudio::stream_success_cb(pa_stream *, int success, void *data) +{ + if(data) + *(bool *)data = success != 0; +} + +//volume control +VolumePulseAudio::VolumePulseAudio() +{ + OutputPulseAudio::volumeControl = this; +} + +VolumePulseAudio::~VolumePulseAudio() +{ + OutputPulseAudio::volumeControl = 0; +} + +void VolumePulseAudio::updateVolume(const pa_cvolume &v) +{ + if(v.channels == 2) { - qDebug("OutputPulseAudio: closing connection"); - pa_simple_free(m_connection); - m_connection = 0; + m_volume.left = ceilf(float(v.values[0]) * 100 / PA_VOLUME_NORM); + m_volume.right = ceilf(float(v.values[1]) * 100 / PA_VOLUME_NORM); } + else + { + m_volume.left = ceilf(float(pa_cvolume_avg(&v)) * 100 / PA_VOLUME_NORM); + m_volume.right = m_volume.left; + } + emit changed(); +} + +void VolumePulseAudio::setVolume(const VolumeSettings &vol) +{ + if(OutputPulseAudio::instance) + OutputPulseAudio::instance->setVolume(vol); +} + +VolumeSettings VolumePulseAudio::volume() const +{ + return m_volume; +} + +void VolumePulseAudio::restore() +{ + +} + +bool VolumePulseAudio::hasNotifySignal() const +{ + return true; } |
