From 9277429ceb376805061d876526544e7f77ffa692 Mon Sep 17 00:00:00 2001 From: fuleyi Date: Mon, 1 Jun 2026 16:44:00 +0800 Subject: [PATCH] fix: resolve audio silence after upgrade MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. Added CardFixGroup to manage delayed fixes for audio cards when profile configurations are missing after upgrade. 2. Modified setPort function to handle cases where saved profile doesn't exist, by selecting a new profile and scheduling a fix to update config after 30 seconds. 3. Added cleanup of pending fixes when audio cards are removed. 4. Implemented Exists method for ProfileList to check profile availability. 5. Updated copyright year in profile.go. This fix addresses issues where pipewire upgrades or asynchronous profile reporting cause audio silence due to configuration mismatches. Log: Fixed audio silence issue after system upgrade by improving profile handling. Influence: 1. Test audio playback after upgrading pipewire or audio components to ensure sound works correctly. 2. Verify that audio profiles are correctly set and can be switched without issues. 3. Check if the delayed fix is applied when profiles change during initialization or after upgrade. 4. Test card removal to ensure pending fixes are cleaned up properly. 5. Verify that configuration updates work as expected after the 30- second delay. 6. Test scenarios where profiles temporarily disappear and reappear to ensure the fix handles temporary changes correctly. 解决升级之后音频无声的问题 1. 引入 CardFixGroup 来管理音频卡在升级后配置文件缺失时的延迟修复。 2. 修改 setPort 函数以处理保存的配置文件不存在的情况,通过选择新配置文件 并计划在30秒后更新配置。 3. 添加在音频卡移除时清理待修复项的功能。 4. 为 ProfileList 实现 Exists 方法以检查配置文件可用性。 5. 更新了 profile.go 中的版权年份。 此修复解决了 pipewire 升级或异步配置文件上报导致的音频无声问题。 Log: 新增音频修复功能,解决升级后无声问题 Influence: 1. 测试升级 pipewire 或音频组件后的音频播放功能,确保声音正常工作。 2. 验证音频配置文件是否正确设置并可切换无问题。 3. 检查在初始化或升级后配置文件变化时延迟修复是否应用。 4. 测试卡片移除以确保待修复项被正确清理。 5. 验证延迟30秒后配置更新是否按预期工作。 6. 测试配置文件暂时消失和重现的情况,确保修复能正确处理临时变化。 PMS: BUG-355831 Change-Id: Ibbce0c7fa1ae76bceb6336a9b342e3554d31523e --- audio1/audio.go | 36 ++++++++++++-- audio1/audio_events.go | 1 + audio1/card.go | 107 +++++++++++++++++++++++++++++++++++++++++ audio1/profile.go | 14 +++++- 4 files changed, 153 insertions(+), 5 deletions(-) diff --git a/audio1/audio.go b/audio1/audio.go index 987dc4603..ce8948bc6 100644 --- a/audio1/audio.go +++ b/audio1/audio.go @@ -178,7 +178,8 @@ type Audio struct { mu sync.Mutex quit chan struct{} - cards CardList + cards CardList + cardFixGroup *CardFixGroup isSaving bool saverLocker sync.Mutex @@ -205,10 +206,11 @@ type Audio struct { func newAudio(service *dbusutil.Service) *Audio { a := &Audio{ - service: service, - meters: make(map[string]*Meter), - MaxUIVolume: pulse.VolumeUIMax, + service: service, + meters: make(map[string]*Meter), + MaxUIVolume: pulse.VolumeUIMax, AudioServerState: AudioStateChanged, + cardFixGroup: newCardFixGroup(), } var err error @@ -991,13 +993,39 @@ func (a *Audio) setPort(cardId uint32, portName string, direction int, auto bool logger.Warning(err) return err } + + // 提前校验:sink 方向的端口必须有可用的 profiles + if direction == pulse.DirectionSink && port.Profiles == nil { + logger.Warningf("setPort: card %d port %s has nil profiles", cardId, portName) + return fmt.Errorf("card %d port %s has nil profiles", cardId, portName) + } + // 获取声卡配置中的profile var profile string if direction == pulse.DirectionSink { profile = GetConfigKeeper().GetMode(card, portName) if profile == "" { profile = port.Profiles.SelectProfile() + if profile == "" { + logger.Warningf("setPort: no available profile for card %d port %s", cardId, portName) + return fmt.Errorf("no available profile for card %d port %s", cardId, portName) + } GetConfigKeeper().SetMode(card, portName, profile) + } else { + // 情况一:兼容pipewire升级导致的声卡的配置文件的发生变化,导致配置找不到 + // 情况二:另一种情况是pipewire初始化时,音频端口不是同时上报上来的,导致配置文件找不到,暂时先切换到已存在的profile + // 由于情况二的场景存在,不能在这里修改profile + if !card.Profiles.Exists(profile) { + oldProfile := profile + profile = port.Profiles.SelectProfile() + if profile == "" { + logger.Warningf("setPort: no available fallback profile for card %d port %s", cardId, portName) + return fmt.Errorf("no available fallback profile for card %d port %s", cardId, portName) + } + a.cardFixGroup.addFix(cardId, 30*time.Second, func() { + card.fixProfile(a, portName, oldProfile) + }) + } } } else { profile = card.ActiveProfile.Name diff --git a/audio1/audio_events.go b/audio1/audio_events.go index 0d69d222f..6648c8a72 100644 --- a/audio1/audio_events.go +++ b/audio1/audio_events.go @@ -335,6 +335,7 @@ func (a *Audio) handleCardRemoved(idx uint32) { return } logger.Infof("card <%s:%d> removed", oldCard.core.Name, idx) + a.cardFixGroup.deleteFix(idx) a.cards, _ = a.cards.delete(idx) cards := a.cards.string() a.setPropCards(cards) diff --git a/audio1/card.go b/audio1/card.go index 7a0eb15b5..0c91f829e 100644 --- a/audio1/card.go +++ b/audio1/card.go @@ -9,6 +9,8 @@ import ( "math" "sort" "strings" + "sync" + "time" "github.com/linuxdeepin/go-lib/pulse" "github.com/linuxdeepin/go-lib/strv" @@ -425,3 +427,108 @@ func (card *Card) doDiff(oldCard *Card, autoPause bool) ChangeType { } return changed } + +// CardFixGroup 管理需要延迟修复的声卡 +type CardFixGroup struct { + mu sync.Mutex + fixes map[uint32]*CardFix +} + +func newCardFixGroup() *CardFixGroup { + return &CardFixGroup{ + fixes: make(map[uint32]*CardFix), + } +} + +// addFix 添加一个待修复的声卡,延迟delay后执行fixFn(sync.Once保证只执行一次) +func (cfg *CardFixGroup) addFix(cardId uint32, delay time.Duration, fixFn func()) { + if fixFn == nil { + logger.Warning("addFix: fixFn is nil, skip") + return + } + + cfg.mu.Lock() + defer cfg.mu.Unlock() + + // 已存在则跳过 + if _, exists := cfg.fixes[cardId]; exists { + return + } + + fix := &CardFix{ + cardId: cardId, + fixFn: fixFn, + } + fix.timer = time.AfterFunc(delay, func() { + fix.once.Do(func() { + fix.fixFn() + }) + }) + cfg.fixes[cardId] = fix +} + +// deleteFix 删除并停止指定声卡的修复定时器 +func (cfg *CardFixGroup) deleteFix(cardId uint32) { + cfg.mu.Lock() + defer cfg.mu.Unlock() + + if fix, exists := cfg.fixes[cardId]; exists { + if fix.timer != nil { + fix.timer.Stop() + } + delete(cfg.fixes, cardId) + } +} + +// fixProfile 修复声卡配置中的profile:配置中保存的profile一直不在card的profile列表中,则更新配置 +func (c *Card) fixProfile(a *Audio, portName string, oldProfile string) { + if a == nil { + logger.Warning("fixProfile: audio is nil") + return + } + + // 加锁保护 a.cards 的并发访问,避免与 handleCardRemoved 产生数据竞争 + a.mu.Lock() + currentCard, err := a.cards.get(c.Id) + a.mu.Unlock() + + if err != nil { + logger.Warningf("fixProfile: card %d not found", c.Id) + return + } + + if currentCard == nil { + logger.Warningf("fixProfile: card %d is nil", c.Id) + return + } + + // 配置已经被其他流程更新,无需修复 + currentMode := GetConfigKeeper().GetMode(currentCard, portName) + if currentMode != oldProfile { + logger.Debugf("fixProfile: config already changed to %s, skip", currentMode) + return + } + + // 配置中保存的profile又出现了,说明是临时性变化,跳过修复 + if currentCard.Profiles.Exists(oldProfile) { + logger.Debugf("fixProfile: old profile %s exists again, skip", oldProfile) + return + } + + if currentCard.ActiveProfile == nil { + logger.Warningf("fixProfile: card %d ActiveProfile is nil", c.Id) + return + } + + logger.Infof("fixProfile: update config profile from %s to %s for port %s", + oldProfile, currentCard.ActiveProfile.Name, portName) + GetConfigKeeper().SetMode(currentCard, portName, currentCard.ActiveProfile.Name) +} + +// CardFix 单个声卡的修复项,fixFn由调用方提供具体修复逻辑 +type CardFix struct { + cardId uint32 + fixFn func() + timer *time.Timer + once sync.Once +} diff --git a/audio1/profile.go b/audio1/profile.go index ea0da2f8d..0d61d20da 100644 --- a/audio1/profile.go +++ b/audio1/profile.go @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2018 - 2022 UnionTech Software Technology Co., Ltd. +// SPDX-FileCopyrightText: 2018 - 2026 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: GPL-3.0-or-later @@ -33,6 +33,18 @@ func newProfile(info pulse.ProfileInfo2) *Profile { type ProfileList []*Profile +func (l ProfileList) Exists(name string) bool { + if l == nil { + return false + } + for _, p := range l { + if p != nil && p.Name == name { + return true + } + } + return false +} + func newProfileList(src []pulse.ProfileInfo2) ProfileList { var result ProfileList blacklist := profileBlacklist()