HarmonyOS 鸿蒙Next中某些播放器和设备不支持HDR视频播放,怎么解决?

HarmonyOS 鸿蒙Next中某些播放器和设备不支持HDR视频播放,怎么解决? 如何将HDR视频转为SDR视频?

4 回复

👍

更多关于HarmonyOS 鸿蒙Next中某些播放器和设备不支持HDR视频播放,怎么解决?的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


参考文档

首先需要理解HDR和SDR的根本区别:

HDR

  • 高亮度:支持最高1000、4000、甚至10000尼特亮度。
  • 宽色域:通常使用Rec.2020色彩空间,能显示更丰富、更鲜艳的颜色。
  • 高比特深度:通常使用10-bit或12-bit,使得色彩过渡非常平滑,几乎看不到色阶断层。

SDR

  • 低亮度:标准亮度约为100尼特。
  • 窄色域:通常使用Rec.709色彩空间,色彩范围小。
  • 低比特深度:传统为8-bit,现在也有10-bit SDR,但是亮度范围有限。

将HDR Vivid视频转码成SDR视频是一个涉及多个技术要点的复杂过程。

在Native侧可以使用AVCodec原生能力转码,而在Arkts侧使用AVTranscoder将HDR视频转SDR。

可以通过调用canIUse接口来判断当前设备是否支持AVTranscoder,当canIUse(“SystemCapability.Multimedia.Media.AVTranscoder”) 的返回值为true时,表示可以使用转码能力。

效果预览:

cke_60891.gif

1、封装一个 Transcoder 类,方便直接调用转换:

/**
 * @fileName : Transcoder.ets
 * @author : @cxy
 * @date : 2025/12/20
 * @description : 视频转码
 */

import { media } from '@kit.MediaKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { fileIo } from '@kit.CoreFileKit';

export class Transcoder {
  processCallback?: (completed: boolean, progress: number, error?: BusinessError) => void
  private avTranscoder: media.AVTranscoder | undefined = undefined;
  private context: Context | undefined;
  private currentProgress: number = 0;
  private avConfig: media.AVTranscoderConfig = {
    audioBitrate: 100000, // 音频比特率。
    audioCodec: media.CodecMimeType.AUDIO_AAC, // 音频编码格式。
    fileFormat: media.ContainerFormatType.CFT_MPEG_4, // 封装格式。
    videoBitrate: 200000, // 视频比特率。
    videoCodec: media.CodecMimeType.VIDEO_AVC, // 视频编码格式。
  };

  constructor(context: Context | undefined) {
    if (context != undefined) {
      this.context = context;
    }
  }

  static canIUse() {
    return canIUse('SystemCapability.Multimedia.Media.AVTranscoder')
  }


  // 注册avTranscoder回调函数。
  setAVTranscoderCallback() {
    if (Transcoder.canIUse()) {
      if (this.avTranscoder != undefined) {
        // 转码完成回调函数。
        this.avTranscoder.on('complete', async () => {
          console.log(`AVTranscoder is completed`);
          this.processCallback?.(true, this.currentProgress)
          await this.releaseTranscoderingProcess();
        });
        // 错误上报回调函数。
        this.avTranscoder.on('error', (err: BusinessError) => {
          console.error(`AVTranscoder failed, code is ${err.code}, message is ${err.message}`);
          this.processCallback?.(false, this.currentProgress, err)
        });
        // 进度上报回调函数
        this.avTranscoder.on('progressUpdate', (progress: number) => {
          console.info(`AVTranscoder progressUpdate = ${progress}`);
          this.currentProgress = progress;
          this.processCallback?.(false, this.currentProgress)
        })
      }
    }
  }

  // 开始转码对应的流程。
  async startTranscoderingProcess(srcPath: string, dstPath: string) {
    if (Transcoder.canIUse()) {
      if (this.avTranscoder != undefined) {
        await this.avTranscoder.release();
        this.avTranscoder = undefined;
      }
      // 1.创建转码实例。
      this.avTranscoder = await media.createAVTranscoder();
      this.setAVTranscoderCallback();
      // 2.获取转码源文件fd和目标文件fd赋予avTranscoder;参考FilePicker文档。
      if (this.context != undefined) {
        try {
          let fileDescriptor = await fileIo.open(srcPath, fileIo.OpenMode.READ_ONLY);
          this.avTranscoder.fdSrc = fileDescriptor;

          let file = await fileIo.open(dstPath, fileIo.OpenMode.READ_WRITE | fileIo.OpenMode.CREATE);
          this.avTranscoder.fdDst = file.fd;
          this.currentProgress = 0;
        } catch (error) {
          console.error('error', error);
        }
      }
      // 3.配置转码参数完成准备工作。
      await this.avTranscoder.prepare(this.avConfig);
      // 4.开始转码。
      await this.avTranscoder.start();
    }
  }

  // 暂停转码对应的流程。
  async pauseTranscoderingProcess() {
    if (Transcoder.canIUse()) {
      if (this.avTranscoder != undefined) { // 仅在调用start返回后调用pause为合理调用。
        await this.avTranscoder.pause();
      }
    }
  }

  // 恢复对应的转码流程。
  async resumeTranscoderingProcess() {
    if (Transcoder.canIUse()) {
      if (this.avTranscoder != undefined) { // 仅在调用pause返回后调用resume为合理调用。
        await this.avTranscoder.resume();
      }
    }
  }

  // 释放转码流程。
  async releaseTranscoderingProcess() {
    if (Transcoder.canIUse()) {
      if (this.avTranscoder != undefined) {
        // 1.释放转码实例。
        await this.avTranscoder.release();
        this.avTranscoder = undefined;
        // 2.关闭转码目标文件fd。
        fileIo.closeSync(this.avTranscoder!.fdDst);
      }
    }
  }

  // 获取当前进度
  getCurrentProgress(): number {
    console.info(`getCurrentProgress = ${this.currentProgress}`);
    return this.currentProgress;
  }
}

2、使用示例,选择图库中的HDR视频,拷贝到沙箱后再进行转换。

/**
 * @fileName : TranscoderDemo.ets
 * @author : @cxy
 * @date : 2025/12/21
 * @description : 视频转码demo
 */
import { photoAccessHelper } from "@kit.MediaLibraryKit";
import { fileIo } from "@kit.CoreFileKit";
import { Transcoder } from "./Transcoder";

@Component
export struct TranscoderDemo {
  @State message: string = ''
  @State isTransing: boolean = false
  @State progress: number = 0

  build() {
    Column({ space: 30 }) {
      if (this.message) {
        Text(this.message)
          .fontSize(14)
      }

      Button(this.isTransing ? `正在转换:${this.progress}%` : '视频转码')
        .enabled(!this.isTransing)
        .onClick(() => {
          this.onPickerVideo()
        })
    }
    .width('100%')
    .height('100%')
    .alignItems(HorizontalAlign.Center)
    .justifyContent(FlexAlign.Center)
  }

  onPickerVideo() {
    const photoSelectOptions = new photoAccessHelper.PhotoSelectOptions();
    photoSelectOptions.MIMEType = photoAccessHelper.PhotoViewMIMETypes.VIDEO_TYPE;
    photoSelectOptions.maxSelectNumber = 1;

    const photoPicker = new photoAccessHelper.PhotoViewPicker();
    photoPicker.select(photoSelectOptions).then((photoSelectResult: photoAccessHelper.PhotoSelectResult) => {
      const uri = photoSelectResult.photoUris[0]
      this.copyToSandBox(uri)
    }).catch((err: BusinessError) => {
    });
  }

  async copyToSandBox(uri: string) {
    const context = this.getUIContext().getHostContext()
    if (!context) {
      return
    }
    const tempDir = context.filesDir + '/'
    const cacheFilePath = tempDir + 'test.mp4';
    const srcFile = fileIo.openSync(uri);
    const dstFile = fileIo.openSync(cacheFilePath, fileIo.OpenMode.READ_WRITE | fileIo.OpenMode.CREATE);
    fileIo.copyFileSync(srcFile.fd, dstFile.fd);

    const srcInfo = await fileIo.stat(srcFile.fd)

    const transPath = tempDir + 'trans.mp4'
    const path = await this.onTransCoder(cacheFilePath, transPath)
    if (!path) {
      console.error('转换失败')
      return
    }

    const trans = await fileIo.stat(path)

    const transInfo: Record<string, Object> = {
      '原大小': this.getSizeStr(srcInfo.size),
      '转码后大小': this.getSizeStr(trans.size),
      'dstPath': transPath
    }
    this.message = JSON.stringify(transInfo, null, 4)

    fileIo.close(srcFile)
    fileIo.close(dstFile)
  }

  async onTransCoder(srcPath: string, dstPath: string): Promise<string> {
    this.isTransing = true
    this.progress = 0
    return new Promise((resolve) => {
      const transcoder = new Transcoder(this.getUIContext().getHostContext())
      transcoder.startTranscoderingProcess(srcPath, dstPath)
      transcoder.processCallback = (completed: boolean, progress: number, error?: BusinessError) => {
        this.progress = progress
        if (completed) {
          this.isTransing = false
          resolve(dstPath)
        } else if (error) {
          this.isTransing = false
          resolve('')
        }
      }
    })
  }

  getSizeStr(size: number): string {
    if (size === 0) {
      return '0KB'
    } else if (size > 1024 * 1024 * 1024) {
      return Number(size / 1024.0 / 1024.0 / 1024.0).toFixed(1) + 'G'
    } else if (size > 1024 * 1024) {
      return Number(size / 1024.0 / 1024.0).toFixed(1) + 'M'
    } else {
      return Number(size / 1024.0).toFixed(1) + 'KB'
    }
  }
}

3、完整的demo

鸿蒙Next中HDR播放问题可通过以下方式解决:

  1. 确认设备硬件支持HDR显示(查看设备规格)
  2. 使用系统预装视频播放器(已做HDR适配)
  3. 更新播放器到最新版本(部分第三方播放器已适配)
  4. 检查视频源是否为标准HDR格式(HDR10/HLG/Dolby Vision)
  5. 确保系统版本为最新(修复已知兼容性问题)

若仍无法播放,可能是该设备/播放器暂未完成HDR功能适配。

针对您提出的在HarmonyOS Next中某些播放器或设备不支持HDR视频播放的问题,核心解决方案是进行色彩空间转换,即将HDR视频转换为标准动态范围(SDR)视频。以下是具体的技术实现路径和关键注意事项:

1. 核心方案:使用媒体处理能力进行转码 HarmonyOS Next提供了强大的媒体编解码媒体处理能力(@ohos.multimedia.media),这是实现HDR转SDR的官方推荐方案。您需要通过创建一个转码任务来完成。

关键步骤:

  • 创建转码实例:使用media.createMediaTranscoder()初始化。
  • 配置输入/输出:设置输入视频文件(HDR格式)和输出文件路径。在配置输出视频格式时,必须明确指定色彩标准为SDR(例如BT.709),并选择兼容性高的编码格式(如H.264)。
  • 执行转码:调用transcode()方法启动转换过程。系统编解码器会在后台自动完成从HDR(如HLG、HDR10)到SDR的色彩映射和色调映射。

2. 重要注意事项

  • 性能与质量平衡:转码是计算密集型操作,会消耗设备资源和时间。建议在系统空闲时(如连接充电器时)进行,或提供清晰的任务进度提示。
  • 色彩信息损失:HDR转SDR本质上是将宽色域、高亮度信息压缩到标准范围,画面动态范围和色彩鲜艳度会有所降低,这是技术原理决定的客观结果。
  • 格式兼容性:优先将输出视频封装为广泛支持的格式(如MP4),编码使用AVC/H.264,以确保在绝大多数设备和播放器上正常播放。

3. 备选方案:云端预处理 如果应用场景允许,可以在视频上传到服务器后,在云端进行HDR到SDR的转码处理。待用户播放时,根据其设备能力动态分发SDR或HDR版本的流媒体。这需要配套的云端服务支持。

总结: 解决HarmonyOS Next上HDR视频兼容性问题的直接有效方法,是利用系统media API进行本地转码,将视频转换为通用的SDR格式。开发者需在代码中显式配置输出色彩标准,并妥善管理转码任务的资源消耗与用户体验。

回到顶部