HarmonyOS开发音频效果:均衡器、低音增强与虚拟环绕声实战

举报
Jack20 发表于 2026/06/20 20:35:22 2026/06/20
【摘要】 一、背景与动机你有没有注意到,同一个耳机在不同手机上听同一首歌,效果可能天差地别?有的手机低音浑厚有力,有的则干瘪单薄;有的手机看视频环绕感十足,有的就像从罐子里传出来的声音。这背后的秘密,就是音频效果处理。音频效果就像给声音加滤镜。就像修图软件有亮度、对比度、饱和度调节一样,音频效果有均衡器(调节各频段音量)、低音增强(让低频更有冲击力)、虚拟环绕声(模拟多声道空间感)等。这些效果叠加在...

一、背景与动机

你有没有注意到,同一个耳机在不同手机上听同一首歌,效果可能天差地别?有的手机低音浑厚有力,有的则干瘪单薄;有的手机看视频环绕感十足,有的就像从罐子里传出来的声音。这背后的秘密,就是音频效果处理

音频效果就像给声音加滤镜。就像修图软件有亮度、对比度、饱和度调节一样,音频效果有均衡器(调节各频段音量)、低音增强(让低频更有冲击力)、虚拟环绕声(模拟多声道空间感)等。这些效果叠加在一起,就构成了音频效果链

为什么需要了解音频效果?

  • 音乐播放器:用户期望能自定义 EQ,调节出自己喜欢的音色
  • 视频播放:虚拟环绕声让手机也能有影院般的沉浸感
  • 游戏音频:低音增强让爆炸声更有冲击力,3D 音效让定位更精准
  • 语音通话:降噪、回声消除让通话更清晰
  • 品牌差异化:好的音频调校是产品的重要卖点

今天咱们就来深入 HarmonyOS 的音频效果体系。


二、核心原理

2.1 音频效果框架总览

HarmonyOS 的音频效果处理框架由以下几层构成:

flowchart TB
    classDef primary fill:#4CAF50,stroke:#388E3C,color:#fff
    classDef warning fill:#FF9800,stroke:#F57C00,color:#fff
    classDef error fill:#F44336,stroke:#D32F2F,color:#fff
    classDef info fill:#2196F3,stroke:#1976D2,color:#fff
    classDef purple fill:#9C27B0,stroke:#7B1FA2,color:#fff

    A[应用层] --> B[AudioEffect API]
    B --> C[音频效果链]
    C --> D[均衡器 Equalizer]
    C --> E[低音增强 BassBoost]
    C --> F[虚拟环绕声 VirtualSurround]
    C --> G[降噪 NoiseSuppression]
    C --> H[回声消除 AEC]

    D --> D1[频段增益调节]
    D --> D2[预设模式切换]
    E --> E1[低频增强强度]
    F --> F1[空间模拟算法]
    G --> G1[背景噪声过滤]
    H --> H1[回声信号消除]

    I[AudioRenderer] --> C
    C --> J[音频输出设备]

    class A primary
    class B info
    class C warning
    class D primary
    class E purple
    class F info
    class G error
    class H error
    class I primary
    class J primary

2.2 均衡器(Equalizer)原理

均衡器是音频效果中最核心的工具。它的工作原理是将音频信号按频率分成多个频段,然后分别调节每个频段的增益(音量大小)。

打个比方:均衡器就像一个调色板。低频(Bass)是暖色调,中频(Mid)是中间色调,高频(Treble)是冷色调。你可以让暖色调更浓(增强低音),让冷色调更亮(增强高音),调出你喜欢的"声音色彩"。

典型频段划分

频段 频率范围 听感描述 典型应用
超低频 20-60 Hz 震撼感、轰鸣 电影音效、电子乐
低频 60-250 Hz 厚度、力度 鼓声、贝斯
中低频 250-500 Hz 温暖感 人声基频
中频 500-2000 Hz 清晰度 语音、主旋律
中高频 2000-4000 Hz 亮度 弦乐、钢琴
高频 4000-8000 Hz 空气感 镲片、齿音
超高频 8000-20000 Hz 细节、泛音 环境声、空间感

2.3 音频效果链

多个音频效果可以串联起来,形成效果链。信号从源头流经每个效果处理器,最终输出到设备:

flowchart LR
    classDef primary fill:#4CAF50,stroke:#388E3C,color:#fff
    classDef warning fill:#FF9800,stroke:#F57C00,color:#fff
    classDef error fill:#F44336,stroke:#D32F2F,color:#fff
    classDef info fill:#2196F3,stroke:#1976D2,color:#fff
    classDef purple fill:#9C27B0,stroke:#7B1FA2,color:#fff

    A[原始音频] --> B[均衡器]
    B --> C[低音增强]
    C --> D[虚拟环绕声]
    D --> E[输出]

    class A primary
    class B info
    class C warning
    class D purple
    class E primary

效果链顺序很重要:先均衡再增强低音,和先增强低音再均衡,效果可能完全不同。一般推荐:均衡器 → 低音增强 → 虚拟环绕声


三、代码实战

3.1 均衡器控制面板

实现一个完整的均衡器,支持频段调节和预设切换:

import { audio } from '@kit.AudioKit';
import { BusinessError } from '@kit.BasicServicesKit';

// 均衡器频段信息
interface BandInfo {
  centerFreq: number;  // 中心频率 Hz
  bandIndex: number;   // 频段索引
  gain: number;        // 当前增益 dB
  minGain: number;     // 最小增益
  maxGain: number;     // 最大增益
}

// 均衡器预设
interface EQPreset {
  name: string;
  gains: number[];  // 各频段增益值
}

@Entry
@Component
struct EqualizerPanel {
  // 均衡器效果实例
  private equalizer: audio.AudioEffectEqualizer | null = null;
  // 音频渲染器(效果需要绑定到渲染器)
  private audioRenderer: audio.AudioRenderer | null = null;

  // 频段列表
  @State bands: BandInfo[] = [];
  // 当前预设名称
  @State currentPreset: string = '默认';
  // 是否已初始化
  @State isInitialized: boolean = false;

  // 预设列表
  private presets: EQPreset[] = [
    { name: '默认', gains: [0, 0, 0, 0, 0] },
    { name: '流行', gains: [1, 3, 5, 3, 1] },
    { name: '摇滚', gains: [4, 2, -1, 2, 4] },
    { name: '古典', gains: [3, 1, 0, 1, 3] },
    { name: '爵士', gains: [2, 0, -1, 1, 3] },
    { name: '电子', gains: [5, 3, 0, 2, 4] },
    { name: '人声', gains: [-1, 0, 4, 2, -1] },
    { name: '低音增强', gains: [5, 4, 1, 0, 0] },
    { name: '高音增强', gains: [0, 0, 1, 3, 5] },
  ];

  aboutToAppear(): void {
    this.initEqualizer();
  }

  aboutToDisappear(): void {
    this.releaseEqualizer();
  }

  /**
   * 初始化均衡器
   */
  async initEqualizer(): Promise<void> {
    try {
      // 创建音频渲染器(均衡器需要绑定到渲染器)
      const rendererOptions: audio.AudioRendererOptions = {
        streamInfo: {
          samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_44100,
          channels: audio.AudioChannel.CHANNEL_2,
          sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_S16LE,
          encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW
        },
        rendererInfo: {
          usage: audio.StreamUsage.STREAM_USAGE_MUSIC,
          rendererFlags: 0
        }
      };

      this.audioRenderer = await audio.createAudioRenderer(rendererOptions);

      // 创建均衡器效果
      this.equalizer = await audio.createAudioEffectEqualizer(this.audioRenderer);

      // 获取频段信息
      const bandCount = this.equalizer.getNumberOfBands();
      const bandList: BandInfo[] = [];

      for (let i = 0; i < bandCount; i++) {
        const centerFreq = this.equalizer.getCenterFreq(i);
        const bandRange = this.equalizer.getBandFreqRange(i);
        const minGain = this.equalizer.getMinGain(i);
        const maxGain = this.equalizer.getMaxGain(i);

        bandList.push({
          centerFreq: centerFreq,
          bandIndex: i,
          gain: 0,
          minGain: minGain,
          maxGain: maxGain
        });
      }

      this.bands = bandList;
      this.isInitialized = true;
      console.info(`[EQ] 初始化成功,共 ${bandCount} 个频段`);

    } catch (err) {
      const error = err as BusinessError;
      console.error(`[EQ] 初始化失败: ${error.message}`);
    }
  }

  /**
   * 设置指定频段的增益
   */
  setBandGain(bandIndex: number, gainDb: number): void {
    if (!this.equalizer) return;

    try {
      this.equalizer.setBandGain(bandIndex, gainDb);
      // 更新本地状态
      this.bands[bandIndex].gain = gainDb;
      console.info(`[EQ] 频段${bandIndex} 增益设为 ${gainDb}dB`);
    } catch (err) {
      const error = err as BusinessError;
      console.error(`[EQ] 设置增益失败: ${error.message}`);
    }
  }

  /**
   * 应用预设
   */
  applyPreset(preset: EQPreset): void {
    if (!this.equalizer || this.bands.length === 0) return;

    const gainCount = Math.min(preset.gains.length, this.bands.length);
    for (let i = 0; i < gainCount; i++) {
      const clampedGain = Math.max(this.bands[i].minGain, Math.min(this.bands[i].maxGain, preset.gains[i]));
      this.setBandGain(i, clampedGain);
    }
    this.currentPreset = preset.name;
    console.info(`[EQ] 应用预设: ${preset.name}`);
  }

  /**
   * 重置所有频段为0
   */
  resetBands(): void {
    for (let i = 0; i < this.bands.length; i++) {
      this.setBandGain(i, 0);
    }
    this.currentPreset = '默认';
  }

  /**
   * 格式化频率显示
   */
  formatFrequency(hz: number): string {
    if (hz >= 1000) {
      return `${(hz / 1000).toFixed(hz >= 10000 ? 0 : 1)}k`;
    }
    return `${hz}`;
  }

  /**
   * 释放资源
   */
  async releaseEqualizer(): Promise<void> {
    try {
      if (this.equalizer) {
        this.equalizer.release();
        this.equalizer = null;
      }
      if (this.audioRenderer) {
        await this.audioRenderer.release();
        this.audioRenderer = null;
      }
    } catch (err) {
      console.error('[EQ] 释放失败');
    }
  }

  build() {
    Column({ space: 16 }) {
      // 标题
      Text('🎛️ 均衡器')
        .fontSize(24)
        .fontWeight(FontWeight.Bold)

      Text(`当前预设: ${this.currentPreset}`)
        .fontSize(14)
        .fontColor('#4CAF50')

      // 预设选择
      Scroll(Scroller) {
        Row({ space: 8 }) {
          ForEach(this.presets, (preset: EQPreset) => {
            Text(preset.name)
              .fontSize(13)
              .padding({ left: 12, right: 12, top: 6, bottom: 6 })
              .borderRadius(16)
              .backgroundColor(this.currentPreset === preset.name ? '#4CAF50' : '#333')
              .fontColor(this.currentPreset === preset.name ? '#fff' : '#aaa')
              .onClick(() => this.applyPreset(preset))
          })
        }
      }
      .scrollable(ScrollDirection.Horizontal)
      .width('90%')

      // 频段调节
      if (this.bands.length > 0) {
        Row({ space: 4 }) {
          ForEach(this.bands, (band: BandInfo) => {
            Column({ space: 8 }) {
              // 增益值显示
              Text(`${band.gain > 0 ? '+' : ''}${band.gain.toFixed(0)}`)
                .fontSize(10)
                .fontColor(band.gain > 0 ? '#4CAF50' : band.gain < 0 ? '#F44336' : '#888')
                .height(20)

              // 竖向滑块
              Slider({
                value: band.gain,
                min: band.minGain,
                max: band.maxGain,
                step: 1,
                style: SliderStyle.InSet
              })
                .width(40)
                .height(180)
                .vertical(true)
                .blockColor('#4CAF50')
                .trackColor('#333')
                .onChange((value: number) => {
                  this.setBandGain(band.bandIndex, Math.round(value));
                })

              // 频率标签
              Text(this.formatFrequency(band.centerFreq))
                .fontSize(10)
                .fontColor('#888')
                .height(20)
            }
            .layoutWeight(1)
          })
        }
        .width('95%')
        .height(260)
      }

      // 重置按钮
      Button('重置')
        .width(120)
        .backgroundColor('#666')
        .onClick(() => this.resetBands())

    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
    .padding(20)
    .backgroundColor('#1a1a2e')
  }
}

3.2 低音增强与虚拟环绕声

低音增强和虚拟环绕声是音乐播放器的标配效果。下面实现一个组合效果控制器:

import { audio } from '@kit.AudioKit';
import { BusinessError } from '@kit.BasicServicesKit';

@Entry
@Component
struct AudioEffectController {
  // 音频渲染器
  private audioRenderer: audio.AudioRenderer | null = null;
  // 低音增强效果
  private bassBoost: audio.AudioEffectBassBoost | null = null;
  // 虚拟环绕声效果
  private virtualSurround: audio.AudioEffectVirtualSurround | null = null;

  // 低音增强强度(0-1000)
  @State bassStrength: number = 0;
  // 虚拟环绕声开关
  @State surroundEnabled: boolean = false;
  // 虚拟环绕声模式
  @State surroundMode: string = '关闭';
  // 整体效果开关
  @State effectEnabled: boolean = true;
  // 状态文本
  @State statusText: string = '未初始化';

  aboutToAppear(): void {
    this.initAudioEffects();
  }

  aboutToDisappear(): void {
    this.releaseEffects();
  }

  /**
   * 初始化音频效果
   */
  async initAudioEffects(): Promise<void> {
    try {
      // 创建音频渲染器
      const rendererOptions: audio.AudioRendererOptions = {
        streamInfo: {
          samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_48000,
          channels: audio.AudioChannel.CHANNEL_2,
          sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_S16LE,
          encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW
        },
        rendererInfo: {
          usage: audio.StreamUsage.STREAM_USAGE_MUSIC,
          rendererFlags: 0
        }
      };

      this.audioRenderer = await audio.createAudioRenderer(rendererOptions);

      // 创建低音增强效果
      this.bassBoost = await audio.createAudioEffectBassBoost(this.audioRenderer);

      // 创建虚拟环绕声效果
      this.virtualSurround = await audio.createAudioEffectVirtualSurround(this.audioRenderer);

      this.statusText = '效果已就绪';
      console.info('[效果] 初始化成功');

    } catch (err) {
      const error = err as BusinessError;
      console.error(`[效果] 初始化失败: ${error.message}`);
      this.statusText = '初始化失败';
    }
  }

  /**
   * 设置低音增强强度
   * @param strength 强度值 0-1000
   */
  setBassBoostStrength(strength: number): void {
    if (!this.bassBoost) return;

    try {
      // 检查低音增强是否支持
      if (!this.bassBoost.isBassBoostSupported()) {
        console.warn('[效果] 设备不支持低音增强');
        return;
      }

      // 设置强度
      this.bassBoost.setStrength(strength);
      this.bassStrength = strength;
      console.info(`[效果] 低音增强强度: ${strength}`);
    } catch (err) {
      const error = err as BusinessError;
      console.error(`[效果] 设置低音增强失败: ${error.message}`);
    }
  }

  /**
   * 切换虚拟环绕声
   */
  toggleVirtualSurround(enabled: boolean): void {
    if (!this.virtualSurround) return;

    try {
      if (enabled) {
        this.virtualSurround.enable();
        this.surroundEnabled = true;
        this.surroundMode = '开启';
        console.info('[效果] 虚拟环绕声已开启');
      } else {
        this.virtualSurround.disable();
        this.surroundEnabled = false;
        this.surroundMode = '关闭';
        console.info('[效果] 虚拟环绕声已关闭');
      }
    } catch (err) {
      const error = err as BusinessError;
      console.error(`[效果] 切换虚拟环绕声失败: ${error.message}`);
    }
  }

  /**
   * 切换整体效果开关
   */
  toggleEffect(enabled: boolean): void {
    this.effectEnabled = enabled;

    if (!enabled) {
      // 关闭所有效果
      this.setBassBoostStrength(0);
      this.toggleVirtualSurround(false);
    }
  }

  /**
   * 获取低音增强描述
   */
  getBassDescription(): string {
    if (this.bassStrength === 0) return '关闭';
    if (this.bassStrength < 300) return '轻微';
    if (this.bassStrength < 600) return '中等';
    if (this.bassStrength < 900) return '强烈';
    return '极致';
  }

  /**
   * 释放所有效果资源
   */
  async releaseEffects(): Promise<void> {
    try {
      if (this.bassBoost) {
        this.bassBoost.release();
        this.bassBoost = null;
      }
      if (this.virtualSurround) {
        this.virtualSurround.release();
        this.virtualSurround = null;
      }
      if (this.audioRenderer) {
        await this.audioRenderer.release();
        this.audioRenderer = null;
      }
    } catch (err) {
      console.error('[效果] 释放失败');
    }
  }

  build() {
    Column({ space: 24 }) {
      // 标题
      Row() {
        Text('🔊 音频效果控制器')
          .fontSize(22)
          .fontWeight(FontWeight.Bold)
          .layoutWeight(1)

        // 总开关
        Toggle({ type: ToggleType.Switch, isOn: this.effectEnabled })
          .onChange((isOn: boolean) => this.toggleEffect(isOn))
          .selectedColor('#4CAF50')
      }
      .width('90%')

      Text(this.statusText)
        .fontSize(14)
        .fontColor('#888')

      // 低音增强控制
      Column({ space: 12 }) {
        Row() {
          Text('🎵 低音增强')
            .fontSize(18)
            .fontWeight(FontWeight.Medium)
            .layoutWeight(1)

          Text(this.getBassDescription())
            .fontSize(14)
            .fontColor('#FF9800')
        }
        .width('100%')

        // 强度滑块
        Slider({
          value: this.bassStrength,
          min: 0,
          max: 1000,
          step: 10,
          style: SliderStyle.OutSet
        })
          .width('100%')
          .blockColor('#FF9800')
          .trackColor('#333')
          .onChange((value: number) => {
            if (this.effectEnabled) {
              this.setBassBoostStrength(Math.round(value));
            }
          })

        // 预设按钮
        Row({ space: 8 }) {
          Text('预设:')
            .fontSize(14)
            .fontColor('#888')

          ForEach([
            { name: '关闭', value: 0 },
            { name: '轻微', value: 200 },
            { name: '中等', value: 500 },
            { name: '强烈', value: 800 }
          ], (item: { name: string; value: number }) => {
            Text(item.name)
              .fontSize(12)
              .padding({ left: 10, right: 10, top: 4, bottom: 4 })
              .borderRadius(12)
              .backgroundColor(this.bassStrength === item.value ? '#FF9800' : '#333')
              .fontColor(this.bassStrength === item.value ? '#fff' : '#aaa')
              .onClick(() => {
                if (this.effectEnabled) {
                  this.setBassBoostStrength(item.value);
                }
              })
          })
        }
      }
      .width('90%')
      .padding(16)
      .borderRadius(12)
      .backgroundColor('#252540')

      // 虚拟环绕声控制
      Column({ space: 12 }) {
        Row() {
          Text('🎧 虚拟环绕声')
            .fontSize(18)
            .fontWeight(FontWeight.Medium)
            .layoutWeight(1)

          Toggle({ type: ToggleType.Switch, isOn: this.surroundEnabled })
            .onChange((isOn: boolean) => {
              if (this.effectEnabled) {
                this.toggleVirtualSurround(isOn);
              }
            })
            .selectedColor('#9C27B0')
        }
        .width('100%')

        Text('模拟多声道空间感,让声音更有沉浸感')
          .fontSize(12)
          .fontColor('#888')

        // 环绕声模式说明
        Row({ space: 8 }) {
          Text('💡 提示: 戴耳机效果最佳')
            .fontSize(12)
            .fontColor('#9C27B0')
        }
      }
      .width('90%')
      .padding(16)
      .borderRadius(12)
      .backgroundColor('#252540')

      // 效果链预览
      Column({ space: 8 }) {
        Text('效果链')
          .fontSize(16)
          .fontWeight(FontWeight.Medium)
          .width('100%')

        Row({ space: 8 }) {
          Text('原始音频')
            .fontSize(12)
            .padding(6)
            .borderRadius(8)
            .backgroundColor('#2196F3')

          Text('→')
            .fontSize(16)
            .fontColor('#666')

          Text(`低音${this.getBassDescription()}`)
            .fontSize(12)
            .padding(6)
            .borderRadius(8)
            .backgroundColor(this.bassStrength > 0 ? '#FF9800' : '#333')

          Text('→')
            .fontSize(16)
            .fontColor('#666')

          Text(`环绕${this.surroundMode}`)
            .fontSize(12)
            .padding(6)
            .borderRadius(8)
            .backgroundColor(this.surroundEnabled ? '#9C27B0' : '#333')

          Text('→')
            .fontSize(16)
            .fontColor('#666')

          Text('输出')
            .fontSize(12)
            .padding(6)
            .borderRadius(8)
            .backgroundColor('#4CAF50')
        }
      }
      .width('90%')
      .padding(16)
      .borderRadius(12)
      .backgroundColor('#252540')
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
    .padding(20)
    .backgroundColor('#1a1a2e')
  }
}

3.3 音频效果链管理器

将均衡器、低音增强、虚拟环绕声整合到一个统一的效果链管理器中,支持效果开关、参数保存与恢复:

import { audio } from '@kit.AudioKit';
import { preferences } from '@kit.ArkData';
import { BusinessError } from '@kit.BasicServicesKit';

// 效果配置模型
interface EffectConfig {
  eqGains: number[];          // 均衡器各频段增益
  eqPresetName: string;       // 当前预设名称
  bassStrength: number;       // 低音增强强度
  surroundEnabled: boolean;   // 虚拟环绕声开关
}

@Entry
@Component
struct EffectChainManager {
  // 音频渲染器
  private audioRenderer: audio.AudioRenderer | null = null;
  // 效果实例
  private equalizer: audio.AudioEffectEqualizer | null = null;
  private bassBoost: audio.AudioEffectBassBoost | null = null;
  private virtualSurround: audio.AudioEffectVirtualSurround | null = null;

  // 状态
  @State eqGains: number[] = [0, 0, 0, 0, 0];
  @State eqPresetName: string = '默认';
  @State bassStrength: number = 0;
  @State surroundEnabled: boolean = false;
  @State masterEnabled: boolean = true;
  @State isInitialized: boolean = false;

  // 均衡器预设
  private eqPresets: Map<string, number[]> = new Map([
    ['默认', [0, 0, 0, 0, 0]],
    ['流行', [1, 3, 5, 3, 1]],
    ['摇滚', [4, 2, -1, 2, 4]],
    ['古典', [3, 1, 0, 1, 3]],
    ['电子', [5, 3, 0, 2, 4]],
    ['人声', [-1, 0, 4, 2, -1]],
  ]);

  // 频段名称
  private bandNames: string[] = ['60Hz', '230Hz', '910Hz', '3.6kHz', '14kHz'];

  aboutToAppear(): void {
    this.initEffectChain();
    this.loadConfig();
  }

  aboutToDisappear(): void {
    this.saveConfig();
    this.releaseAll();
  }

  /**
   * 初始化完整效果链
   */
  async initEffectChain(): Promise<void> {
    try {
      // 创建渲染器
      const rendererOptions: audio.AudioRendererOptions = {
        streamInfo: {
          samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_48000,
          channels: audio.AudioChannel.CHANNEL_2,
          sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_S16LE,
          encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW
        },
        rendererInfo: {
          usage: audio.StreamUsage.STREAM_USAGE_MUSIC,
          rendererFlags: 0
        }
      };

      this.audioRenderer = await audio.createAudioRenderer(rendererOptions);

      // 按顺序创建效果:均衡器 → 低音增强 → 虚拟环绕声
      this.equalizer = await audio.createAudioEffectEqualizer(this.audioRenderer);
      this.bassBoost = await audio.createAudioEffectBassBoost(this.audioRenderer);
      this.virtualSurround = await audio.createAudioEffectVirtualSurround(this.audioRenderer);

      this.isInitialized = true;
      console.info('[效果链] 初始化完成');

    } catch (err) {
      const error = err as BusinessError;
      console.error(`[效果链] 初始化失败: ${error.message}`);
    }
  }

  /**
   * 应用完整效果配置
   */
  applyConfig(config: EffectConfig): void {
    // 应用均衡器
    if (this.equalizer) {
      const bandCount = this.equalizer.getNumberOfBands();
      for (let i = 0; i < Math.min(config.eqGains.length, bandCount); i++) {
        try {
          this.equalizer.setBandGain(i, config.eqGains[i]);
        } catch (err) {
          console.warn(`[效果链] EQ频段${i}设置失败`);
        }
      }
    }

    // 应用低音增强
    if (this.bassBoost) {
      try {
        this.bassBoost.setStrength(config.bassStrength);
      } catch (err) {
        console.warn('[效果链] 低音增强设置失败');
      }
    }

    // 应用虚拟环绕声
    if (this.virtualSurround) {
      try {
        if (config.surroundEnabled) {
          this.virtualSurround.enable();
        } else {
          this.virtualSurround.disable();
        }
      } catch (err) {
        console.warn('[效果链] 虚拟环绕声设置失败');
      }
    }

    // 更新状态
    this.eqGains = [...config.eqGains];
    this.eqPresetName = config.eqPresetName;
    this.bassStrength = config.bassStrength;
    this.surroundEnabled = config.surroundEnabled;
  }

  /**
   * 获取当前效果配置
   */
  getCurrentConfig(): EffectConfig {
    return {
      eqGains: [...this.eqGains],
      eqPresetName: this.eqPresetName,
      bassStrength: this.bassStrength,
      surroundEnabled: this.surroundEnabled
    };
  }

  /**
   * 保存配置到本地
   */
  async saveConfig(): Promise<void> {
    try {
      const context = getContext(this);
      const prefs = await preferences.getPreferences(context, 'audio_effect_config');
      const config = this.getCurrentConfig();

      await prefs.put('eqGains', JSON.stringify(config.eqGains));
      await prefs.put('eqPresetName', config.eqPresetName);
      await prefs.put('bassStrength', config.bassStrength);
      await prefs.put('surroundEnabled', config.surroundEnabled);
      await prefs.flush();

      console.info('[效果链] 配置已保存');
    } catch (err) {
      console.error('[效果链] 保存配置失败');
    }
  }

  /**
   * 从本地加载配置
   */
  async loadConfig(): Promise<void> {
    try {
      const context = getContext(this);
      const prefs = await preferences.getPreferences(context, 'audio_effect_config');

      const eqGainsStr = await prefs.get('eqGains', '[0,0,0,0,0]') as string;
      const eqPresetName = await prefs.get('eqPresetName', '默认') as string;
      const bassStrength = await prefs.get('bassStrength', 0) as number;
      const surroundEnabled = await prefs.get('surroundEnabled', false) as boolean;

      const config: EffectConfig = {
        eqGains: JSON.parse(eqGainsStr),
        eqPresetName: eqPresetName,
        bassStrength: bassStrength,
        surroundEnabled: surroundEnabled
      };

      this.applyConfig(config);
      console.info('[效果链] 配置已加载');
    } catch (err) {
      console.warn('[效果链] 加载配置失败,使用默认值');
    }
  }

  /**
   * 重置所有效果
   */
  resetAll(): void {
    this.applyConfig({
      eqGains: [0, 0, 0, 0, 0],
      eqPresetName: '默认',
      bassStrength: 0,
      surroundEnabled: false
    });
  }

  /**
   * 释放所有资源
   */
  async releaseAll(): Promise<void> {
    try {
      this.equalizer?.release();
      this.bassBoost?.release();
      this.virtualSurround?.release();
      await this.audioRenderer?.release();

      this.equalizer = null;
      this.bassBoost = null;
      this.virtualSurround = null;
      this.audioRenderer = null;
    } catch (err) {
      console.error('[效果链] 释放失败');
    }
  }

  build() {
    Scroll() {
      Column({ space: 20 }) {
        // 标题栏
        Row() {
          Text('🎛️ 效果链管理器')
            .fontSize(22)
            .fontWeight(FontWeight.Bold)
            .layoutWeight(1)

          Toggle({ type: ToggleType.Switch, isOn: this.masterEnabled })
            .onChange((isOn: boolean) => {
              this.masterEnabled = isOn;
              if (!isOn) this.resetAll();
            })
            .selectedColor('#4CAF50')
        }
        .width('90%')

        // 均衡器区域
        Column({ space: 12 }) {
          Text('📊 均衡器')
            .fontSize(18)
            .fontWeight(FontWeight.Medium)
            .width('100%')

          // 预设选择
          Scroll(Scroller) {
            Row({ space: 6 }) {
              ForEach(Array.from(this.eqPresets.entries()), ([name, gains]: [string, number[]]) => {
                Text(name)
                  .fontSize(12)
                  .padding({ left: 10, right: 10, top: 4, bottom: 4 })
                  .borderRadius(12)
                  .backgroundColor(this.eqPresetName === name ? '#4CAF50' : '#333')
                  .fontColor(this.eqPresetName === name ? '#fff' : '#aaa')
                  .onClick(() => {
                    this.eqPresetName = name;
                    this.eqGains = [...gains];
                    if (this.equalizer) {
                      gains.forEach((g, i) => this.equalizer?.setBandGain(i, g));
                    }
                  })
              })
            }
          }
          .scrollable(ScrollDirection.Horizontal)

          // 频段调节
          Row({ space: 4 }) {
            ForEach(this.eqGains, (gain: number, index: number) => {
              Column({ space: 4 }) {
                Text(`${gain > 0 ? '+' : ''}${gain}`)
                  .fontSize(9)
                  .fontColor(gain > 0 ? '#4CAF50' : gain < 0 ? '#F44336' : '#666')

                Slider({
                  value: gain,
                  min: -10,
                  max: 10,
                  step: 1,
                  style: SliderStyle.InSet
                })
                  .width(36)
                  .height(120)
                  .vertical(true)
                  .blockColor('#4CAF50')
                  .onChange((value: number) => {
                    this.eqGains[index] = Math.round(value);
                    this.eqPresetName = '自定义';
                    this.equalizer?.setBandGain(index, Math.round(value));
                  })

                Text(this.bandNames[index] || `${index}`)
                  .fontSize(8)
                  .fontColor('#888')
              }
              .layoutWeight(1)
            })
          }
          .width('100%')
        }
        .width('90%')
        .padding(16)
        .borderRadius(12)
        .backgroundColor('#252540')

        // 低音增强区域
        Column({ space: 12 }) {
          Row() {
            Text('🎵 低音增强')
              .fontSize(18)
              .fontWeight(FontWeight.Medium)
              .layoutWeight(1)

            Text(this.bassStrength > 0 ? `${Math.round(this.bassStrength / 10)}%` : '关闭')
              .fontSize(14)
              .fontColor('#FF9800')
          }

          Slider({
            value: this.bassStrength,
            min: 0,
            max: 1000,
            step: 10,
            style: SliderStyle.OutSet
          })
          .width('100%')
          .blockColor('#FF9800')
          .onChange((value: number) => {
            this.bassStrength = Math.round(value);
            this.bassBoost?.setStrength(Math.round(value));
          })
        }
        .width('90%')
        .padding(16)
        .borderRadius(12)
        .backgroundColor('#252540')

        // 虚拟环绕声区域
        Column({ space: 12 }) {
          Row() {
            Text('🎧 虚拟环绕声')
              .fontSize(18)
              .fontWeight(FontWeight.Medium)
              .layoutWeight(1)

            Toggle({ type: ToggleType.Switch, isOn: this.surroundEnabled })
              .onChange((isOn: boolean) => {
                this.surroundEnabled = isOn;
                if (isOn) {
                  this.virtualSurround?.enable();
                } else {
                  this.virtualSurround?.disable();
                }
              })
              .selectedColor('#9C27B0')
          }
        }
        .width('90%')
        .padding(16)
        .borderRadius(12)
        .backgroundColor('#252540')

        // 操作按钮
        Row({ space: 12 }) {
          Button('保存配置')
            .layoutWeight(1)
            .backgroundColor('#4CAF50')
            .onClick(() => this.saveConfig())

          Button('重置')
            .layoutWeight(1)
            .backgroundColor('#666')
            .onClick(() => this.resetAll())
        }
        .width('90%')
      }
      .width('100%')
      .alignItems(HorizontalAlign.Center)
      .padding({ top: 20, bottom: 40 })
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#1a1a2e')
  }
}

四、踩坑与注意事项

4.1 效果实例必须绑定到 AudioRenderer

问题:直接创建 AudioEffectEqualizer 不传 renderer,导致效果不生效。

原因:音频效果必须绑定到一个正在工作的音频渲染器上,否则效果无处施加。

解决方案:先创建 AudioRenderer,再基于它创建效果实例。

// ❌ 错误写法:没有绑定渲染器
const eq = await audio.createAudioEffectEqualizer();  // 参数缺失!

// ✅ 正确写法:绑定到渲染器
const renderer = await audio.createAudioRenderer(options);
const eq = await audio.createAudioEffectEqualizer(renderer);

4.2 效果参数范围超限

问题:设置均衡器增益为 20dB,但设备只支持 -10 ~ +10dB,导致设置失败。

解决方案:设置前先查询设备支持的范围。

// 查询频段支持的增益范围
const minGain = this.equalizer.getMinGain(bandIndex);
const maxGain = this.equalizer.getMaxGain(bandIndex);

// 限制在有效范围内
const clampedGain = Math.max(minGain, Math.min(maxGain, desiredGain));
this.equalizer.setBandGain(bandIndex, clampedGain);

4.3 效果链顺序影响音质

问题:先加虚拟环绕声再调均衡器,结果低音增强效果变差了。

原因:虚拟环绕声会重新分配频谱能量,之后再调均衡器可能效果不如预期。

解决方案:遵循标准效果链顺序——均衡器 → 低音增强 → 虚拟环绕声。先调整基础音色,再增强低频,最后添加空间效果。

4.4 效果与蓝牙耳机兼容性

问题:某些蓝牙耳机自带 EQ 和音效,和系统效果叠加后声音失真。

解决方案:检测输出设备类型,蓝牙设备时提供"关闭系统效果"选项。

import { audio } from '@kit.AudioKit';

// 获取当前输出设备
const audioManager = audio.getAudioManager();
const routingManager = audioManager.getRoutingManager();
const devices = routingManager.getDevices(audio.DeviceFlag.OUTPUT_DEVICES_FLAG);

// 检测是否为蓝牙设备
const isBluetooth = devices.some(d =>
  d.deviceType === audio.DeviceType.BLUETOOTH_A2DP ||
  d.deviceType === audio.DeviceType.BLUETOOTH_SCO
);

if (isBluetooth) {
  // 提示用户蓝牙设备可能自带音效
  console.info('[效果] 检测到蓝牙设备,建议关闭系统效果');
}

4.5 效果配置持久化

问题:用户调好的 EQ 每次重启 App 都重置了。

解决方案:使用 @ohos.data.preferences 保存效果配置,启动时恢复。

(示例见 3.3 节的 saveConfig / loadConfig 方法)


五、HarmonyOS 6 适配

5.1 API 变更

变更项 HarmonyOS 5 HarmonyOS 6
效果创建 createAudioEffectXxx(renderer) 保持一致,新增 createAudioEffectReverb(混响)
均衡器频段 通常 5 频段 新增 10 频段模式支持
低音增强 setStrength(0-1000) 新增 setBassBoostMode() 预设模式
虚拟环绕声 enable/disable 新增 setSurroundScene() 场景模式(影院/音乐/游戏)
混响效果 新增 AudioEffectReverb(房间/大厅/教堂等混响)

5.2 迁移指南

// HarmonyOS 5 写法
this.virtualSurround.enable();

// HarmonyOS 6 写法(支持场景模式)
this.virtualSurround.enable();
this.virtualSurround.setSurroundScene(audio.SurroundScene.SCENE_CINEMA); // 影院模式

5.3 新特性

  • 混响效果:新增房间、大厅、教堂等空间混响模拟
  • 10 频段均衡器:更精细的频率调节
  • 场景化预设:虚拟环绕声支持影院、音乐、游戏等场景模式
  • 自适应效果:根据输出设备自动调整效果参数

六、总结

mindmap
  root((音频效果))
    均衡器 Equalizer
      频段调节
        超低频 20-60Hz
        低频 60-250Hz
        中频 500-2kHz
        高频 4k-8kHz
      预设模式
        流行/摇滚/古典
        人声/电子/自定义
      API
        createAudioEffectEqualizer
        setBandGain
        getNumberOfBands
    低音增强 BassBoost
      强度调节 0-1000
      预设等级
        轻微/中等/强烈
      API
        createAudioEffectBassBoost
        setStrength
        isBassBoostSupported
    虚拟环绕声 VirtualSurround
      空间模拟
        多声道虚拟化
        头相关传递函数
      开关控制
        enable / disable
      API
        createAudioEffectVirtualSurround
        enable / disable
    效果链
      顺序原则
        均衡器 → 低音增强 → 虚拟环绕声
      绑定要求
        必须绑定AudioRenderer
      配置持久化
        preferences保存/加载
    注意事项
      参数范围检查
      蓝牙设备兼容
      效果链顺序
      资源释放

核心要点回顾

  1. 均衡器是基础:通过调节各频段增益来塑造音色,预设模式方便快速切换
  2. 低音增强简单有效:一个强度参数就能让低频更有冲击力,但别开太大
  3. 虚拟环绕声提升沉浸感:戴耳机效果最佳,适合电影和游戏场景
  4. 效果链顺序有讲究:均衡器 → 低音增强 → 虚拟环绕声,先调基础再调空间
  5. 配置要持久化:用户调好的效果要保存下来,别让用户每次都重新调
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。