HarmonyOS鸿蒙Next中硬件解码释放后,同一XComponent Surface使用OpenGL渲染画面不更新

HarmonyOS鸿蒙Next中硬件解码释放后,同一XComponent Surface使用OpenGL渲染画面不更新 问题描述

在OpenHarmony-5.0.3.135系统上,使用XComponent的Surface进行硬件解码直接渲染。播放结束后释放硬件解码器,然后使用OpenGL渲染同一Surface时,画面无法更新,一直停留在最后一帧。

场景说明

我们的业务场景是视频播放器在播放过程中,需要从硬件解码切换为软件解码。硬件解码使用Surface模式直接渲染到XComponent,切换时先释放硬件解码器,再使用OpenGL渲染同一Surface来显示软件解码后的画面。但切换后画面卡住不动,无法正常显示软解内容。

环境信息

  • 系统版本:OpenHarmony-5.0.3.135
  • 涉及组件:XComponent(surface类型)、OH_VideoDecoder(硬件解码)、OpenGL ES

复现步骤

  1. 使用OH_VideoDecoder_SetSurface()将硬件解码器绑定到XComponent的Surface,开始硬解渲染
  2. 播放结束后,调用OH_VideoDecoder_Stop()OH_VideoDecoder_Destroy()释放硬件解码器
  3. 使用OpenGL ES在同一XComponent Surface上渲染软件解码后的画面
  4. 实际结果:画面无变化,停留在硬解最后一帧

补充说明

  • 如果跳过硬解步骤,直接用OpenGL渲染该Surface,画面显示正常
  • 硬解播放期间画面显示正常
  • 已确认OpenGL渲染逻辑本身没有问题(在纯软解场景下验证过)
  • 已确认Surface未被销毁,XComponent仍处于活跃状态

尝试过的方案

  • 在释放硬解后调用OH_NativeWindow_NativeWindowHandle相关接口尝试重置Surface状态
  • 在OpenGL渲染前重新创建EGLContext和EGLSurface
  • 在硬解释放和OpenGL渲染之间加入延迟

以上方案均未能解决问题。

问题

  1. 硬件解码器释放后,XComponent的Surface是否仍持有之前解码器绑定的状态?是否需要主动清理?
  2. 多解码器共用同一NativeWindow时,是否有推荐的切换流程?
  3. OpenHarmony-5.0.3.135上是否有已知的Surface状态残留问题?
  4. 硬解切软解的场景下,是否有官方推荐的最佳实践?

期待回复

希望有经验的大佬指点,或者官方开发人员确认是否为系统已知问题。如有需要,我可以提供更多代码细节和日志信息。


更多关于HarmonyOS鸿蒙Next中硬件解码释放后,同一XComponent Surface使用OpenGL渲染画面不更新的实战教程也可以访问 https://www.itying.com/category-93-b0.html

10 回复

开发者你好,根据您当前提供的信息,无法确定是哪一部分的问题,是否可以提供可复现的demo和hilog日志。

更多关于HarmonyOS鸿蒙Next中硬件解码释放后,同一XComponent Surface使用OpenGL渲染画面不更新的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


开发者您好,分析硬件解码释放后渲染画面不更新的问题,建议逐项排查以下问题点:

  1. 确保硬件解码完全释放后再切换到OpenGL渲染;
  2. 检查Surface是否有效,可能需要重新创建或初始化;
  3. 注意EGL上下文的管理,避免上下文丢失;
  4. 渲染后调用swapBuffers等方法刷新显示;
  5. 如果还是不行建议提交工单,将问题代码附上,会有鸿蒙技术支持人员进行处理。

我遇到类似问题,最后的解决思路参考:

动态挂载XComponent,当上一个XComponent结束时卸载掉(会自动触发onSurfaceDestroyed), 然后在下一个render周期再动态挂载一个新的XComponent即可,新的XComponent不会受到之前的影响

@State surfaceActive: boolean = true;
@State surfaceVersion: number = 0;

// build() 内
Row() {
  if (this.surfaceActive) {
    XComponent({
      id: `componentId_${this.surfaceVersion}`,   // 唯一 id,确保重挂载时全新实例
      type: XComponentType.SURFACE,
      controller: this.mXComponentController
    })
      .width(`${this.videoWidth}px`)
      .height(`${this.videoHeight}px`)
      .rotate({ angle: this.videoRotationAngle })
      .onLoad(() => { AppStorage.setOrCreate('XComponent', this.mXComponentController); })
  }
}
handleSurfaceDestroyed(surfaceId: string) {
  if (!this.pendingSwitch) { return; }
  // onSurfaceDestroyed 在 render 周期内触发,直接改 @State 会报
  // "State variable changed during render" 错误,导致 surfaceActive=true 被丢弃、
  // XComponent 不重挂载(实测卡 loading)。必须 setTimeout 延迟到 render 周期之外。
  setTimeout(() => {
    this.surfaceId = '';
    this.surfaceVersion++;        // 递增版本号使重挂载的 XComponent id 唯一
    this.surfaceActive = true;    // 触发重挂载
  }, 0);
}

调用OH_VideoDecoder_Flush()刷新解码器。

调用OH_VideoDecoder_Flush接口后,解码器仍处于运行态,但会清除解码器中缓存的输入和输出数据及参数集(如H.264格式的PPS/SPS)。此时需要调用OH_VideoDecoder_Start接口重新开始解码。

// 通过codecMutex来避免调用Flush接口,状态切换后,解码线程还在跑会退出循环的问题。
std::unique_lock<std::shared_mutex> lock(codecMutex);
// 刷新解码器videoDec。
OH_AVErrCode ret = OH_VideoDecoder_Flush(videoDec);
if (ret != AV_ERR_OK) {
    // 异常处理。
}

// 重新开始解码。
ret = OH_VideoDecoder_Start(videoDec);
if (ret != AV_ERR_OK) {
    // 异常处理。
}

cke_414.png

看看这个官方demo:

https://gitcode.com/openharmony/applications_app_samples/tree/master/code/BasicFeature/Media/AVCodec

同一个 XComponent Surface 先给硬解码器用,再切到 OpenGL 渲染,关键是 Surface 的所有权和缓冲队列状态要彻底切干净。只 Stop/Flush 解码器不一定足够。

建议按这个顺序收敛:

  1. 停止硬解后先 Drain/Stop,再 Destroy 解码器输出 Surface 绑定关系,确认没有旧 producer 继续持有 buffer queue。
  2. 切到 GL 前重新 makeCurrent,重新设置 viewport,并在第一帧 glClear + swapBuffers,确认不是仍显示最后一帧缓存。
  3. 如果使用的是同一个 native window,尝试在切换时重建 EGLSurface;如果重建后正常,说明旧 EGLSurface/解码 Surface 的队列状态未释放干净。
  4. 在 hilog 里同时打解码器释放完成、EGLSurface 创建、首帧 swap 的时间点。

如果业务允许,硬解 Surface 和 GL Surface 分开管理,再用上层状态切换显示,会比复用同一个 Surface 稳。

找HarmonyOS工作还需要会Flutter的哦,有需要Flutter教程的可以学学大地老师的教程,很不错,B站免费学的哦:https://www.bilibili.com/video/BV1S4411E7LY/?p=17

停止并销毁硬解码器

OH_VideoDecoder_Stop(hardDec);
OH_VideoDecoder_Flush(hardDec);
OH_VideoDecoder_SetSurface(hardDec, nullptr);
OH_VideoDecoder_Destroy(hardDec);
hardDec = nullptr;
WaitCodecResourceRelease(); // 搞个短暂等待底层释放

虽然是OpenHarmony的,也可以参考下HarmonyOS。
文档:视频解码(有示例)
示例:Video-render

这个场景建议把硬解 Surface 模式和 OpenGL ES 渲染理解为两个 producer 在切换同一个 Surface/BufferQueue,而不是只看 decoder 对象是否已经 Destroy。

可以按下面顺序排查:

  1. 硬解结束时先停止送入数据并确保异步回调里不再持有 input/output buffer;如果还存在回调拿到的 buffer,下游继续访问会影响后续状态。需要清空缓存时可先 Flush,再 Stop/Destroy。若复用同一个 decoder 实例,则应走 Reset 后重新 Configure/SetSurface/Prepare,而不是在已 Prepare/Started 后重新绑定 Surface。

  2. 切到 GLES 前不要只重建 EGLContext/EGLSurface,建议同时重新从当前 XComponent 的 surfaceId 获取/创建 OHNativeWindow,再基于新的 window 创建 EGLSurface;旧的 EGLSurface、EGLContext 与 OHNativeWindow 都按生命周期释放。SetSurface 的声明要求在 Prepare 前绑定输出 Surface,并没有单独的“解绑 decoder 与 Surface”接口,所以重点是 producer 侧资源和 window 引用不要残留。

  3. GLES 接管后先做一次明确的 glViewport + glClear + eglSwapBuffers,确认第一帧已经真正入队。如果代码里曾经 request/attach buffer 但没有 flush/post,也要走 abort/detach/释放路径归还 buffer,避免队列被占住。

  4. 如果同一个 XComponent Surface 在上述流程下仍停留在硬解最后一帧,更稳的工程方案是切换时重建 XComponent/让 Surface 重新触发 onSurfaceCreated,或者硬解和软解各使用独立 Surface,在 ArkUI 层切换显示。这样可以避开同一个 Surface 在 codec 和 GLES producer 间切换后的残留状态。

另外,是否为 OpenHarmony-5.0.3.135 的系统侧已知问题需要官方结合最小 demo 和 hilog 确认;如果重建 OHNativeWindow/EGLSurface 甚至重建 XComponent 后仍可稳定复现,建议附版本、设备、最小复现工程和 codec/nativewindow 相关日志提单。

硬件解码释放后,XComponent Surface的EGL context可能已失效或未与Surface重新绑定,导致OpenGL渲染不更新。需确保解码器释放时未销毁Surface,且EGL context重新初始化并绑定到当前Surface。同时检查渲染线程同步和buffer队列状态。

问题核心在于硬件解码器释放后,Surface 的缓冲队列状态未完全重置。硬解通过 OH_VideoDecoder_SetSurface 绑定的是解码器内部创建的 buffer producer,释放解码器时该 producer 虽然销毁,但 Surface 对应的 consumer(如 XComponent 侧)可能仍处于“等待硬解帧”的同步状态,导致后续 OpenGL 提交的新帧被丢弃或不被刷新。

已知在 OpenHarmony 5.0.3.135 上,Surface 从解码模式切换为普通 EGL 渲染模式时,buffer 队列的 idle/busy 状态残留是常见问题。建议确认释放硬解后调用 OH_NativeWindow_CancelBuffer 或申请一次缓冲区来“唤醒”consumer;或将 EGLSurface 重建时显式使用 EGL_RENDER_BUFFER 配置。若仍无效,可尝试将 XComponent 的 surfaceId 重新获取一次(通过 getXComponentSurfaceId),再创建 EGLSurface,能绕过残留状态。

回到顶部