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.IssuePluginEvent在Unity渲染线程调用的
SurfaceTexture和创建它的EGL上下文强绑定,两个线程的EGL上下文默认不共享,所以即使你调用了updateTexImage(),渲染线程也无法读取到主线程创建的SurfaceTexture里的帧数据,自然显示黑屏。
修复方案:统一在渲染线程初始化视频播放器
- 修改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); }
- 修改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);
- 移除不必要的Thread.Sleep:用协程等待一帧比Sleep更可靠,避免主线程阻塞。
三、其他需要确认的细节
- 视频宽高同步:确保
videoWidth和videoHeight是从原生插件获取的真实视频分辨率,不要用硬编码值,否则CreateExternalTexture会创建尺寸不匹配的纹理。 - 材质赋值时机:一定要等
videoTexture创建完成后再赋值给材质,避免材质绑定空纹理。 - OpenGL错误排查:可以在C++的渲染事件里加入
glGetError()检查,确认每一步OpenGL操作都没有错误。
按照上面的步骤修复后,应该就能正常显示视频帧了。
内容来源于stack exchange




