Skip to content

fix: fix audio device switching and mute synchronization issues#428

Merged
robertkill merged 1 commit intolinuxdeepin:masterfrom
robertkill:master
Feb 9, 2026
Merged

fix: fix audio device switching and mute synchronization issues#428
robertkill merged 1 commit intolinuxdeepin:masterfrom
robertkill:master

Conversation

@robertkill
Copy link
Contributor

@robertkill robertkill commented Feb 9, 2026

  1. Added proper disconnection of previous DBus sink interface before creating new one to prevent signal conflicts
  2. Fixed device activation timing issue by manually refreshing device list before setting active port
  3. Changed mute handling logic to automatically set mute when volume reaches 0
  4. Removed automatic unmute on volume change to prevent inconsistent state

Log: Fixed audio device switching issues and improved mute/volume synchronization

Influence:

  1. Test audio device switching between different output devices
  2. Verify mute state synchronization when volume is set to 0
  3. Test volume adjustment while muted/unmuted
  4. Check for any signal conflicts during device switching
  5. Verify device list refresh works correctly on specific hardware models

fix: 修复音频设备切换和静音同步问题

  1. 在创建新的DBus接收器接口前正确断开之前的连接,防止信号冲突
  2. 通过手动刷新设备列表再设置活动端口,修复设备激活时序问题
  3. 修改静音处理逻辑,当音量达到0时自动设置静音
  4. 移除音量变化时的自动取消静音,防止状态不一致

Log: 修复音频设备切换问题并改进静音/音量同步

Influence:

  1. 测试不同输出设备之间的音频设备切换
  2. 验证音量设为0时的静音状态同步
  3. 测试静音/取消静音状态下的音量调整
  4. 检查设备切换过程中是否有信号冲突
  5. 验证特定硬件型号上的设备列表刷新功能

PMS: BUG-350057

Summary by Sourcery

Improve audio output device switching reliability and mute/volume state synchronization in the dock sound controller.

Bug Fixes:

  • Prevent duplicate DBus sink signals during default audio device changes by disconnecting the previous sink interface before creating a new one.
  • Ensure active port selection works on hardware with add/active timing issues by refreshing the device list before applying the active port.
  • Align mute state with volume changes by automatically muting when volume reaches zero and removing auto-unmute on volume adjustments.

Enhancements:

  • Simplify volume change handling by updating volume while relying on explicit mute control rather than implicit unmute behavior.

@sourcery-ai
Copy link

sourcery-ai bot commented Feb 9, 2026

Reviewer's guide (collapsed on small PRs)

Reviewer's Guide

Adjusts how the dock sound controller manages the default sink DBus interface, fixes device activation timing by refreshing card info before activating, and revises mute/volume synchronization so volume==0 enforces mute while removing the previous auto‑unmute-on-volume-change behavior.

Sequence diagram for updated default sink change handling

sequenceDiagram
    participant DBusDaemon
    participant SoundController
    participant DBusSink
    participant SoundModel

    DBusDaemon->>SoundController: onDefaultSinkChanged(path)
    alt previous m_defaultSinkInter exists
        SoundController->>DBusSink: disconnect(this)
        SoundController->>SoundModel: clear previous active sink state
    end

    SoundController->>DBusSink: create new DBusSink(path)
    SoundController->>SoundModel: setCardsInfo(m_audioInter.cardsWithoutUnavailable())
    SoundController->>SoundModel: setActivePort(card, activePort)
    SoundController->>SoundModel: setMute(m_defaultSinkInter.mute())
    alt existActiveOutputDevice
        SoundController->>SoundModel: setVolume(m_defaultSinkInter.volume())
    else no active output device
        SoundController->>SoundModel: setVolume(0)
    end

    DBusSink-->>SoundController: MuteChanged(value)
    SoundController->>SoundModel: setMute(m_defaultSinkInter.mute())
    SoundController->>SoundModel: setVolume(m_defaultSinkInter.volume())

    DBusSink-->>SoundController: VolumeChanged(value)
    alt value == 0 and SoundModel.isMute() is false
        SoundController->>SoundController: SetMute(true)
        SoundController->>SoundModel: setVolume(0)
    else value != 0 or already muted
        SoundController->>SoundModel: setVolume(value)
    end

    DBusSink-->>SoundController: ActivePortChanged(port)
    SoundController->>SoundModel: setActivePort(card, port.name)
Loading

Class diagram for updated sound controller, DBus sink, and sound model

classDiagram
    class SoundController {
        - DBusSink m_defaultSinkInter
        - AudioInterface m_audioInter
        + static SoundController ref()
        + void SetMute(bool in0)
        + void onDefaultSinkChanged(QDBusObjectPath path)
    }

    class DBusSink {
        + DBusSink(QString service, QString path, QDBusConnection connection, QObject parent)
        + int card()
        + AudioPort activePort()
        + bool mute()
        + double volume()
        + void SetMuteQueued(bool mute)
        + void disconnect(QObject receiver)
        <<signal>> void MuteChanged(bool value)
        <<signal>> void VolumeChanged(double value)
        <<signal>> void ActivePortChanged(AudioPort port)
    }

    class SoundModel {
        + static SoundModel ref()
        + void setCardsInfo(QList~AudioCard~ cards)
        + void setActivePort(int cardId, QString portName)
        + void setMute(bool mute)
        + void setVolume(double volume)
        + bool isMute()
    }

    class AudioInterface {
        + QList~AudioCard~ cardsWithoutUnavailable()
    }

    class AudioPort {
        + QString name
    }

    class QDBusObjectPath {
        + QString path()
    }

    SoundController --> DBusSink : uses m_defaultSinkInter
    SoundController --> SoundModel : updates state
    SoundController --> AudioInterface : uses m_audioInter
    SoundController --> QDBusObjectPath : onDefaultSinkChanged parameter
    DBusSink --> AudioPort : returns activePort
    AudioInterface --> AudioCard : returns list
    SoundModel --> AudioCard : stores cards
    SoundModel --> AudioPort : stores active port
Loading

File-Level Changes

Change Details Files
Ensure previous DBus sink interface connections are fully disconnected before creating a new one when the default sink changes.
  • Add an explicit disconnect on the existing default sink interface object at the start of onDefaultSinkChanged to avoid duplicate signal delivery and conflicts.
  • Preserve the existing deletion of the old interface and creation of a new DBusSink bound to the new default sink path.
plugins/dde-dock/sound/soundcontroller.cpp
Fix device activation timing by refreshing sound card information before setting the active output port when the default sink changes.
  • Call SoundModel::ref().setCardsInfo(m_audioInter->cardsWithoutUnavailable()) just after creating the new DBusSink instance.
  • Rely on the refreshed card list so that setActivePort uses up-to-date device entries, avoiding cases where ActivePortChanged arrives before the corresponding device is added.
plugins/dde-dock/sound/soundcontroller.cpp
Change mute/volume synchronization behavior so volume 0 forces mute and automatic unmute on volume changes is removed.
  • Update the VolumeChanged connection lambda to no longer unmute when volume changes while muted.
  • Introduce logic in the VolumeChanged handler that checks for volume becoming 0 while the model is not muted and then calls SoundController::ref().SetMute(true), logging this adjustment.
  • Keep SoundModel volume updates in the VolumeChanged handler but route mute changes through the new conditional logic instead of DBusSink::SetMuteQueued(false).
plugins/dde-dock/sound/soundcontroller.cpp
Keep SoundModel’s mute and volume in sync with the DBus sink while relying more on explicit queries than on DBus changed signals.
  • Maintain the MuteChanged handler that re-queries and sets both mute and volume from the DBus sink when mute changes.
  • Remove the use of the captured this pointer in the VolumeChanged lambda since only the value and global references are needed.
plugins/dde-dock/sound/soundcontroller.cpp

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've left some high level feedback:

  • In onDefaultSinkChanged, m_defaultSinkInter->disconnect(this); only disconnects slots on this, but the previous m_defaultSinkInter is also connected to SoundModel::ref(); if the intent is to avoid duplicate signals on subsequent connections, consider calling m_defaultSinkInter->disconnect(); or explicitly disconnecting the SoundModel-related connections as well.
  • The mute-on-zero-volume check uses qFuzzyCompare(0.0, value), which is slightly unconventional for testing a value against zero; using qFuzzyIsNull(value) or an explicit value <= epsilon would be clearer and avoid relying on qFuzzyCompare's symmetric-comparison semantics.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `onDefaultSinkChanged`, `m_defaultSinkInter->disconnect(this);` only disconnects slots on `this`, but the previous `m_defaultSinkInter` is also connected to `SoundModel::ref()`; if the intent is to avoid duplicate signals on subsequent connections, consider calling `m_defaultSinkInter->disconnect();` or explicitly disconnecting the `SoundModel`-related connections as well.
- The mute-on-zero-volume check uses `qFuzzyCompare(0.0, value)`, which is slightly unconventional for testing a value against zero; using `qFuzzyIsNull(value)` or an explicit `value <= epsilon` would be clearer and avoid relying on `qFuzzyCompare`'s symmetric-comparison semantics.

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

1. Added proper disconnection of previous DBus sink interface before
creating new one to prevent signal conflicts
2. Fixed device activation timing issue by manually refreshing device
list before setting active port
3. Changed mute handling logic to automatically set mute when volume
reaches 0
4. Removed automatic unmute on volume change to prevent inconsistent
state

Log: Fixed audio device switching issues and improved mute/volume
synchronization

Influence:
1. Test audio device switching between different output devices
2. Verify mute state synchronization when volume is set to 0
3. Test volume adjustment while muted/unmuted
4. Check for any signal conflicts during device switching
5. Verify device list refresh works correctly on specific hardware
models

fix: 修复音频设备切换和静音同步问题

1. 在创建新的DBus接收器接口前正确断开之前的连接,防止信号冲突
2. 通过手动刷新设备列表再设置活动端口,修复设备激活时序问题
3. 修改静音处理逻辑,当音量达到0时自动设置静音
4. 移除音量变化时的自动取消静音,防止状态不一致

Log: 修复音频设备切换问题并改进静音/音量同步

Influence:
1. 测试不同输出设备之间的音频设备切换
2. 验证音量设为0时的静音状态同步
3. 测试静音/取消静音状态下的音量调整
4. 检查设备切换过程中是否有信号冲突
5. 验证特定硬件型号上的设备列表刷新功能

PMS: BUG-350057
@deepin-ci-robot
Copy link

deepin pr auto review

这段代码修改主要针对音频设备切换时的时序问题和信号处理进行了优化。以下是对代码的详细审查和改进建议:

1. 语法逻辑审查

优点:

  • 在创建新的 m_defaultSinkInter 前先断开旧连接,避免重复连接
  • 添加了设备列表刷新逻辑,解决了设备添加和激活的时序问题

问题:

  1. m_defaultSinkInter 的断开连接和后续使用存在潜在的竞态条件:

    if (m_defaultSinkInter)
        m_defaultSinkInter->disconnect(this); // 断开连接
    
    // ... 中间代码 ...
    
    m_defaultSinkInter = new DBusSink(...); // 重新赋值

    建议改为:

    if (m_defaultSinkInter) {
        m_defaultSinkInter->disconnect(this);
        m_defaultSinkInter->deleteLater();
    }
  2. VolumeChanged 的 lambda 修改后不再使用 this,但 MuteChanged 的 lambda 仍然捕获 this,建议保持一致:

    connect(m_defaultSinkInter, &DBusSink::VolumeChanged, &SoundModel::ref(), [this] (double value) {
        SoundModel::ref().setVolume(value);
    });

2. 代码质量审查

优点:

  • 添加了有意义的中文注释,解释了修改原因
  • 简化了 VolumeChanged 的处理逻辑

改进建议:

  1. 考虑使用 QPointer 管理 m_defaultSinkInter,避免悬空指针:

    QPointer<DBusSink> m_defaultSinkInter;
  2. 将重复的 SoundModel::ref() 调用提取为局部变量:

    auto &soundModel = SoundModel::ref();
    soundModel.setCardsInfo(m_audioInter->cardsWithoutUnavailable());
    soundModel.setActivePort(...);
  3. 对于设备切换逻辑,建议添加防抖动机制,避免短时间内多次切换:

    static QTimer debounceTimer;
    if (!debounceTimer.isActive()) {
        debounceTimer.setSingleShot(true);
        debounceTimer.start(300); // 300ms 防抖动
        // 执行切换逻辑
    }

3. 代码性能审查

优点:

  • 减少了不必要的 SetMuteQueued 调用
  • 优化了信号槽连接

改进建议:

  1. cardsWithoutUnavailable() 可能是耗时操作,建议检查是否可以缓存结果
  2. 考虑使用 Qt::QueuedConnection 连接信号,避免在信号处理中阻塞

4. 代码安全审查

问题:

  1. 没有检查 path 的有效性:

    if (path.path().isEmpty()) {
        qWarning() << "Invalid default sink path";
        return;
    }
  2. m_audioInter 可能为空,需要添加检查:

    if (!m_audioInter) {
        qWarning() << "Audio interface is null";
        return;
    }
  3. setActivePortsetVolume 的调用顺序可能导致不一致状态,建议添加事务处理:

    // 先收集所有状态
    bool mute = m_defaultSinkInter->mute();
    double volume = m_defaultSinkInter->volume();
    AudioPort port = m_defaultSinkInter->activePort();
    
    // 然后统一设置
    SoundModel::ref().beginUpdate();
    SoundModel::ref().setMute(mute);
    SoundModel::ref().setVolume(volume);
    SoundModel::ref().setActivePort(...);
    SoundModel::ref().endUpdate();

综合改进后的代码示例:

void SoundController::onDefaultSinkChanged(const QDBusObjectPath &path)
{
    if (path.path().isEmpty() || !m_audioInter) {
        qWarning() << "Invalid input parameters";
        return;
    }

    // 防抖动处理
    static QTimer debounceTimer;
    if (debounceTimer.isActive()) {
        return;
    }
    debounceTimer.start(300);

    if (m_defaultSinkInter) {
        m_defaultSinkInter->disconnect(this);
        m_defaultSinkInter->deleteLater();
    }

    m_defaultSinkInter = new DBusSink("org.deepin.dde.Audio1", path.path(), QDBusConnection::sessionBus(), this);
    
    auto &soundModel = SoundModel::ref();
    
    // 事务处理状态更新
    soundModel.beginUpdate();
    soundModel.setCardsInfo(m_audioInter->cardsWithoutUnavailable());
    soundModel.setActivePort(m_defaultSinkInter->card(), m_defaultSinkInter->activePort().name);
    soundModel.setMute(m_defaultSinkInter->mute());
    soundModel.setVolume(existActiveOutputDevice() ? m_defaultSinkInter->volume() : 0);
    soundModel.endUpdate();

    // 使用Qt::QueuedConnection避免阻塞
    connect(m_defaultSinkInter, &DBusSink::MuteChanged, &soundModel, [this] (bool value) {
        Q_UNUSED(value)
        if (m_defaultSinkInter) {
            soundModel.setMute(m_defaultSinkInter->mute());
            soundModel.setVolume(m_defaultSinkInter->volume());
        }
    }, Qt::QueuedConnection);
    
    connect(m_defaultSinkInter, &DBusSink::VolumeChanged, &soundModel, [this] (double value) {
        if (m_defaultSinkInter) {
            soundModel.setVolume(value);
        }
    }, Qt::QueuedConnection);
    
    connect(m_defaultSinkInter, &DBusSink::ActivePortChanged, this, [this](AudioPort port) {
        if (m_defaultSinkInter) {
            soundModel.setActivePort(m_defaultSinkInter->card(), port.name);
        }
    }, Qt::QueuedConnection);
}

这些改进可以显著提高代码的健壮性、可维护性和性能,同时解决潜在的时序问题和安全问题。

@deepin-ci-robot
Copy link

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: fly602, robertkill

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@robertkill robertkill merged commit 352f45f into linuxdeepin:master Feb 9, 2026
9 of 10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants