鸿蒙 NEXT 下 RTSP/RTMP 播放器如何实时调节音量、亮度、对比度与饱和度?

本文基于大牛直播 SDK(SmartMediaKit)的鸿蒙 NEXT 移植版本,结合真实的 ArkTS 工程代码,完整介绍播放过程中如何对音量、画面亮度、对比度、饱和度进行实时动态调节。

背景

在监控预览、直播连麦等实时流媒体场景中,用户往往需要在不中断播放的前提下随时调整音量或画质参数。鸿蒙 NEXT(纯血鸿蒙)上的 SmartPlayer SDK 通过 Native 接口将这些能力透出给 ArkTS 层,整个调用链路清晰:

swift 复制代码
ArkTS UI(SmartPlayerPage)
      ↓  @State 绑定 / onValueChange 回调
SmartPlayerWrapper(ArkTS 封装层)
      ↓  NT 命名空间
SmartPlayerNative(libSmartPlayer.so NAPI 映射)
      ↓
C++ 原生层(OHAudio 音量 / GLES2 色彩矩阵)

所有可实时调节的参数,在 SmartPlayerWrapper 内部都有对应的 Session 状态缓存 ,页面重建或 Surface 重绑后会通过 reapplySavedConfig() 自动恢复,不需要调用方手动记录。

一、接口速览

Native 层(SmartPlayerNative.ets)

javascript 复制代码
// 音量(0 ~ 100 整数)
export function SetSmartPlayerVolume(h: number, v: number): number

// 亮度:先 enable,再 set
export function SmartPlayerEnableVideoBrightnessOption(h: number, v: number): number
export function SmartPlayerSetVideoBrightness(h: number, v: number): number

// 对比度:先 enable,再 set
export function SmartPlayerEnableVideoContrastOption(h: number, v: number): number
export function SmartPlayerSetVideoContrast(h: number, v: number): number

// 饱和度:先 enable,再 set
export function SmartPlayerEnableVideoSaturationOption(h: number, v: number): number
export function SmartPlayerSetVideoSaturation(h: number, v: number): number

注意 :亮度/对比度/饱和度三个选项都有独立的 enable 开关。首次调用前必须先 enable(true),否则 set 不生效。

Wrapper 层(SmartPlayerWrapper.ets)

Wrapper 在 Native 调用外包了一层 isOpened() 保护,并在成功后同步更新 Session 缓存:

kotlin 复制代码
// 音量
setVolume(volume: number): boolean {
  if (!this.isOpened()) return false;
  const ret = NT.SetSmartPlayerVolume(this.handle, volume) === SmartPlayerResult.OK;
  const s = this.getSession();
  if (ret && s) s.volume = volume;
  return ret;
}

// 亮度(enable + set 拆分为两个方法)
enableVideoBrightnessOption(enable: boolean): boolean {
  if (!this.isOpened()) return false;
  return NT.SmartPlayerEnableVideoBrightnessOption(this.handle, enable ? 1 : 0) === SmartPlayerResult.OK;
}
setVideoBrightness(value: number): boolean {
  if (!this.isOpened()) return false;
  const ret = NT.SmartPlayerSetVideoBrightness(this.handle, value) === SmartPlayerResult.OK;
  const s = this.getSession();
  if (ret && s) s.imageBrightnessValue = value;
  return ret;
}

// 对比度、饱和度同理(enableVideoContrastOption / setVideoContrast,enableVideoSaturationOption / setVideoSaturation)

二、UI 层设计:AdjustSliderRow 组件

​编辑

ArkUI 的 Slider 存在一个已知问题:在父组件 @State 更新时,Slider 内部位置不会自动回弹(双向绑定失效)。为此,项目封装了 AdjustSliderRow 子组件,用 @Link 把状态直接传入,彻底绕过这个问题:

typescript 复制代码
@Component
struct AdjustSliderRow {
  title: string = '';
  @Link value: number;          // @Link 双向绑定,解决回弹问题
  activeColor: string = '';
  useNeutralColorRule: boolean = true;
  onValueChange: (value: number) => void = () => {};

  private clampAdjustValue(val: number): number {
    return Math.min(100, Math.max(0, Math.round(val)));  // 值域钳制 0~100
  }

  build() {
    Row({ space: 8 }) {
      Text(this.title).fontSize(13).fontColor('#666666').width(56)

      Slider({ value: this.value, min: 0, max: 100, step: 1, style: SliderStyle.OutSet })
        .layoutWeight(1)
        .showTips(true)
        .selectedColor(this.activeColor)
        .trackColor('#D9D9D9')
        .blockColor(this.activeColor)
        .onChange((sliderValue: number) => {
          const nextValue = this.clampAdjustValue(sliderValue);
          this.value = nextValue;          // 同步到父组件 @State
          this.onValueChange(nextValue);   // 触发 Native 调用
        })

      Text(`${this.value}`)
        .fontSize(13)
        // 中性值(50)显示灰色,其余显示主题色
        .fontColor(this.useNeutralColorRule && this.value === 50 ? '#9E9E9E' : this.activeColor)
        .width(40)
        .textAlign(TextAlign.End)
    }
    .width('100%')
  }
}

useNeutralColorRule 这个参数值得注意:对于亮度/对比度/饱和度,50 是默认中性值(无效果),用灰色渲染提示用户当前没有偏移;而音量没有"中性"概念,所以传 false

三、实时调节区域完整实现

3.1 页面状态声明

less 复制代码
@State volume: number = 100;             // 音量,0~100
@State imageBrightnessValue: number = 50; // 亮度,默认中性 50
@State imageContrastValue: number = 50;   // 对比度,默认中性 50
@State imageSaturationValue: number = 50; // 饱和度,默认中性 50

3.2 UI 构建

php 复制代码
@Builder
private buildRealtimeAdjustSection() {
  Column({ space: 10 }) {
    Row() {
      Text('音量 / 画面实时调节')
        .fontSize(14)
        .fontWeight(FontWeight.Medium)
        .fontColor('#333333')

      Blank()

      Button('重置图像')
        .height(30)
        .fontSize(12)
        .backgroundColor('#607D8B')
        .fontColor(Color.White)
        .borderRadius(15)
        .padding({ left: 12, right: 12 })
        .onClick(() => { this.resetImageAdjustments(); })
    }
    .width('100%')

    // 音量滑条(无中性色规则,activeColor 蓝色)
    AdjustSliderRow({
      title: '音量',
      value: $volume,
      activeColor: '#2196F3',
      useNeutralColorRule: false,
      onValueChange: (val: number) => { this.applyAudioVolume(val); }
    })

    // 亮度滑条(有中性色规则,activeColor 橙色)
    AdjustSliderRow({
      title: '亮度',
      value: $imageBrightnessValue,
      activeColor: '#FF9800',
      useNeutralColorRule: true,
      onValueChange: (val: number) => { this.applyImageBrightness(val); }
    })

    // 对比度滑条(有中性色规则,activeColor 靛蓝)
    AdjustSliderRow({
      title: '对比度',
      value: $imageContrastValue,
      activeColor: '#3F51B5',
      useNeutralColorRule: true,
      onValueChange: (val: number) => { this.applyImageContrast(val); }
    })

    // 饱和度滑条(有中性色规则,activeColor 绿色)
    AdjustSliderRow({
      title: '饱和度',
      value: $imageSaturationValue,
      activeColor: '#4CAF50',
      useNeutralColorRule: true,
      onValueChange: (val: number) => { this.applyImageSaturation(val); }
    })

    Text('范围 0~100,默认 50 为中性值;拖动时播放画面会实时生效。')
      .fontSize(11)
      .fontColor('#888888')
      .width('100%')
  }
  .width('92%')
  .padding(12)
  .backgroundColor(Color.White)
  .borderRadius(10)
}

3.3 实际调用方法

音量调节

音量最简单,直接一个调用,无需 enable 开关:

kotlin 复制代码
private applyAudioVolume(value: number): void {
  if (!this.player.isOpened()) return;
  this.player.setVolume(value);  // 0 静音,100 满音量,播放中立即生效
}

亮度调节

亮度需要先 enable,再 set。两步必须都成功才算完整:

typescript 复制代码
private applyImageBrightness(value: number): void {
  console.info(`${this.TAG} 触发亮度调节, 当前值: ${value}`);
  if (!this.player.isOpened()) return;
  this.player.enableVideoBrightnessOption(true);      // 第一步:开启功能
  const setRet = this.player.setVideoBrightness(value); // 第二步:写入值
  console.info(`${this.TAG} setVideoBrightness ret: ${setRet}`);
}

对比度调节

typescript 复制代码
private applyImageContrast(value: number): void {
  console.info(`${this.TAG} 触发对比度调节, 当前值: ${value}`);
  if (!this.player.isOpened()) return;
  this.player.enableVideoContrastOption(true);
  const setRet = this.player.setVideoContrast(value);
  console.info(`${this.TAG} setVideoContrast ret: ${setRet}`);
}

饱和度调节

typescript 复制代码
private applyImageSaturation(value: number): void {
  console.info(`${this.TAG} 触发饱和度调节, 当前值: ${value}`);
  if (!this.player.isOpened()) return;
  this.player.enableVideoSaturationOption(true);
  const setRet = this.player.setVideoSaturation(value);
  console.info(`${this.TAG} setVideoSaturation ret: ${setRet}`);
}

一键重置图像

重置时将三个 @State 值归回 50,然后直接调 set(不需要再 enable,因为 enable 在 applyPlaybackConfig 时已经一次性开启过):

kotlin 复制代码
private resetImageAdjustments(): void {
  this.imageBrightnessValue = 50;
  this.imageContrastValue = 50;
  this.imageSaturationValue = 50;
  if (!this.player.isOpened()) return;
  this.player.setVideoBrightness(this.imageBrightnessValue);
  this.player.setVideoContrast(this.imageContrastValue);
  this.player.setVideoSaturation(this.imageSaturationValue);
}

四、播放启动时的初始化:applyPlaybackConfig

调节功能在播放启动阶段 也要完成一次完整的初始化,写入 config 里保存的默认值,并且把三个 enable 一并打开。这个逻辑集中在 SmartPlayerWrapper.applyPlaybackConfig() 中:

ini 复制代码
applyPlaybackConfig(config: PlayerCommonConfig): boolean {
  if (!this.isOpened()) return false;
  let ok = this.applyBaseConfig(config);
  // ...其他配置省略...

  // 硬解码 Surface 直通模式(mode=2)下无法做色彩处理,跳过
  const enableImageOptions = config.videoDecoderMode !== 2;
  if (enableImageOptions) {
    ok = this.enableVideoBrightnessOption(true) && ok;
    ok = this.enableVideoContrastOption(true) && ok;
    ok = this.enableVideoSaturationOption(true) && ok;
    ok = this.setVideoBrightness(config.imageBrightnessValue) && ok;
    ok = this.setVideoContrast(config.imageContrastValue) && ok;
    ok = this.setVideoSaturation(config.imageSaturationValue) && ok;
  }
  return ok;
}

这里有一个重要的约束:硬解码 + Surface 直通模式(videoDecoderMode = 2)不支持色彩调节。Surface 直通时视频帧直接写入 OHNativeWindow,绕过了软件色彩矩阵,亮度/对比度/饱和度的操作在这种模式下会被跳过。如果业务场景需要色彩调节,应选择软解码(mode=0)或普通硬解码(mode=1)。

HarmonyOS NEXT纯血鸿蒙RTSP|RTMP播放器

五、常见问题

Q:调节亮度后画面没有变化?

最常见的原因有两个:一是漏掉了 enableVideoBrightnessOption(true) 这一步,enable 和 set 是成对的,缺一不可;二是当前解码模式是 Surface 直通(mode=2),该模式下色彩调节不生效,需要切换到软解码或普通硬解码。

Q:滑条拖动后松手会弹回原位?

这是 ArkUI Slider 的已知行为:当父组件 @State 在同一帧更新时,Slider 的内部位置可能不跟随。解决方案就是文中的 AdjustSliderRow 子组件------将状态用 @Link 传入子组件,让 Slider 与状态源在同一组件树节点内,即可消除回弹。

六、完整调用时序

scss 复制代码
SmartPlayerOpen()           // 获取 handle
   ↓
applyPlaybackConfig()       // 初始化,含 enable + set 色彩默认值
   ↓
SmartPlayerStartPlayback()  // 开始播放
   ↓
── 播放过程中 ──────────────────────────────────
setVolume(val)              // 随时调节音量(0~100)
enableVideoBrightnessOption(true) → setVideoBrightness(val)  // 亮度
enableVideoContrastOption(true)   → setVideoContrast(val)    // 对比度
enableVideoSaturationOption(true) → setVideoSaturation(val)  // 饱和度
   ↓
SmartPlayerStopPlayback()   // 停止
SmartPlayerClose()          // 释放

小结

大牛直播 SDK 在鸿蒙 NEXT 上把音量和色彩调节的接口设计得相当完整。音量是无状态的单一调用,色彩三项则需要 enable + set 两步配合,这个细节在初次集成时容易忽略。此外,调节效果依赖解码模式,Surface 直通这条路子虽然延迟最低,但牺牲了所有后处理能力,实际项目中需要根据业务需要权衡取舍。

整体来看,借助 SmartPlayerWrapper 的 Session 缓存机制,调节值在 Surface 重建后可以自动恢复,UI 层只需要维护好 @State 变量和滑条的双向绑定,其余都由 SDK 内部处理,接入成本并不高。

相关推荐
音视频牛哥3 小时前
鸿蒙 NEXT RTSP/RTMP 播放器如何回调 RGB 数据并实现 AI 视觉算法分析
人工智能·算法·harmonyos·鸿蒙rtmp播放器·鸿蒙rtsp播放器·鸿蒙next rtsp播放器·鸿蒙next rtmp播放器
音视频牛哥4 小时前
HarmonyOS鸿蒙 Next 中如何实现低延迟 RTSP 流媒体播放?
华为·harmonyos·鸿蒙next·鸿蒙rtmp播放器·鸿蒙rtsp播放器·鸿蒙next rtsp播放器·鸿蒙next rtmp播放器
key_3_feng4 小时前
HarmonyOS 6.0 开发组件深度详解
华为·harmonyos
2601_949593655 小时前
小白入门ReactNative for OpenHarmony项目鸿蒙化三方库:react-native-fast-image
react native·react.js·harmonyos
Swift社区6 小时前
鸿蒙游戏的资源加载与管理
游戏·华为·harmonyos
前端不太难6 小时前
鸿蒙游戏如何避免“巨型页面文件”?
游戏·华为·harmonyos
千百元7 小时前
HBuilderX数据线运行mete80 (鸿蒙版本6.0.0)
华为·harmonyos
想你依然心痛7 小时前
HarmonyOS 5.0工业物联网开发实战:构建分布式智能制造监控与数字孪生预测维护系统
分布式·物联网·harmonyos·数字孪生
特立独行的猫a8 小时前
HarmonyOS鸿蒙三方库移植:选 vcpkg 还是 lycium_plusplus?两种“框架化”方案对比
harmonyos·openharmony·vcpkg·三方库移植·鸿蒙pc·lycium_plusplus