Refactor gstreamer engine code, equalizer and fix stereo balancer

This commit is contained in:
Jonas Kvinge 2019-11-08 23:07:21 +01:00
parent d033b79af4
commit 834877c503
10 changed files with 430 additions and 393 deletions

View File

@ -200,12 +200,13 @@ void Player::Init() {
connect(engine_.get(), SIGNAL(MetaData(Engine::SimpleMetaBundle)), SLOT(EngineMetadataReceived(Engine::SimpleMetaBundle)));
// Equalizer
qLog(Debug) << "Creating equalizer";
connect(equalizer_, SIGNAL(ParametersChanged(int,QList<int>)), app_->player()->engine(), SLOT(SetEqualizerParameters(int,QList<int>)));
connect(equalizer_, SIGNAL(EnabledChanged(bool)), app_->player()->engine(), SLOT(SetEqualizerEnabled(bool)));
connect(equalizer_, SIGNAL(StereoBalanceChanged(bool, float)), app_->player()->engine(), SLOT(SetStereoBalance(bool, float)));
connect(equalizer_, SIGNAL(StereoBalancerEnabledChanged(bool)), app_->player()->engine(), SLOT(SetStereoBalancerEnabled(bool)));
connect(equalizer_, SIGNAL(StereoBalanceChanged(float)), app_->player()->engine(), SLOT(SetStereoBalance(float)));
connect(equalizer_, SIGNAL(EqualizerEnabledChanged(bool)), app_->player()->engine(), SLOT(SetEqualizerEnabled(bool)));
connect(equalizer_, SIGNAL(EqualizerParametersChanged(int, QList<int>)), app_->player()->engine(), SLOT(SetEqualizerParameters(int, QList<int>)));
engine_->SetStereoBalance(equalizer_->is_stereo_balancer_enabled(), equalizer_->stereo_balance());
engine_->SetStereoBalancerEnabled(equalizer_->is_stereo_balancer_enabled());
engine_->SetStereoBalance(equalizer_->stereo_balance());
engine_->SetEqualizerEnabled(equalizer_->is_equalizer_enabled());
engine_->SetEqualizerParameters(equalizer_->preamp_value(), equalizer_->gain_values());

View File

@ -82,7 +82,7 @@ bool Engine::Base::Play(const QUrl &stream_url, const QUrl &original_url, TrackC
}
void Engine::Base::SetVolume(uint value) {
void Engine::Base::SetVolume(const uint value) {
volume_ = value;
SetVolumeSW(MakeVolumeLogarithmic(value));

View File

@ -126,9 +126,10 @@ public:
QVariant device() { return device_; }
public slots:
virtual void SetStereoBalancerEnabled(const bool) {}
virtual void SetStereoBalance(const float) {}
virtual void SetEqualizerEnabled(const bool) {}
virtual void SetEqualizerParameters(const int preamp, const QList<int> &bandGains) { Q_UNUSED(preamp); Q_UNUSED(bandGains); }
virtual void SetStereoBalance(const bool enabled, const float value) { Q_UNUSED(enabled); Q_UNUSED(value); }
virtual void SetEqualizerParameters(const int, const QList<int>&) {}
signals:
// Emitted when crossfading is enabled and the track is crossfade_duration_ away from finishing

View File

@ -85,6 +85,8 @@ GstEngine::GstEngine(TaskManager *task_manager)
latest_buffer_(nullptr),
stereo_balancer_enabled_(false),
stereo_balance_(0.0f),
equalizer_enabled_(false),
equalizer_preamp_(0),
seek_timer_(new QTimer(this)),
timer_id_(-1),
next_element_id_(0),
@ -103,12 +105,15 @@ GstEngine::GstEngine(TaskManager *task_manager)
}
GstEngine::~GstEngine() {
EnsureInitialised();
current_pipeline_.reset();
if (latest_buffer_) {
gst_buffer_unref(latest_buffer_);
latest_buffer_ = nullptr;
}
}
bool GstEngine::Init() {
@ -175,7 +180,7 @@ bool GstEngine::Load(const QUrl &stream_url, const QUrl &original_url, Engine::T
current_pipeline_ = pipeline;
SetVolume(volume_);
SetStereoBalance(stereo_balancer_enabled_, stereo_balance_);
SetStereoBalance(stereo_balance_);
SetEqualizerParameters(equalizer_preamp_, equalizer_gains_);
// Maybe fade in this track
@ -425,11 +430,25 @@ void GstEngine::ConsumeBuffer(GstBuffer *buffer, const int pipeline_id, const QS
}
void GstEngine::SetStereoBalancerEnabled(const bool enabled) {
stereo_balancer_enabled_ = enabled;
if (current_pipeline_) current_pipeline_->set_stereo_balancer_enabled(enabled);
}
void GstEngine::SetStereoBalance(const float value) {
stereo_balance_ = value;
if (current_pipeline_) current_pipeline_->SetStereoBalance(value);
}
void GstEngine::SetEqualizerEnabled(const bool enabled) {
equalizer_enabled_ = enabled;
if (current_pipeline_) current_pipeline_->set_equalizer_enabled(enabled);
if (current_pipeline_) current_pipeline_->SetEqualizerEnabled(enabled);
}
void GstEngine::SetEqualizerParameters(const int preamp, const QList<int> &band_gains) {
@ -437,27 +456,22 @@ void GstEngine::SetEqualizerParameters(const int preamp, const QList<int> &band_
equalizer_preamp_ = preamp;
equalizer_gains_ = band_gains;
if (current_pipeline_)
current_pipeline_->SetEqualizerParams(preamp, band_gains);
}
void GstEngine::SetStereoBalance(const bool enabled, const float value) {
stereo_balance_ = value;
if (current_pipeline_) current_pipeline_->SetStereoBalance(enabled, value);
if (current_pipeline_) current_pipeline_->SetEqualizerParams(preamp, band_gains);
}
void GstEngine::AddBufferConsumer(GstBufferConsumer *consumer) {
buffer_consumers_ << consumer;
if (current_pipeline_) current_pipeline_->AddBufferConsumer(consumer);
}
void GstEngine::RemoveBufferConsumer(GstBufferConsumer *consumer) {
buffer_consumers_.removeAll(consumer);
if (current_pipeline_) current_pipeline_->RemoveBufferConsumer(consumer);
}
void GstEngine::timerEvent(QTimerEvent *e) {
@ -745,11 +759,12 @@ shared_ptr<GstEnginePipeline> GstEngine::CreatePipeline() {
shared_ptr<GstEnginePipeline> ret(new GstEnginePipeline(this));
ret->set_output_device(output_, device_);
ret->set_volume_control(volume_control_);
ret->set_volume_enabled(volume_control_);
ret->set_stereo_balancer_enabled(stereo_balancer_enabled_);
ret->set_equalizer_enabled(equalizer_enabled_);
ret->set_replaygain(rg_enabled_, rg_mode_, rg_preamp_, rg_compression_);
ret->set_buffer_duration_nanosec(buffer_duration_nanosec_);
ret->set_buffer_min_fill(buffer_min_fill_);
ret->SetEqualizerEnabled(equalizer_enabled_);
ret->AddBufferConsumer(this);
for (GstBufferConsumer *consumer : buffer_consumers_) {

View File

@ -95,15 +95,18 @@ class GstEngine : public Engine::Base, public GstBufferConsumer {
public slots:
void ReloadSettings();
/** Set whether equalizer is enabled */
// Set whether stereo balancer is enabled
void SetStereoBalancerEnabled(const bool enabled);
// Set Stereo balance, range -1.0f..1.0f
void SetStereoBalance(const float value);
// Set whether equalizer is enabled
void SetEqualizerEnabled(const bool);
/** Set equalizer preamp and gains, range -100..100. Gains are 10 values. */
// Set equalizer preamp and gains, range -100..100. Gains are 10 values.
void SetEqualizerParameters(const int preamp, const QList<int> &bandGains);
/** Set Stereo balance, range -1.0f..1.0f */
void SetStereoBalance(const bool enabled, const float value);
void AddBufferConsumer(GstBufferConsumer *consumer);
void RemoveBufferConsumer(GstBufferConsumer *consumer);
@ -173,6 +176,7 @@ class GstEngine : public Engine::Base, public GstBufferConsumer {
bool stereo_balancer_enabled_;
float stereo_balance_;
bool equalizer_enabled_;
int equalizer_preamp_;
QList<int> equalizer_gains_;

View File

@ -67,12 +67,12 @@ GstEnginePipeline::GstEnginePipeline(GstEngine *engine)
engine_(engine),
id_(sId++),
valid_(false),
volume_control_(true),
stereo_balance_enabled_(false),
stereo_balance_(0.0f),
volume_enabled_(true),
stereo_balancer_enabled_(false),
eq_enabled_(false),
eq_preamp_(0),
rg_enabled_(false),
stereo_balance_(0.0f),
eq_preamp_(0),
rg_mode_(0),
rg_preamp_(0.0),
rg_compression_(true),
@ -89,9 +89,10 @@ GstEnginePipeline::GstEnginePipeline(GstEngine *engine)
pipeline_is_initialised_(false),
pipeline_is_connected_(false),
pending_seek_nanosec_(-1),
last_known_position_ns_(0),
next_uri_set_(false),
volume_percent_(100),
volume_modifier_(1.0),
volume_modifier_(1.0f),
use_fudge_timer_(false),
pipeline_(nullptr),
audiobin_(nullptr),
@ -101,9 +102,9 @@ GstEnginePipeline::GstEnginePipeline(GstEngine *engine)
equalizer_(nullptr),
equalizer_preamp_(nullptr),
discoverer_(nullptr),
about_to_finish_cb_id_(-1),
pad_added_cb_id_(-1),
notify_source_cb_id_(-1),
about_to_finish_cb_id_(-1),
bus_cb_id_(-1),
discovery_finished_cb_id_(-1),
discovery_discovered_cb_id_(-1)
@ -131,15 +132,15 @@ GstEnginePipeline::~GstEnginePipeline() {
if (pipeline_) {
if (about_to_finish_cb_id_ != -1)
g_signal_handler_disconnect(G_OBJECT(pipeline_), about_to_finish_cb_id_);
if (pad_added_cb_id_ != -1)
g_signal_handler_disconnect(G_OBJECT(pipeline_), pad_added_cb_id_);
if (notify_source_cb_id_ != -1)
g_signal_handler_disconnect(G_OBJECT(pipeline_), notify_source_cb_id_);
if (about_to_finish_cb_id_ != -1)
g_signal_handler_disconnect(G_OBJECT(pipeline_), about_to_finish_cb_id_);
gst_bus_set_sync_handler(gst_pipeline_get_bus(GST_PIPELINE(pipeline_)), nullptr, nullptr, nullptr);
if (bus_cb_id_ != -1)
@ -159,13 +160,22 @@ void GstEnginePipeline::set_output_device(const QString &output, const QVariant
}
void GstEnginePipeline::set_volume_control(bool volume_control) {
volume_control_ = volume_control;
void GstEnginePipeline::set_volume_enabled(const bool enabled) {
volume_enabled_ = enabled;
}
void GstEnginePipeline::set_replaygain(bool enabled, int mode, float preamp, bool compression) {
void GstEnginePipeline::set_stereo_balancer_enabled(const bool enabled) {
stereo_balancer_enabled_ = enabled;
if (!enabled) stereo_balance_ = 0.0f;
if (pipeline_) UpdateStereoBalance();
}
void GstEnginePipeline::set_equalizer_enabled(const bool enabled) {
eq_enabled_ = enabled;
if (pipeline_) UpdateEqualizer();
}
void GstEnginePipeline::set_replaygain(const bool enabled, const int mode, const float preamp, const bool compression) {
rg_enabled_ = enabled;
rg_mode_ = mode;
@ -182,6 +192,45 @@ void GstEnginePipeline::set_buffer_min_fill(int percent) {
buffer_min_fill_ = percent;
}
bool GstEnginePipeline::InitFromUrl(const QByteArray &stream_url, const QUrl original_url, const qint64 end_nanosec) {
stream_url_ = stream_url;
original_url_ = original_url;
end_offset_nanosec_ = end_nanosec;
pipeline_ = engine_->CreateElement("playbin");
if (!pipeline_) return false;
g_object_set(G_OBJECT(pipeline_), "uri", stream_url.constData(), nullptr);
gint flags;
g_object_get(G_OBJECT(pipeline_), "flags", &flags, nullptr);
flags |= 0x00000002;
flags &= ~0x00000001;
g_object_set(G_OBJECT(pipeline_), "flags", flags, nullptr);
pad_added_cb_id_ = CHECKED_GCONNECT(G_OBJECT(pipeline_), "pad-added", &NewPadCallback, this);
notify_source_cb_id_ = CHECKED_GCONNECT(G_OBJECT(pipeline_), "notify::source", &SourceSetupCallback, this);
about_to_finish_cb_id_ = CHECKED_GCONNECT(G_OBJECT(pipeline_), "about-to-finish", &AboutToFinishCallback, this);
// Setting up a discoverer
discoverer_ = gst_discoverer_new(kDiscoveryTimeoutS * GST_SECOND, nullptr);
if (discoverer_) {
discovery_discovered_cb_id_ = CHECKED_GCONNECT(G_OBJECT(discoverer_), "discovered", &StreamDiscovered, this);
discovery_finished_cb_id_ = CHECKED_GCONNECT(G_OBJECT(discoverer_), "finished", &StreamDiscoveryFinished, this);
gst_discoverer_start(discoverer_);
}
if (!InitAudioBin()) return false;
// Set playbin's sink to be our custom audio-sink.
g_object_set(GST_OBJECT(pipeline_), "audio-sink", audiobin_, nullptr);
pipeline_is_connected_ = true;
return true;
}
bool GstEnginePipeline::InitAudioBin() {
gst_segment_init(&last_playbin_segment_, GST_FORMAT_TIME);
@ -239,12 +288,12 @@ bool GstEnginePipeline::InitAudioBin() {
}
// Create the volume elements if it's enabled.
if (volume_control_) {
if (volume_enabled_) {
volume_ = engine_->CreateElement("volume", audiobin_);
}
// Create the stereo balancer elements if it's enabled.
if (stereo_balance_enabled_) {
if (stereo_balancer_enabled_) {
audiopanorama_ = engine_->CreateElement("audiopanorama", audiobin_, false);
// Set the stereo balance.
if (audiopanorama_) g_object_set(G_OBJECT(audiopanorama_), "panorama", stereo_balance_, nullptr);
@ -293,13 +342,13 @@ bool GstEnginePipeline::InitAudioBin() {
GstElement *eventprobe = audioqueue_;
GstElement *rgvolume = nullptr;
GstElement *rglimiter = nullptr;
GstElement *audioconverter2 = nullptr;
GstElement *rgconverter = nullptr;
if (rg_enabled_) {
rgvolume = engine_->CreateElement("rgvolume", audiobin_, false);
rglimiter = engine_->CreateElement("rglimiter", audiobin_, false);
audioconverter2 = engine_->CreateElement("audioconvert", audiobin_, false);
if (rgvolume && rglimiter && audioconverter2) {
eventprobe = audioconverter2;
rgconverter = engine_->CreateElement("audioconvert", audiobin_, false);
if (rgvolume && rglimiter && rgconverter) {
eventprobe = rgconverter;
// Set replaygain settings
g_object_set(G_OBJECT(rgvolume), "album-mode", rg_mode_, nullptr);
g_object_set(G_OBJECT(rgvolume), "pre-amp", double(rg_preamp_), nullptr);
@ -335,9 +384,9 @@ bool GstEnginePipeline::InitAudioBin() {
GstElement *next = audioqueue_; // The next element to link from.
// Link replaygain elements if enabled.
if (rg_enabled_ && rgvolume && rglimiter && audioconverter2) {
gst_element_link_many(next, rgvolume, rglimiter, audioconverter2, nullptr);
next = audioconverter2;
if (rg_enabled_ && rgvolume && rglimiter && rgconverter) {
gst_element_link_many(next, rgvolume, rglimiter, rgconverter, nullptr);
next = rgconverter;
}
// Link equalizer elements if enabled.
@ -347,20 +396,19 @@ bool GstEnginePipeline::InitAudioBin() {
}
// Link equalizer elements if enabled.
if (stereo_balance_enabled_ && audiopanorama_) {
if (stereo_balancer_enabled_ && audiopanorama_) {
gst_element_link(next, audiopanorama_);
next = audiopanorama_;
}
// Link volume elements if enabled.
if (volume_control_ && volume_) {
if (volume_enabled_ && volume_) {
gst_element_link(next, volume_);
next = volume_;
}
gst_element_link(next, audioconverter);
// Let the audio output of the tee autonegotiate the bit depth and format.
GstCaps *caps = gst_caps_new_empty_simple("audio/x-raw");
gst_element_link_filtered(audioconverter, audiosink, caps);
gst_caps_unref(caps);
@ -386,42 +434,225 @@ bool GstEnginePipeline::InitAudioBin() {
}
bool GstEnginePipeline::InitFromUrl(const QByteArray &stream_url, const QUrl original_url, const qint64 end_nanosec) {
GstPadProbeReturn GstEnginePipeline::EventHandoffCallback(GstPad*, GstPadProbeInfo *info, gpointer self) {
stream_url_ = stream_url;
original_url_ = original_url;
end_offset_nanosec_ = end_nanosec;
GstEnginePipeline *instance = reinterpret_cast<GstEnginePipeline*>(self);
pipeline_ = engine_->CreateElement("playbin");
if (!pipeline_) return false;
GstEvent *e = gst_pad_probe_info_get_event(info);
g_object_set(G_OBJECT(pipeline_), "uri", stream_url.constData(), nullptr);
qLog(Debug) << instance->id() << "event" << GST_EVENT_TYPE_NAME(e);
gint flags;
g_object_get(G_OBJECT(pipeline_), "flags", &flags, nullptr);
flags |= 0x00000002;
flags &= ~0x00000001;
g_object_set(G_OBJECT(pipeline_), "flags", flags, nullptr);
switch (GST_EVENT_TYPE(e)) {
case GST_EVENT_SEGMENT:
if (!instance->segment_start_received_) {
// The segment start time is used to calculate the proper offset of data buffers from the start of the stream
const GstSegment *segment = nullptr;
gst_event_parse_segment(e, &segment);
instance->segment_start_ = segment->start;
instance->segment_start_received_ = true;
}
break;
about_to_finish_cb_id_ = CHECKED_GCONNECT(G_OBJECT(pipeline_), "about-to-finish", &AboutToFinishCallback, this);
pad_added_cb_id_ = CHECKED_GCONNECT(G_OBJECT(pipeline_), "pad-added", &NewPadCallback, this);
notify_source_cb_id_ = CHECKED_GCONNECT(G_OBJECT(pipeline_), "notify::source", &SourceSetupCallback, this);
// Setting up a discoverer
discoverer_ = gst_discoverer_new(kDiscoveryTimeoutS * GST_SECOND, nullptr);
if (discoverer_) {
discovery_discovered_cb_id_ = CHECKED_GCONNECT(G_OBJECT(discoverer_), "discovered", &StreamDiscovered, this);
discovery_finished_cb_id_ = CHECKED_GCONNECT(G_OBJECT(discoverer_), "finished", &StreamDiscoveryFinished, this);
gst_discoverer_start(discoverer_);
default:
break;
}
if (!InitAudioBin()) return false;
return GST_PAD_PROBE_OK;
// Set playbin's sink to be our costum audio-sink.
g_object_set(GST_OBJECT(pipeline_), "audio-sink", audiobin_, nullptr);
pipeline_is_connected_ = true;
}
return true;
void GstEnginePipeline::SourceSetupCallback(GstPlayBin *bin, GParamSpec *, gpointer self) {
GstEnginePipeline *instance = reinterpret_cast<GstEnginePipeline*>(self);
GstElement *element = nullptr;
g_object_get(bin, "source", &element, nullptr);
if (!element) {
return;
}
if (g_object_class_find_property(G_OBJECT_GET_CLASS(element), "device") && !instance->source_device().isEmpty()) {
// Gstreamer is not able to handle device in URL (referring to Gstreamer documentation, this might be added in the future).
// Despite that, for now we include device inside URL: we decompose it during Init and set device here, when this callback is called.
g_object_set(element, "device", instance->source_device().toLocal8Bit().constData(), nullptr);
}
if (g_object_class_find_property(G_OBJECT_GET_CLASS(element), "user-agent")) {
QString user_agent = QString("%1 %2").arg(QCoreApplication::applicationName(), QCoreApplication::applicationVersion());
g_object_set(element, "user-agent", user_agent.toUtf8().constData(), nullptr);
g_object_set(element, "ssl-strict", FALSE, nullptr);
}
// If the pipeline was buffering we stop that now.
if (instance->buffering_) {
instance->buffering_ = false;
emit instance->BufferingFinished();
instance->SetState(GST_STATE_PLAYING);
}
g_object_unref(element);
}
void GstEnginePipeline::NewPadCallback(GstElement*, GstPad *pad, gpointer self) {
GstEnginePipeline *instance = reinterpret_cast<GstEnginePipeline*>(self);
GstPad *const audiopad = gst_element_get_static_pad(instance->audiobin_, "sink");
// Link playbin's sink pad to audiobin's src pad.
if (GST_PAD_IS_LINKED(audiopad)) {
qLog(Warning) << instance->id() << "audiopad is already linked, unlinking old pad";
gst_pad_unlink(audiopad, GST_PAD_PEER(audiopad));
}
gst_pad_link(pad, audiopad);
gst_object_unref(audiopad);
// Offset the timestamps on all the buffers coming out of the playbin so they line up exactly with the end of the last buffer from the old playbin.
// "Running time" is the time since the last flushing seek.
GstClockTime running_time = gst_segment_to_running_time(&instance->last_playbin_segment_, GST_FORMAT_TIME, instance->last_playbin_segment_.position);
gst_pad_set_offset(pad, running_time);
// Add a probe to the pad so we can update last_playbin_segment_.
gst_pad_add_probe(pad, static_cast<GstPadProbeType>(GST_PAD_PROBE_TYPE_BUFFER | GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM | GST_PAD_PROBE_TYPE_EVENT_FLUSH), PlaybinProbe, instance, nullptr);
instance->pipeline_is_connected_ = true;
if (instance->pending_seek_nanosec_ != -1 && instance->pipeline_is_initialised_) {
QMetaObject::invokeMethod(instance, "Seek", Qt::QueuedConnection, Q_ARG(qint64, instance->pending_seek_nanosec_));
}
}
GstPadProbeReturn GstEnginePipeline::PlaybinProbe(GstPad *pad, GstPadProbeInfo *info, gpointer data) {
GstEnginePipeline *instance = reinterpret_cast<GstEnginePipeline*>(data);
const GstPadProbeType info_type = GST_PAD_PROBE_INFO_TYPE(info);
if (info_type & GST_PAD_PROBE_TYPE_BUFFER) {
// The playbin produced a buffer. Record its end time, so we can offset the buffers produced by the next playbin when transitioning to the next song.
GstBuffer *buffer = GST_PAD_PROBE_INFO_BUFFER(info);
GstClockTime timestamp = GST_BUFFER_TIMESTAMP(buffer);
GstClockTime duration = GST_BUFFER_DURATION(buffer);
if (timestamp == GST_CLOCK_TIME_NONE) {
timestamp = instance->last_playbin_segment_.position;
}
if (duration != GST_CLOCK_TIME_NONE) {
timestamp += duration;
}
instance->last_playbin_segment_.position = timestamp;
}
else if (info_type & GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM) {
GstEvent *event = GST_PAD_PROBE_INFO_EVENT(info);
GstEventType event_type = GST_EVENT_TYPE(event);
if (event_type == GST_EVENT_SEGMENT) {
// A new segment started, we need to save this to calculate running time offsets later.
gst_event_copy_segment(event, &instance->last_playbin_segment_);
}
else if (event_type == GST_EVENT_FLUSH_START) {
// A flushing seek resets the running time to 0, so remove any offset we set on this pad before.
gst_pad_set_offset(pad, 0);
}
}
return GST_PAD_PROBE_OK;
}
GstPadProbeReturn GstEnginePipeline::HandoffCallback(GstPad *pad, GstPadProbeInfo *info, gpointer self) {
GstEnginePipeline *instance = reinterpret_cast<GstEnginePipeline*>(self);
GstCaps *caps = gst_pad_get_current_caps(pad);
GstStructure *structure = gst_caps_get_structure(caps, 0);
QString format = QString(gst_structure_get_string(structure, "format"));
int channels = 0;
int rate = 0;
gst_structure_get_int(structure, "channels", &channels);
gst_structure_get_int(structure, "rate", &rate);
GstBuffer *buf = gst_pad_probe_info_get_buffer(info);
GstBuffer *buf16 = nullptr;
if (format.startsWith("S32")) {
GstMapInfo map_info;
gst_buffer_map(buf, &map_info, GST_MAP_READ);
int32_t *s = (int32_t*) map_info.data;
int samples = (map_info.size / sizeof(int32_t)) / channels;
int buf16_size = samples * sizeof(int16_t) * channels;
int16_t *d = (int16_t*) g_malloc(buf16_size);
memset(d, 0, buf16_size);
for (int i = 0 ; i < (samples * 2) ; ++i) {
d[i] = (int16_t) (s[i] >> 16);
}
gst_buffer_unmap(buf, &map_info);
buf16 = gst_buffer_new_wrapped(d, buf16_size);
GST_BUFFER_DURATION(buf16) = GST_FRAMES_TO_CLOCK_TIME(samples * sizeof(int16_t) * channels, rate);
buf = buf16;
}
QList<GstBufferConsumer*> consumers;
{
QMutexLocker l(&instance->buffer_consumers_mutex_);
consumers = instance->buffer_consumers_;
}
for (GstBufferConsumer *consumer : consumers) {
gst_buffer_ref(buf);
consumer->ConsumeBuffer(buf, instance->id(), format);
}
if (buf16) {
gst_buffer_unref(buf16);
}
// Calculate the end time of this buffer so we can stop playback if it's after the end time of this song.
if (instance->end_offset_nanosec_ > 0) {
quint64 start_time = GST_BUFFER_TIMESTAMP(buf) - instance->segment_start_;
quint64 duration = GST_BUFFER_DURATION(buf);
quint64 end_time = start_time + duration;
if (end_time > instance->end_offset_nanosec_) {
if (instance->has_next_valid_url() && instance->next_stream_url_ == instance->stream_url_ && instance->next_beginning_offset_nanosec_ == instance->end_offset_nanosec_) {
// The "next" song is actually the next segment of this file - so cheat and keep on playing, but just tell the Engine we've moved on.
instance->end_offset_nanosec_ = instance->next_end_offset_nanosec_;
instance->next_stream_url_.clear();
instance->next_original_url_.clear();
instance->next_beginning_offset_nanosec_ = 0;
instance->next_end_offset_nanosec_ = 0;
// GstEngine will try to seek to the start of the new section, but we're already there so ignore it.
instance->ignore_next_seek_ = true;
emit instance->EndOfStreamReached(instance->id(), true);
}
else {
// There's no next song
emit instance->EndOfStreamReached(instance->id(), false);
}
}
}
return GST_PAD_PROBE_OK;
}
void GstEnginePipeline::AboutToFinishCallback(GstPlayBin*, gpointer self) {
GstEnginePipeline *instance = reinterpret_cast<GstEnginePipeline*>(self);
if (instance->has_next_valid_url() && !instance->next_uri_set_) {
// Set the next uri. When the current song ends it will be played automatically and a STREAM_START message is send to the bus.
// When the next uri is not playable an error message is send when the pipeline goes to PLAY (or PAUSE) state or immediately if it is currently in PLAY state.
instance->next_uri_set_ = true;
g_object_set(G_OBJECT(instance->pipeline_), "uri", instance->next_stream_url_.constData(), nullptr);
}
}
@ -710,230 +941,8 @@ void GstEnginePipeline::BufferingMessageReceived(GstMessage *msg) {
}
void GstEnginePipeline::NewPadCallback(GstElement*, GstPad *pad, gpointer self) {
GstEnginePipeline *instance = reinterpret_cast<GstEnginePipeline*>(self);
if (!instance) return;
GstPad *const audiopad = gst_element_get_static_pad(instance->audiobin_, "sink");
// Link playbin's sink pad to audiobin's src pad.
if (GST_PAD_IS_LINKED(audiopad)) {
qLog(Warning) << instance->id() << "audiopad is already linked, unlinking old pad";
gst_pad_unlink(audiopad, GST_PAD_PEER(audiopad));
}
gst_pad_link(pad, audiopad);
gst_object_unref(audiopad);
// Offset the timestamps on all the buffers coming out of the playbin so they line up exactly with the end of the last buffer from the old playbin.
// "Running time" is the time since the last flushing seek.
GstClockTime running_time = gst_segment_to_running_time(&instance->last_playbin_segment_, GST_FORMAT_TIME, instance->last_playbin_segment_.position);
gst_pad_set_offset(pad, running_time);
// Add a probe to the pad so we can update last_playbin_segment_.
gst_pad_add_probe(pad, static_cast<GstPadProbeType>(GST_PAD_PROBE_TYPE_BUFFER | GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM | GST_PAD_PROBE_TYPE_EVENT_FLUSH), PlaybinProbe, instance, nullptr);
instance->pipeline_is_connected_ = true;
if (instance->pending_seek_nanosec_ != -1 && instance->pipeline_is_initialised_) {
QMetaObject::invokeMethod(instance, "Seek", Qt::QueuedConnection, Q_ARG(qint64, instance->pending_seek_nanosec_));
}
}
GstPadProbeReturn GstEnginePipeline::PlaybinProbe(GstPad *pad, GstPadProbeInfo *info, gpointer data) {
GstEnginePipeline *instance = reinterpret_cast<GstEnginePipeline*>(data);
const GstPadProbeType info_type = GST_PAD_PROBE_INFO_TYPE(info);
if (info_type & GST_PAD_PROBE_TYPE_BUFFER) {
// The playbin produced a buffer. Record its end time, so we can offset the buffers produced by the next playbin when transitioning to the next song.
GstBuffer *buffer = GST_PAD_PROBE_INFO_BUFFER(info);
GstClockTime timestamp = GST_BUFFER_TIMESTAMP(buffer);
GstClockTime duration = GST_BUFFER_DURATION(buffer);
if (timestamp == GST_CLOCK_TIME_NONE) {
timestamp = instance->last_playbin_segment_.position;
}
if (duration != GST_CLOCK_TIME_NONE) {
timestamp += duration;
}
instance->last_playbin_segment_.position = timestamp;
}
else if (info_type & GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM) {
GstEvent *event = GST_PAD_PROBE_INFO_EVENT(info);
GstEventType event_type = GST_EVENT_TYPE(event);
if (event_type == GST_EVENT_SEGMENT) {
// A new segment started, we need to save this to calculate running time offsets later.
gst_event_copy_segment(event, &instance->last_playbin_segment_);
}
else if (event_type == GST_EVENT_FLUSH_START) {
// A flushing seek resets the running time to 0, so remove any offset we set on this pad before.
gst_pad_set_offset(pad, 0);
}
}
return GST_PAD_PROBE_OK;
}
GstPadProbeReturn GstEnginePipeline::HandoffCallback(GstPad *pad, GstPadProbeInfo *info, gpointer self) {
GstEnginePipeline *instance = reinterpret_cast<GstEnginePipeline*>(self);
GstCaps *caps = gst_pad_get_current_caps(pad);
GstStructure *structure = gst_caps_get_structure(caps, 0);
QString format = QString(gst_structure_get_string(structure, "format"));
int channels = 0;
int rate = 0;
gst_structure_get_int(structure, "channels", &channels);
gst_structure_get_int(structure, "rate", &rate);
GstBuffer *buf = gst_pad_probe_info_get_buffer(info);
GstBuffer *buf16 = nullptr;
if (format.startsWith("S32")) {
GstMapInfo map_info;
gst_buffer_map(buf, &map_info, GST_MAP_READ);
int32_t *s = (int32_t*) map_info.data;
int samples = (map_info.size / sizeof(int32_t)) / channels;
int buf16_size = samples * sizeof(int16_t) * channels;
int16_t *d = (int16_t*) g_malloc(buf16_size);
memset(d, 0, buf16_size);
for (int i = 0 ; i < (samples * 2) ; ++i) {
d[i] = (int16_t) (s[i] >> 16);
}
gst_buffer_unmap(buf, &map_info);
buf16 = gst_buffer_new_wrapped(d, buf16_size);
GST_BUFFER_DURATION(buf16) = GST_FRAMES_TO_CLOCK_TIME(samples * sizeof(int16_t) * channels, rate);
buf = buf16;
}
QList<GstBufferConsumer*> consumers;
{
QMutexLocker l(&instance->buffer_consumers_mutex_);
consumers = instance->buffer_consumers_;
}
for (GstBufferConsumer *consumer : consumers) {
gst_buffer_ref(buf);
consumer->ConsumeBuffer(buf, instance->id(), format);
}
if (buf16) {
gst_buffer_unref(buf16);
}
// Calculate the end time of this buffer so we can stop playback if it's after the end time of this song.
if (instance->end_offset_nanosec_ > 0) {
quint64 start_time = GST_BUFFER_TIMESTAMP(buf) - instance->segment_start_;
quint64 duration = GST_BUFFER_DURATION(buf);
quint64 end_time = start_time + duration;
if (end_time > instance->end_offset_nanosec_) {
if (instance->has_next_valid_url() && instance->next_stream_url_ == instance->stream_url_ && instance->next_beginning_offset_nanosec_ == instance->end_offset_nanosec_) {
// The "next" song is actually the next segment of this file - so cheat and keep on playing, but just tell the Engine we've moved on.
instance->end_offset_nanosec_ = instance->next_end_offset_nanosec_;
instance->next_stream_url_.clear();
instance->next_original_url_.clear();
instance->next_beginning_offset_nanosec_ = 0;
instance->next_end_offset_nanosec_ = 0;
// GstEngine will try to seek to the start of the new section, but we're already there so ignore it.
instance->ignore_next_seek_ = true;
emit instance->EndOfStreamReached(instance->id(), true);
}
else {
// There's no next song
emit instance->EndOfStreamReached(instance->id(), false);
}
}
}
return GST_PAD_PROBE_OK;
}
GstPadProbeReturn GstEnginePipeline::EventHandoffCallback(GstPad*, GstPadProbeInfo *info, gpointer self) {
GstEnginePipeline *instance = reinterpret_cast<GstEnginePipeline*>(self);
GstEvent *e = gst_pad_probe_info_get_event(info);
qLog(Debug) << instance->id() << "event" << GST_EVENT_TYPE_NAME(e);
switch (GST_EVENT_TYPE(e)) {
case GST_EVENT_SEGMENT:
if (!instance->segment_start_received_) {
// The segment start time is used to calculate the proper offset of data buffers from the start of the stream
const GstSegment *segment = nullptr;
gst_event_parse_segment(e, &segment);
instance->segment_start_ = segment->start;
instance->segment_start_received_ = true;
}
break;
default:
break;
}
return GST_PAD_PROBE_OK;
}
void GstEnginePipeline::AboutToFinishCallback(GstPlayBin*, gpointer self) {
GstEnginePipeline *instance = reinterpret_cast<GstEnginePipeline*>(self);
if (instance->has_next_valid_url() && !instance->next_uri_set_) {
// Set the next uri. When the current song ends it will be played automatically and a STREAM_START message is send to the bus.
// When the next uri is not playable an error message is send when the pipeline goes to PLAY (or PAUSE) state or immediately if it is currently in PLAY state.
instance->next_uri_set_ = true;
g_object_set(G_OBJECT(instance->pipeline_), "uri", instance->next_stream_url_.constData(), nullptr);
}
}
void GstEnginePipeline::SourceSetupCallback(GstPlayBin *bin, GParamSpec *, gpointer self) {
GstEnginePipeline *instance = reinterpret_cast<GstEnginePipeline*>(self);
GstElement *element;
g_object_get(bin, "source", &element, nullptr);
if (!element) {
return;
}
if (g_object_class_find_property(G_OBJECT_GET_CLASS(element), "device") && !instance->source_device().isEmpty()) {
// Gstreamer is not able to handle device in URL (referring to Gstreamer documentation, this might be added in the future).
// Despite that, for now we include device inside URL: we decompose it during Init and set device here, when this callback is called.
g_object_set(element, "device", instance->source_device().toLocal8Bit().constData(), nullptr);
}
if (g_object_class_find_property(G_OBJECT_GET_CLASS(element), "user-agent")) {
QString user_agent = QString("%1 %2").arg(QCoreApplication::applicationName(), QCoreApplication::applicationVersion());
g_object_set(element, "user-agent", user_agent.toUtf8().constData(), nullptr);
g_object_set(element, "ssl-strict", FALSE, nullptr);
}
// If the pipeline was buffering we stop that now.
if (instance->buffering_) {
instance->buffering_ = false;
emit instance->BufferingFinished();
instance->SetState(GST_STATE_PLAYING);
}
g_object_unref(element);
}
qint64 GstEnginePipeline::position() const {
if (pipeline_is_initialised_)
gst_element_query_position(pipeline_, GST_FORMAT_TIME, &last_known_position_ns_);
@ -942,6 +951,7 @@ qint64 GstEnginePipeline::position() const {
}
qint64 GstEnginePipeline::length() const {
gint64 value = 0;
gst_element_query_duration(pipeline_, GST_FORMAT_TIME, &value);
@ -977,8 +987,6 @@ bool GstEnginePipeline::Seek(const qint64 nanosec) {
}
if (next_uri_set_) {
qDebug() << "MYTODO: gstenginepipeline.seek: seeking after Transition";
pending_seek_nanosec_ = nanosec;
SetState(GST_STATE_READY);
return true;
@ -993,7 +1001,6 @@ bool GstEnginePipeline::Seek(const qint64 nanosec) {
void GstEnginePipeline::SetVolume(const int percent) {
if (!volume_) return;
volume_percent_ = percent;
UpdateVolume();
@ -1015,23 +1022,18 @@ void GstEnginePipeline::UpdateVolume() {
}
void GstEnginePipeline::SetStereoBalance(const bool enabled, const float value) {
void GstEnginePipeline::SetStereoBalance(const float value) {
stereo_balance_enabled_ = enabled;
if (enabled) {
stereo_balance_ = value;
}
else {
stereo_balance_ = 0.0f;
}
UpdateStereoBalance();
}
void GstEnginePipeline::SetEqualizerEnabled(bool enabled) {
void GstEnginePipeline::UpdateStereoBalance() {
eq_enabled_ = enabled;
UpdateEqualizer();
if (audiopanorama_) {
g_object_set(G_OBJECT(audiopanorama_), "panorama", stereo_balance_, nullptr);
}
}
@ -1043,12 +1045,6 @@ void GstEnginePipeline::SetEqualizerParams(const int preamp, const QList<int>& b
}
void GstEnginePipeline::UpdateStereoBalance() {
if (audiopanorama_) {
g_object_set(G_OBJECT(audiopanorama_), "panorama", stereo_balance_, nullptr);
}
}
void GstEnginePipeline::UpdateEqualizer() {
if (!equalizer_ || !equalizer_preamp_) return;
@ -1171,7 +1167,6 @@ void GstEnginePipeline::SetNextUrl(const QByteArray &stream_url, const QUrl &ori
void GstEnginePipeline::StreamDiscovered(GstDiscoverer*, GstDiscovererInfo *info, GError*, gpointer self) {
GstEnginePipeline *instance = reinterpret_cast<GstEnginePipeline*>(self);
if (!instance) return;
QString discovered_url(gst_discoverer_info_get_uri(info));

View File

@ -69,7 +69,9 @@ class GstEnginePipeline : public QObject {
// Call these setters before Init
void set_output_device(const QString &sink, const QVariant &device);
void set_volume_control(const bool volume_control);
void set_volume_enabled(const bool enabled);
void set_stereo_balancer_enabled(const bool enabled);
void set_equalizer_enabled(const bool enabled);
void set_replaygain(const bool enabled, const int mode, const float preamp, const bool compression);
void set_buffer_duration_nanosec(qint64 duration_nanosec);
void set_buffer_min_fill(int percent);
@ -85,10 +87,10 @@ class GstEnginePipeline : public QObject {
// Control the music playback
QFuture<GstStateChangeReturn> SetState(const GstState state);
Q_INVOKABLE bool Seek(const qint64 nanosec);
void SetEqualizerEnabled(const bool enabled);
void SetEqualizerParams(const int preamp, const QList<int> &band_gains);
void SetVolume(const int percent);
void SetStereoBalance(const bool enabled, const float value);
void SetStereoBalance(const float value);
void SetEqualizerParams(const int preamp, const QList<int> &band_gains);
void StartFader(const qint64 duration_nanosec, const QTimeLine::Direction direction = QTimeLine::Forward, const QTimeLine::CurveShape shape = QTimeLine::LinearCurve, const bool use_fudge_timer = true);
// If this is set then it will be loaded automatically when playback finishes for gapless playback
@ -101,6 +103,7 @@ class GstEnginePipeline : public QObject {
QByteArray stream_url() const { return stream_url_; }
QUrl original_url() const { return original_url_; }
bool is_valid() const { return valid_; }
// Please note that this method (unlike GstEngine's.position()) is multiple-section media unaware.
qint64 position() const;
// Please note that this method (unlike GstEngine's.length()) is multiple-section media unaware.
@ -135,17 +138,21 @@ signals:
void timerEvent(QTimerEvent*);
private:
bool InitAudioBin();
// Static callbacks. The GstEnginePipeline instance is passed in the last argument.
static GstPadProbeReturn EventHandoffCallback(GstPad*, GstPadProbeInfo*, gpointer);
static void SourceSetupCallback(GstPlayBin*, GParamSpec* pspec, gpointer);
static void NewPadCallback(GstElement*, GstPad*, gpointer);
static GstPadProbeReturn PlaybinProbe(GstPad*, GstPadProbeInfo*, gpointer);
static GstPadProbeReturn HandoffCallback(GstPad*, GstPadProbeInfo*, gpointer);
static void AboutToFinishCallback(GstPlayBin*, gpointer);
static GstBusSyncReply BusCallbackSync(GstBus*, GstMessage*, gpointer);
static gboolean BusCallback(GstBus*, GstMessage*, gpointer);
static void NewPadCallback(GstElement*, GstPad*, gpointer);
static GstPadProbeReturn HandoffCallback(GstPad*, GstPadProbeInfo*, gpointer);
static GstPadProbeReturn SourceHandoffCallback(GstPad*, GstPadProbeInfo*, gpointer);
static GstPadProbeReturn EventHandoffCallback(GstPad*, GstPadProbeInfo*, gpointer);
static void AboutToFinishCallback(GstPlayBin*, gpointer);
static GstPadProbeReturn PlaybinProbe(GstPad*, GstPadProbeInfo*, gpointer);
static void SourceSetupCallback(GstPlayBin*, GParamSpec* pspec, gpointer);
static void TaskEnterCallback(GstTask*, GThread*, gpointer);
static void StreamDiscovered(GstDiscoverer *discoverer, GstDiscovererInfo *info, GError *err, gpointer instance);
static void StreamDiscoveryFinished(GstDiscoverer *discoverer, gpointer instance);
static QString GSTdiscovererErrorMessage(GstDiscovererResult result);
void TagMessageReceived(GstMessage*);
void ErrorMessageReceived(GstMessage*);
@ -158,15 +165,9 @@ signals:
QString ParseStrTag(GstTagList *list, const char *tag) const;
guint ParseUIntTag(GstTagList *list, const char *tag) const;
bool InitAudioBin();
void UpdateVolume();
void UpdateEqualizer();
void UpdateStereoBalance();
static void StreamDiscovered(GstDiscoverer *discoverer, GstDiscovererInfo *info, GError *err, gpointer instance);
static void StreamDiscoveryFinished(GstDiscoverer *discoverer, gpointer instance);
static QString GSTdiscovererErrorMessage(GstDiscovererResult result);
void UpdateEqualizer();
private slots:
void FaderTimelineFinished();
@ -191,21 +192,21 @@ signals:
bool valid_;
QString output_;
QVariant device_;
bool volume_control_;
bool volume_enabled_;
bool stereo_balancer_enabled_;
bool eq_enabled_;
bool rg_enabled_;
// Stereo balance.
// Stereo balance:
// From -1.0 - 1.0
// -1.0 is left, 1.0 is right.
bool stereo_balance_enabled_;
float stereo_balance_;
// Equalizer
bool eq_enabled_;
int eq_preamp_;
QList<int> eq_band_gains_;
// ReplayGain
bool rg_enabled_;
int rg_mode_;
float rg_preamp_;
bool rg_compression_;
@ -276,9 +277,9 @@ signals:
GstElement *equalizer_preamp_;
GstDiscoverer *discoverer_;
int about_to_finish_cb_id_;
int pad_added_cb_id_;
int notify_source_cb_id_;
int about_to_finish_cb_id_;
int bus_cb_id_;
int discovery_finished_cb_id_;
int discovery_discovered_cb_id_;

View File

@ -74,16 +74,14 @@ Equalizer::Equalizer(QWidget *parent)
// Must be done before the signals are connected
ReloadSettings();
connect(ui_->enable, SIGNAL(toggled(bool)), SIGNAL(EnabledChanged(bool)));
connect(ui_->enable, SIGNAL(toggled(bool)), ui_->slider_container, SLOT(setEnabled(bool)));
connect(ui_->enable, SIGNAL(toggled(bool)), SLOT(Save()));
connect(ui_->enable_equalizer, SIGNAL(toggled(bool)), SLOT(EqualizerEnabledChangedSlot(bool)));
connect(ui_->preset, SIGNAL(currentIndexChanged(int)), SLOT(PresetChanged(int)));
connect(ui_->preset_save, SIGNAL(clicked()), SLOT(SavePreset()));
connect(ui_->preset_del, SIGNAL(clicked()), SLOT(DelPreset()));
connect(ui_->enable_stereo_balancer, SIGNAL(toggled(bool)), SLOT(Save()));
connect(ui_->enable_stereo_balancer, SIGNAL(toggled(bool)), ui_->balance_slider, SLOT(setEnabled(bool)));
connect(ui_->balance_slider, SIGNAL(valueChanged(int)), SLOT(StereoSliderChanged(int)));
connect(ui_->enable_stereo_balancer, SIGNAL(toggled(bool)), SLOT(StereoBalancerEnabledChangedSlot(bool)));
connect(ui_->stereo_balance_slider, SIGNAL(valueChanged(int)), SLOT(StereoBalanceSliderChanged(int)));
QShortcut *close = new QShortcut(QKeySequence::Close, this);
connect(close, SIGNAL(activated()), SLOT(close()));
@ -119,16 +117,16 @@ void Equalizer::ReloadSettings() {
if (selected_index != -1) ui_->preset->setCurrentIndex(selected_index);
// Enabled?
ui_->enable->setChecked(s.value("enabled", false).toBool());
ui_->slider_container->setEnabled(ui_->enable->isChecked());
ui_->enable_equalizer->setChecked(s.value("enabled", false).toBool());
ui_->slider_container->setEnabled(ui_->enable_equalizer->isChecked());
ui_->enable_stereo_balancer->setChecked(s.value("enable_stereo_balancer", false).toBool());
ui_->slider_label_layout->setEnabled(ui_->enable_stereo_balancer->isChecked());
ui_->balance_slider->setEnabled(ui_->enable_stereo_balancer->isChecked());
ui_->stereo_balance_slider->setEnabled(ui_->enable_stereo_balancer->isChecked());
int stereo_balance = s.value("stereo_balance", 0).toInt();
ui_->balance_slider->setValue(stereo_balance);
StereoSliderChanged(stereo_balance);
ui_->stereo_balance_slider->setValue(stereo_balance);
StereoBalanceSliderChanged(stereo_balance);
PresetChanged(selected_preset);
@ -191,7 +189,7 @@ void Equalizer::PresetChanged(const QString& name) {
for (int i = 0; i < kBands; ++i) gain_[i]->set_value(p.gain[i]);
loading_ = false;
ParametersChanged();
EqualizerParametersChangedSlot();
Save();
}
@ -241,7 +239,7 @@ EqualizerSlider *Equalizer::AddSlider(const QString &label) {
EqualizerSlider *ret = new EqualizerSlider(label, ui_->slider_container);
ui_->slider_container->layout()->addWidget(ret);
connect(ret, SIGNAL(ValueChanged(int)), SLOT(ParametersChanged()));
connect(ret, SIGNAL(ValueChanged(int)), SLOT(EqualizerParametersChangedSlot()));
return ret;
@ -252,7 +250,7 @@ bool Equalizer::is_stereo_balancer_enabled() const {
}
bool Equalizer::is_equalizer_enabled() const {
return ui_->enable->isChecked();
return ui_->enable_equalizer->isChecked();
}
int Equalizer::preamp_value() const {
@ -279,13 +277,41 @@ Equalizer::Params Equalizer::current_params() const {
}
float Equalizer::stereo_balance() const {
return qBound(-1.0f, ui_->balance_slider->value() / 100.0f, 1.0f);
return qBound(-1.0f, ui_->stereo_balance_slider->value() / 100.0f, 1.0f);
}
void Equalizer::ParametersChanged() {
if (loading_) return;
void Equalizer::StereoBalancerEnabledChangedSlot(const bool enabled) {
if (!enabled) {
ui_->stereo_balance_slider->setValue(0);
emit StereoBalanceChanged(stereo_balance());
}
ui_->stereo_balance_slider->setEnabled(enabled);
emit StereoBalancerEnabledChanged(enabled);
Save();
}
void Equalizer::StereoBalanceSliderChanged(int) {
emit StereoBalanceChanged(stereo_balance());
Save();
}
void Equalizer::EqualizerEnabledChangedSlot(const bool enabled) {
emit EqualizerEnabledChanged(enabled);
ui_->slider_container->setEnabled(enabled);
Save();
}
void Equalizer::EqualizerParametersChangedSlot() {
if (loading_) return;
emit EqualizerParametersChanged(preamp_value(), gain_values());
emit ParametersChanged(preamp_value(), gain_values());
}
void Equalizer::Save() {
@ -307,16 +333,14 @@ void Equalizer::Save() {
s.setValue("selected_preset", ui_->preset->itemData(ui_->preset->currentIndex()).toString());
// Enabled?
s.setValue("enabled", ui_->enable->isChecked());
s.setValue("enabled", ui_->enable_equalizer->isChecked());
s.setValue("enable_stereo_balancer", ui_->enable_stereo_balancer->isChecked());
s.setValue("stereo_balance", ui_->balance_slider->value());
s.setValue("stereo_balance", ui_->stereo_balance_slider->value());
}
void Equalizer::closeEvent(QCloseEvent *e) {
Q_UNUSED(e);
void Equalizer::closeEvent(QCloseEvent*) {
QString name = ui_->preset->currentText();
if (!presets_.contains(name)) return;
@ -331,8 +355,7 @@ Equalizer::Params::Params() : preamp(0) {
for (int i = 0; i < Equalizer::kBands; ++i) gain[i] = 0;
}
Equalizer::Params::Params(int g0, int g1, int g2, int g3, int g4, int g5, int g6, int g7, int g8, int g9, int pre)
: preamp(pre) {
Equalizer::Params::Params(int g0, int g1, int g2, int g3, int g4, int g5, int g6, int g7, int g8, int g9, int pre) : preamp(pre) {
gain[0] = g0;
gain[1] = g1;
gain[2] = g2;
@ -357,12 +380,6 @@ bool Equalizer::Params::operator !=(const Equalizer::Params& other) const {
return ! (*this == other);
}
void Equalizer::StereoSliderChanged(int value) {
Q_UNUSED(value);
emit StereoBalanceChanged(is_stereo_balancer_enabled(), stereo_balance());
Save();
}
QDataStream &operator<<(QDataStream& s, const Equalizer::Params& p) {
s << p.preamp;
for (int i = 0; i < Equalizer::kBands; ++i) s << p.gain[i];

View File

@ -69,21 +69,24 @@ class Equalizer : public QDialog {
float stereo_balance() const;
signals:
void EnabledChanged(bool enabled);
void ParametersChanged(int preamp, const QList<int> &band_gains);
void StereoBalanceChanged(bool enabled, float balance);
void StereoBalancerEnabledChanged(const bool enabled);
void StereoBalanceChanged(const float balance);
void EqualizerEnabledChanged(const bool enabled);
void EqualizerParametersChanged(const int preamp, const QList<int> &band_gains);
protected:
void closeEvent(QCloseEvent*);
private slots:
void ParametersChanged();
void StereoBalancerEnabledChangedSlot(const bool enabled);
void StereoBalanceSliderChanged(const int value);
void EqualizerEnabledChangedSlot(const bool enabled);
void EqualizerParametersChangedSlot();
void PresetChanged(const QString &name);
void PresetChanged(int index);
void SavePreset();
void DelPreset();
void Save();
void StereoSliderChanged(int value);
private:
EqualizerSlider *AddSlider(const QString &label);

View File

@ -61,7 +61,7 @@
</widget>
</item>
<item>
<widget class="QCheckBox" name="enable">
<widget class="QCheckBox" name="enable_equalizer">
<property name="text">
<string>Enable equalizer</string>
</property>
@ -133,7 +133,7 @@
</layout>
</item>
<item>
<widget class="QSlider" name="balance_slider">
<widget class="QSlider" name="stereo_balance_slider">
<property name="minimum">
<number>-100</number>
</property>