1. Unity自定义视频播放器架构分层
C# VideoPlayer (Unity层)
↓
C++ VideoPlugin (Native Plugin接口层)
↓
C++ VideoPlayer (播放器控制层)
↓
C++ AVPlayer (解码层,基于FFmpeg)
- C# VideoPlayer:Unity脚本层,负责与Unity生命周期、UI、事件系统集成。
- C++ VideoPlugin:Native Plugin接口,C#与C++桥接,暴露必要的C接口。
- C++ VideoPlayer:播放器控制,管理解码器、渲染、音频、事件分发等。
- C++ AVPlayer:底层解码,基于FFmpeg,负责音视频流解码、同步、帧缓存。
2. 主要调用流程梳理
2.1 初始化与准备
- C# VideoPlayer 调用
gvpCreate
创建播放器实例,注册事件回调。 - C++ VideoPlugin 创建
VideoPlayer
对象。 - C++ VideoPlayer 创建
AVPlayer
,设置渲染、音频、事件等回调。 - C# 通过
GL.IssuePluginEvent(EVENT_INIT)
触发C++层初始化渲染资源(如纹理)。 - C++ VideoPlayer::init()` 初始化纹理等渲染资源。
- C# 调用
gvpPrepare(id, url, decodeType)
传入视频URL和解码类型。 - C++ AVPlayer 设置解码类型、数据源,准备解码。
- C++ AVPlayer 打开流(
stream_open
),准备解码线程。 - C++ AVPlayer 通过事件回调通知C#层“准备就绪”或“加载失败”。
- C++ VideoPlayer 生成纹理资源,等待渲染线程提交。
2.2 播放与渲染
- C# 调用
gvpPlay(id)
开始播放。 - C++ VideoPlayer 调用
AVPlayer::play()
。 - C++ AVPlayer 启动解码循环,持续解码音视频帧。
- C++ VideoPlayer 更新最新帧缓存。
- C# VideoPlayer 在
Update()
中通过GL.IssuePluginEvent(EVENT_RENDER)
触发C++层渲染。 - C++ VideoPlayer::render()` 提交最新帧到纹理,供Unity渲染。
- C++ VideoPlayer 将帧数据commit到Unity纹理。
- 循环:只要流未结束,持续解码、渲染。
2.3 结束与清理
- C++ AVPlayer 检测到流结束,通知C#层“播放完成”。
- C# 或C++层调用
stop()
、destroy()
,停止解码、释放资源。 - C# 通过
GL.IssuePluginEvent(EVENT_CLEAR)
触发C++层清理。 - C++ VideoPlayer::clear()` 释放纹理、解码器等资源。
- 异常处理:如遇解码错误,及时通知C#层,走清理流程。
3. 工程实现要点
- 事件回调机制:C++层通过回调(如
onNativeEvent
)通知C#层状态变更(准备就绪、播放完成、出错等)。 - 渲染线程同步:纹理更新必须在Unity渲染线程,通过
GL.IssuePluginEvent
实现。 - 帧缓存机制:C++层缓存最新解码帧,C#层拉取并提交到Unity纹理,保证音画同步和流畅播放。
- 多线程解码:解码、渲染、音频播放分线程处理,提升性能。
- 资源管理:播放结束或出错时,及时释放解码器、纹理、音频等资源,防止内存泄漏。
4. 常见问题与优化建议
- 音视频同步:用PTS(Presentation Timestamp)精确同步音视频,避免音画不同步。
- 丢帧与卡顿:帧缓存区过小可能丢帧,过大则延迟高,需动态调整。
- 纹理复用:避免频繁创建/销毁纹理,提升性能。
- 平台兼容性:不同平台的Native Texture接口、音频API需分别适配。
- 异常容错:流中断、解码失败等异常要有健壮的处理和回调。
5. 总结
Unity自定义视频播放器通过C#-C++多层架构,结合FFmpeg解码和平台渲染/音频API,实现了高性能、强兼容的视频播放。核心流程包括初始化、解码、渲染、事件回调和资源管理。工程上需关注多线程同步、帧缓存、异常处理和平台适配。
下面我将继续深入,从以下几个方面详细展开:
- 各层接口设计与数据流动细节
- Native Texture(原生纹理)与Unity渲染集成要点
- 音视频同步与帧缓存机制实现建议
- 事件回调与异常处理机制
- 典型伪代码流程与示意图
- 常见问题与优化建议补充
1. 各层接口设计与数据流动细节
1.1 C# VideoPlayer(Unity层)
- 负责生命周期管理(Create/Prepare/Play/Stop/Destroy)
- 负责UI、用户交互、事件响应
- 通过
[DllImport]
调用C++接口(如gvpCreate
、gvpPrepare
、gvpPlay
等) - 通过
GL.IssuePluginEvent
与C++渲染线程同步
1.2 C++ VideoPlugin(Native Plugin接口层)
- 提供C接口,供C#调用
- 负责对象管理(创建/销毁播放器实例)
- 负责事件回调注册与分发
- 负责与Unity渲染线程的交互(如纹理提交)
1.3 C++ VideoPlayer(播放器控制层)
- 管理AVPlayer实例
- 管理纹理、音频设备、事件分发
- 负责帧缓存、同步、渲染、音频播放等
1.4 C++ AVPlayer(解码层)
- 基于FFmpeg,负责音视频流解码
- 解码后将YUV帧/PCM数据推送到VideoPlayer
- 负责音视频同步、解码线程管理
2. Native Texture与Unity渲染集成要点
- 纹理创建:C++层创建OpenGL/Metal/Direct3D纹理对象,返回给Unity
- 纹理共享:Unity通过
Texture2D.CreateExternalTexture
或NativeTexturePtr
与C++层共享纹理 - 帧上传:每次解码出新帧,C++层将YUV转RGB后上传到纹理
- 渲染同步:通过
GL.IssuePluginEvent
确保纹理更新在Unity渲染线程执行,避免多线程冲突 - 性能优化:纹理对象尽量复用,避免频繁创建/销毁
3. 音视频同步与帧缓存机制实现建议
- 帧缓存队列:C++层维护音频帧队列、视频帧队列
- PTS同步:每帧带有PTS(显示时间戳),渲染时根据当前播放时间选择合适帧
- 音频驱动:以音频为主时钟,视频帧根据音频播放进度同步显示
- 丢帧策略:当解码速度跟不上播放速度时,丢弃过期视频帧,保证音画同步
- 缓存大小:根据设备性能和网络状况动态调整,平衡延迟与流畅度
4. 事件回调与异常处理机制
- 事件类型:READY、COMPLETED、ERROR、BUFFERING等
- 回调注册:C#层注册回调,C++层通过函数指针或消息队列回调C#
- 线程安全:事件回调需在主线程或通过Unity的
SynchronizationContext
派发到主线程 - 异常处理:如解码失败、流中断、资源不足等,及时回调ERROR事件,触发清理流程
5. 典型伪代码流程与示意图
5.1 伪代码流程
// C#层
void Start() {
gvpCreate(playerType, OnNativeEvent);
GL.IssuePluginEvent(EVENT_INIT);
gvpPrepare(id, url, decodeType);
}
void OnNativeEvent(EventType type) {
if (type == READY) {
gvpPlay(id);
} else if (type == COMPLETED || type == ERROR) {
gvpStop(id);
gvpDestroy(id);
}
}
void Update() {
GL.IssuePluginEvent(EVENT_RENDER);
}
// C++层
void VideoPlayer::init() {
createNativeTexture();
}
void VideoPlayer::prepare(const char* url) {
avPlayer->setDataSource(url);
avPlayer->prepare();
}
void AVPlayer::decodeLoop() {
while (!eof) {
decodeFrame();
pushFrameToQueue();
}
notifyEvent(COMPLETED);
}
void VideoPlayer::render() {
Frame* frame = getLatestFrame();
uploadToTexture(frame);
}
5.2 流程示意图
C# VideoPlayer
|
| gvpCreate/Prepare/Play/Stop/Destroy
v
C++ VideoPlugin
|
| 创建/管理播放器对象
v
C++ VideoPlayer
|
| 控制解码、渲染、音频、事件
v
C++ AVPlayer (FFmpeg)
|
| 解码音视频帧,推送到VideoPlayer
v
帧缓存队列 <----> 纹理/音频设备
|
| 事件回调
v
C# VideoPlayer
6. 常见问题与优化建议补充
- 多平台适配:不同平台的纹理/音频API差异大,需分别实现并在C++层做平台分发
- 内存管理:帧缓存、纹理、音频缓冲区及时释放,防止内存泄漏
- 解码性能:可用硬解码(如MediaCodec、VideoToolbox)提升性能,FFmpeg软解码做兜底
- 音频播放:如需与Unity音频系统混音,需将PCM数据送入Unity自定义AudioClip
- 线程模型:解码、渲染、音频播放分线程,主线程只负责事件和UI
- 调试工具:建议集成帧率、缓冲区状态、同步延迟等调试信息,便于性能分析
7. 总结
Unity自定义视频播放器的核心在于C#与C++多层协作,FFmpeg高效解码,Native Texture高效渲染,音视频同步与事件回调机制健全。工程实现需关注多线程同步、平台适配、性能优化和异常容错。
下面我将继续深入,补充更具体的实现细节和工程建议,尤其关注你可能关心的接口设计、Native Texture集成、音视频同步、音频输出、异常处理等方面。
1. C# 与 C++ 交互接口设计建议
1.1 C# 侧接口(伪代码)
// 1. 创建播放器
[DllImport("GalaxyVideoPlugin")]
public static extern int gvpCreate(int playerType, NativeEventCallback callback);
// 2. 初始化渲染资源
[DllImport("GalaxyVideoPlugin")]
public static extern void gvpInit(int id);
// 3. 准备视频
[DllImport("GalaxyVideoPlugin")]
public static extern void gvpPrepare(int id, string url, int decodeType);
// 4. 播放/暂停/停止
[DllImport("GalaxyVideoPlugin")]
public static extern void gvpPlay(int id);
[DllImport("GalaxyVideoPlugin")]
public static extern void gvpPause(int id);
[DllImport("GalaxyVideoPlugin")]
public static extern void gvpStop(int id);
// 5. 清理资源
[DllImport("GalaxyVideoPlugin")]
public static extern void gvpDestroy(int id);
// 6. 渲染帧同步
[DllImport("GalaxyVideoPlugin")]
public static extern void gvpRender(int id);
// 7. 事件回调
public delegate void NativeEventCallback(int id, int eventType, int param1, int param2);
1.2 C++ 侧接口(伪代码)
// C接口暴露给C#
extern "C" {
int gvpCreate(int playerType, NativeEventCallback callback);
void gvpInit(int id);
void gvpPrepare(int id, const char* url, int decodeType);
void gvpPlay(int id);
void gvpPause(int id);
void gvpStop(int id);
void gvpDestroy(int id);
void gvpRender(int id);
}
2. Native Texture 与 Unity 渲染集成
2.1 纹理创建与共享
- C++层创建原生纹理(OpenGL/Metal/Direct3D),保存其句柄。
- C#层通过
Texture2D.CreateExternalTexture
或Texture2D.UpdateExternalTexture
将原生纹理包装为Unity可用的Texture2D。 - 纹理句柄通过接口传递给C#,如
IntPtr GetNativeTexturePtr(int id)
。
2.2 纹理更新
- 每次解码出新帧,C++层将YUV转为RGB(可用GPU shader),上传到原生纹理。
- C#层在
Update()
或OnPostRender()
中调用GL.IssuePluginEvent
,确保纹理更新在Unity渲染线程执行。
2.3 伪代码示例
// C#侧
IntPtr nativeTex = GetNativeTexturePtr(id);
Texture2D unityTex = Texture2D.CreateExternalTexture(width, height, TextureFormat.RGBA32, false, false, nativeTex);
3. 音视频同步与帧缓存机制
3.1 帧缓存队列
- C++层维护视频帧队列(如std::deque<Frame*>),每帧带有PTS。
- 音频帧队列同理。
3.2 同步策略
- 以音频为主时钟(推荐),视频帧PTS与音频播放进度对齐。
- 若视频帧PTS落后于音频,丢弃过期帧。
- 若视频帧PTS超前,等待音频进度。
3.3 伪代码
// 解码线程
while (running) {
Frame* frame = decodeNextFrame();
videoQueue.push(frame);
}
// 渲染线程
double audioClock = getAudioClock();
while (!videoQueue.empty()) {
Frame* frame = videoQueue.front();
if (frame->pts <= audioClock) {
renderFrame(frame);
videoQueue.pop();
} else {
break;
}
}
4. 音频输出与Unity集成
4.1 直接Native播放
- C++层用Oboe(Android)、AudioQueue(iOS)、CoreAudio(Mac)、WASAPI(Win)等API播放PCM数据,延迟低,性能高。
4.2 Unity音频系统集成
- C++层将PCM数据通过回调送到C#,C#用
AudioClip.SetData
或OnAudioFilterRead
播放。 - 适合需要与Unity音效混音的场景,但延迟略高。
5. 事件回调与异常处理
5.1 事件类型
- READY:准备就绪
- COMPLETED:播放完成
- ERROR:解码/播放出错
- BUFFERING:缓冲中
- SEEKED:跳转完成
5.2 回调机制
- C++层通过注册的回调函数通知C#层。
- 注意线程安全,必要时用消息队列派发到Unity主线程。
5.3 异常处理建议
- 解码失败、流中断、资源不足等,及时回调ERROR事件。
- C#层收到ERROR后,自动走清理流程,防止资源泄漏。
6. 典型流程图(简化版)
sequenceDiagram
participant C# as C# VideoPlayer
participant C++ as C++ VideoPlugin
participant VP as C++ VideoPlayer
participant AV as C++ AVPlayer
C#->>C++: gvpCreate
C++->>VP: new VideoPlayer
VP->>AV: new AVPlayer
C#->>C++: gvpInit
C++->>VP: init()
C#->>C++: gvpPrepare(url)
C++->>VP: prepare(url)
VP->>AV: setDataSource, prepare
AV->>AV: stream_open
AV-->>C#: onNativeEvent(READY)
C#->>C++: gvpPlay
C++->>VP: play()
VP->>AV: play()
AV->>VP: decodeLoop (push frames)
C#->>C++: gvpRender (每帧)
C++->>VP: render()
VP->>C#: commitTextures
AV-->>C#: onNativeEvent(COMPLETED/ERROR)
C#->>C++: gvpStop/gvpDestroy
C++->>VP: stop/destroy
7. 性能与稳定性优化补充
- 帧队列长度:动态调整,防止内存暴涨或丢帧。
- YUV转RGB:用GPU shader加速,CPU只做数据搬运。
- 多线程:解码、渲染、音频播放分离,互不阻塞。
- 资源复用:纹理、帧缓存等尽量复用,减少GC压力。
- 平台适配:不同平台的纹理/音频API需分别实现,接口统一。
8. 总结
Unity自定义视频播放器的高效实现,关键在于C#与C++层清晰分工、接口简洁、Native Texture高效渲染、音视频同步精准、事件回调健壮、异常处理完善。工程上需关注多线程、平台适配、性能与内存优化。