You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多
文档控制台
免费开始使用

Unity Android原生视频播放器异常:音频正常播放但视频画面黑屏

Unity Android原生视频播放器异常:音频正常播放但视频画面黑屏

我帮你梳理下可能的问题点和修复方案,结合你给出的代码来看,核心问题大概率出在Shader采样方式线程上下文不匹配这两个地方,咱们一步步来排查:

一、最直接的坑:Shader没有正确处理外部纹理

你提供的Shader代码只写了一半,但从已有的部分能看出,你用了samplerExternalOES却可能没使用对应的采样函数。GL_OES_EGL_image_external扩展要求必须用专门的采样函数来读取外部纹理,普通的texture2D是无法正确采样的。

修复后的完整Shader

替换你现有的AR_Video_shader.shader内容,确保顶点着色器传递正确UV,片元着色器用texture2DExternal采样:

Shader "Custom/VideoExternalOES" {
    Properties {
        _MainTex ("External Video Texture", 2D) = "white" {}
    }
    SubShader {
        Tags { "RenderType"="Opaque" "Queue"="Geometry" }
        Pass {
            GLSLPROGRAM
            #extension GL_OES_EGL_image_external : require
            
            #ifdef VERTEX
            varying vec2 uv;
            void main() {
                uv = gl_MultiTexCoord0.xy;
                gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
            }
            #endif
            
            #ifdef FRAGMENT
            uniform samplerExternalOES _MainTex;
            varying vec2 uv;
            void main() {
                gl_FragColor = texture2DExternal(_MainTex, uv);
            }
            #endif
            ENDGLSL
        }
    }
    FallBack "Diffuse"
}

同时要确认你的3D物体材质确实使用了这个Shader,而不是默认的Standard Shader(Standard Shader不支持外部纹理的特殊采样逻辑)。

二、致命的线程上下文问题:SurfaceTexture创建与更新不在同一线程

你的代码里有个很隐蔽的错误:

  • 你在Unity主线程调用Java的setInitVideoPlayerParams,导致SurfaceTexture是在Android主线程创建的
  • updateSurfaceTexture()是通过GL.IssuePluginEventUnity渲染线程调用的

SurfaceTexture和创建它的EGL上下文强绑定,两个线程的EGL上下文默认不共享,所以即使你调用了updateTexImage(),渲染线程也无法读取到主线程创建的SurfaceTexture里的帧数据,自然显示黑屏。

修复方案:统一在渲染线程初始化视频播放器

  1. 修改C++代码,新增渲染事件来触发Java端的初始化:
// 新增事件ID定义
#define kInitVideoPlayerEventId 5
// 新增全局变量存储视频路径
const char* g_VideoPath = nullptr;

extern "C" void UNITY_INTERFACE_API OnRenderEvent(int eventID) {
    // ... 原有事件处理逻辑 ...
    else if (eventID == kInitVideoPlayerEventId) {
        if (g_VideoTextureId != 0 && g_VideoPath != nullptr) {
            jclass pluginClass = env->FindClass("org/vibrissestudio/unityvideoplugin/UnityVideoPlugin");
            jmethodID initMethod = env->GetStaticMethodID(pluginClass, "initVideoPlayerStepTwoImpl", "(JLjava/lang/String;)V");
            jstring jVideoPath = env->NewStringUTF(g_VideoPath);
            env->CallStaticVoidMethod(pluginClass, initMethod, (jlong)g_VideoTextureId, jVideoPath);
            env->DeleteLocalRef(jVideoPath);
            env->DeleteLocalRef(pluginClass);
        }
    }
}

// 新增C接口用于传递视频路径
extern "C" void UNITY_INTERFACE_API setVideoPath(const char* path) {
    if (g_VideoPath != nullptr) {
        delete[] g_VideoPath;
    }
    g_VideoPath = new char[strlen(path) + 1];
    strcpy((char*)g_VideoPath, path);
}
  1. 修改C#代码,把初始化逻辑移到渲染线程执行:
public void InitializeVideo(string finalPath) {
    // 先把视频路径传给原生插件存储
    directSetVideoPath(finalPath);
    // 1. 在渲染线程创建外部纹理
    GL.IssuePluginEvent(GetRenderEventFunc(), kInitVideoTextureEventId);
    // 2. 在渲染线程初始化视频播放器(创建SurfaceTexture、MediaCodec等)
    GL.IssuePluginEvent(GetRenderEventFunc(), kInitVideoPlayerEventId);
    // 3. 等待一帧再创建Unity外部纹理
    StartCoroutine(CreateUnityExternalTextureAfterFrame());
}

private IEnumerator CreateUnityExternalTextureAfterFrame() {
    yield return null; // 等待渲染线程执行完初始化
    _longPtrValue = directGetNativeVideoTexturePtr();
    if (_longPtrValue != 0) {
        IntPtr nativeTexPtr = new IntPtr(_longPtrValue);
        // 注意:视频宽高要从原生插件同步过来,不要用硬编码值
        videoTexture = Texture2D.CreateExternalTexture(videoWidth, videoHeight, TextureFormat.RGBA32, false, false, nativeTexPtr);
        targetRenderer.material.mainTexture = videoTexture;
    }
}

// 新增C接口绑定
[DllImport("UnityVideoPlugin")]
private static extern void directSetVideoPath(string path);
  1. 移除不必要的Thread.Sleep:用协程等待一帧比Sleep更可靠,避免主线程阻塞。

三、其他需要确认的细节

  1. 视频宽高同步:确保videoWidthvideoHeight是从原生插件获取的真实视频分辨率,不要用硬编码值,否则CreateExternalTexture会创建尺寸不匹配的纹理。
  2. 材质赋值时机:一定要等videoTexture创建完成后再赋值给材质,避免材质绑定空纹理。
  3. OpenGL错误排查:可以在C++的渲染事件里加入glGetError()检查,确认每一步OpenGL操作都没有错误。

按照上面的步骤修复后,应该就能正常显示视频帧了。

内容来源于stack exchange

火山引擎 最新活动