1047 lines
34 KiB
C#
1047 lines
34 KiB
C#
/*----------------------------------------------------------------
|
||
// Copyright (C) 2025 Beijing All rights reserved.
|
||
//
|
||
// Author: huachangmiao
|
||
// Create Date: 2025/10/29
|
||
// Module Describe: Unity微信小程序Worker版本解码器
|
||
//----------------------------------------------------------------*/
|
||
// #define ENABLE_LOG //启用日志
|
||
|
||
using System;
|
||
using System.Runtime.InteropServices;
|
||
using Unity.Collections;
|
||
using Unity.Collections.LowLevel.Unsafe;
|
||
using UnityEngine;
|
||
using LeviathanVideo.Abstractions;
|
||
|
||
|
||
//******************************************************************
|
||
// 微信小游戏Worker解码器
|
||
//******************************************************************
|
||
|
||
internal unsafe class LeviathanWorkerDecoder : ILeviathanDecoder
|
||
{
|
||
// 日志辅助方法
|
||
private static void Log(string message)
|
||
{
|
||
LeviathanLogConfig.Log("WorkerDecoder", message);
|
||
}
|
||
|
||
private static void LogWarning(string message)
|
||
{
|
||
LeviathanLogConfig.LogWarning("WorkerDecoder", message);
|
||
}
|
||
|
||
private static void LogError(string message)
|
||
{
|
||
LeviathanLogConfig.LogError("WorkerDecoder", message);
|
||
}
|
||
|
||
// 事件实现
|
||
public event Action OnFirstFrameDecoded;
|
||
#if UNITY_WEBGL
|
||
// JavaScript接口声明 - 对应jslib中的方法
|
||
[DllImport("__Internal")]
|
||
private static extern void InitializeLeviathanWorkerSystem();
|
||
|
||
[DllImport("__Internal")]
|
||
private static extern int CreateWorkerVideoInstance();
|
||
|
||
[DllImport("__Internal")]
|
||
private static extern bool SetWorkerVideoData(int instanceId, byte[] data, int dataSize);
|
||
|
||
[DllImport("__Internal")]
|
||
private static extern bool PrepareWorkerVideo(int instanceId, bool fastDecode);
|
||
|
||
[DllImport("__Internal")]
|
||
private static extern bool PlayWorkerVideo(int instanceId);
|
||
|
||
[DllImport("__Internal")]
|
||
private static extern bool PauseWorkerVideo(int instanceId);
|
||
|
||
[DllImport("__Internal")]
|
||
private static extern bool ResumeWorkerVideo(int instanceId);
|
||
|
||
[DllImport("__Internal")]
|
||
private static extern bool StopWorkerVideo(int instanceId);
|
||
|
||
[DllImport("__Internal")]
|
||
private static extern bool UpdateWorkerVideo(int instanceId, float deltaTime);
|
||
|
||
[DllImport("__Internal")]
|
||
private static extern bool DestroyWorkerVideo(int instanceId);
|
||
|
||
[DllImport("__Internal")]
|
||
private static extern void SetWorkerVideoStatusCallback(int instanceId, System.Action<int, int> callback);
|
||
|
||
[DllImport("__Internal")]
|
||
private static extern void SetWorkerVideoFirstFrameDisplayCallback(int instanceId, System.Action<int> callback);
|
||
|
||
// 改进的静态回调方法,支持实例ID参数
|
||
#if UNITY_WEBGL && !UNITY_EDITOR
|
||
[AOT.MonoPInvokeCallback(typeof(System.Action<int, int>))]
|
||
#endif
|
||
private static void StaticOnStatusChanged(int instanceId, int status)
|
||
{
|
||
// 直接通过instanceId查找对应的实例
|
||
if (_instances.ContainsKey(instanceId))
|
||
{
|
||
var instance = _instances[instanceId];
|
||
if (instance != null && instance._isInitialized && !instance._isDisposing)
|
||
{
|
||
instance.HandleStatusChanged(status);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
LogWarning($"状态回调: 未找到实例ID {instanceId}");
|
||
}
|
||
}
|
||
|
||
#if UNITY_WEBGL && !UNITY_EDITOR
|
||
[AOT.MonoPInvokeCallback(typeof(System.Action<int>))]
|
||
#endif
|
||
private static void StaticOnFirstFrameDisplay(int instanceId)
|
||
{
|
||
// 直接通过instanceId查找对应的实例
|
||
if (_instances.ContainsKey(instanceId))
|
||
{
|
||
var instance = _instances[instanceId];
|
||
if (instance != null && instance._isInitialized && !instance._isDisposing)
|
||
{
|
||
instance.HandleFirstFrameDisplay();
|
||
}
|
||
}
|
||
else
|
||
{
|
||
LogWarning($"首帧回调: 未找到实例ID {instanceId}");
|
||
}
|
||
}
|
||
|
||
[DllImport("__Internal")]
|
||
private static extern int GetWorkerVideoFrameCount(int instanceId);
|
||
|
||
[DllImport("__Internal")]
|
||
private static extern long GetWorkerVideoDuration(int instanceId);
|
||
|
||
[DllImport("__Internal")]
|
||
private static extern int GetWorkerVideoWidth(int instanceId);
|
||
|
||
[DllImport("__Internal")]
|
||
private static extern int GetWorkerVideoHeight(int instanceId);
|
||
|
||
[DllImport("__Internal")]
|
||
private static extern int GetWorkerVideoYTextureWidth(int instanceId);
|
||
|
||
[DllImport("__Internal")]
|
||
private static extern int GetWorkerVideoYTextureHeight(int instanceId);
|
||
|
||
[DllImport("__Internal")]
|
||
private static extern int GetWorkerVideoUVTextureWidth(int instanceId);
|
||
|
||
[DllImport("__Internal")]
|
||
private static extern int GetWorkerVideoUVTextureHeight(int instanceId);
|
||
|
||
[DllImport("__Internal")]
|
||
private static extern void SetWorkerVideoTextureId(int instanceId, string type, int textureId);
|
||
|
||
[DllImport("__Internal")]
|
||
private static extern int GetWorkerVideoCurrentFrameIndex(int instanceId);
|
||
|
||
[DllImport("__Internal")]
|
||
private static extern bool SeekWorkerVideo(int instanceId, float timeMs);
|
||
|
||
[DllImport("__Internal")]
|
||
private static extern bool SeekWorkerVideoToFrame(int instanceId, int frameIndex);
|
||
|
||
[DllImport("__Internal")]
|
||
private static extern bool SetWorkerVideoPlaySpeed(int instanceId, float speed);
|
||
|
||
[DllImport("__Internal")]
|
||
private static extern bool SetWorkerVideoLooping(int instanceId, bool isLooping);
|
||
|
||
[DllImport("__Internal")]
|
||
private static extern bool SetWorkerVideoAlphaType(int instanceId, int alphaType);
|
||
|
||
// 纹理上传相关接口
|
||
[DllImport("__Internal")]
|
||
private static extern System.IntPtr GetWorkerVideoSharedYBuffer(int instanceId);
|
||
|
||
[DllImport("__Internal")]
|
||
private static extern System.IntPtr GetWorkerVideoSharedUBuffer(int instanceId);
|
||
|
||
[DllImport("__Internal")]
|
||
private static extern System.IntPtr GetWorkerVideoSharedVBuffer(int instanceId);
|
||
|
||
[DllImport("__Internal")]
|
||
private static extern int GetWorkerVideoSharedBufferSize(int instanceId, string type);
|
||
|
||
[DllImport("__Internal")]
|
||
private static extern bool NotifyWorkerVideoTextureReady(int instanceId);
|
||
|
||
// 获取WASM内存指针接口(已废弃,保留用于向后兼容)
|
||
[DllImport("__Internal")]
|
||
private static extern System.IntPtr GetWorkerVideoWasmYBufferPtr(int instanceId);
|
||
|
||
[DllImport("__Internal")]
|
||
private static extern System.IntPtr GetWorkerVideoWasmUBufferPtr(int instanceId);
|
||
|
||
[DllImport("__Internal")]
|
||
private static extern System.IntPtr GetWorkerVideoWasmVBufferPtr(int instanceId);
|
||
|
||
// 检查是否使用JS端GPU纹理上传(所有平台都使用)
|
||
[DllImport("__Internal")]
|
||
private static extern bool IsWorkerVideoUsingJSTextureUpload(int instanceId);
|
||
#endif
|
||
|
||
// 私有字段
|
||
private long _frameCount;
|
||
private double _frameInterval;
|
||
private string _codecName = "worker_decoder";
|
||
private long _duration;
|
||
private long _decodedFrameIndex;
|
||
private AVFrame* _videoFrame;
|
||
private bool _isLooping = false;
|
||
private bool _fastDecode = true; // 快速解码开关
|
||
|
||
#if UNITY_WEBGL
|
||
// 销毁标志(防止销毁期间的回调继续执行)
|
||
private bool _isDisposing = false;
|
||
|
||
private int _instanceId = -1;
|
||
private bool _isInitialized = false;
|
||
private NativeArray<byte> _videoData;
|
||
private bool _isWorkerSystemReady = false;
|
||
|
||
// 实例管理
|
||
private static System.Collections.Generic.Dictionary<int, LeviathanWorkerDecoder> _instances =
|
||
new System.Collections.Generic.Dictionary<int, LeviathanWorkerDecoder>();
|
||
|
||
// 帧数据缓存
|
||
private int _currentFrameWidth = 0;
|
||
private int _currentFrameHeight = 0;
|
||
private System.IntPtr _currentFrameData = System.IntPtr.Zero;
|
||
|
||
// 内存共享缓冲区管理
|
||
private System.IntPtr _sharedYBuffer = System.IntPtr.Zero;
|
||
private System.IntPtr _sharedUBuffer = System.IntPtr.Zero;
|
||
private System.IntPtr _sharedVBuffer = System.IntPtr.Zero;
|
||
private int _sharedYBufferSize = 0;
|
||
private int _sharedUBufferSize = 0;
|
||
private int _sharedVBufferSize = 0;
|
||
private bool _sharedBuffersInitialized = false;
|
||
private UnityEngine.Texture2D[] _textureReferences = null;
|
||
|
||
// 帧上传追踪(避免重复上传同一帧)
|
||
private long _lastUploadedFrameIndex = -1;
|
||
|
||
// 是否使用JS端GPU纹理上传(所有平台都使用)
|
||
private bool _useJSTextureUpload = false;
|
||
|
||
// 视频状态枚举
|
||
private enum VideoStatus
|
||
{
|
||
Initialized = 0,
|
||
Prepared = 1,
|
||
Playing = 2,
|
||
Paused = 3,
|
||
Stopped = 4,
|
||
Error = 5
|
||
}
|
||
|
||
// 当前视频状态
|
||
private VideoStatus _currentStatus = VideoStatus.Initialized;
|
||
|
||
// 静态初始化标志
|
||
private static bool _systemInitialized = false;
|
||
#endif
|
||
|
||
// 实现接口属性
|
||
public long FrameCount => _frameCount;
|
||
public double FrameInterval => _frameInterval;
|
||
public string CodecName => _codecName;
|
||
public long Duration => _duration;
|
||
public long DecodedFrameIndex => _decodedFrameIndex;
|
||
public AVFrame* VideoFrame => _videoFrame;
|
||
|
||
// 循环播放属性
|
||
public bool IsLooping
|
||
{
|
||
get => _isLooping;
|
||
set
|
||
{
|
||
_isLooping = value;
|
||
#if UNITY_WEBGL
|
||
if (_instanceId >= 0 && _isWorkerSystemReady && _isInitialized)
|
||
{
|
||
SetWorkerVideoLooping(_instanceId, _isLooping);
|
||
}
|
||
else
|
||
{
|
||
Log($"循环播放设置延迟到初始化完成后: {_isLooping}");
|
||
}
|
||
#endif
|
||
}
|
||
}
|
||
|
||
public LeviathanWorkerDecoder()
|
||
{
|
||
// 分配AVFrame结构
|
||
_videoFrame = (AVFrame*)Marshal.AllocHGlobal(sizeof(AVFrame));
|
||
*_videoFrame = new AVFrame();
|
||
|
||
// 初始化Worker系统
|
||
InitializeWorkerSystem();
|
||
}
|
||
|
||
private void InitializeWorkerSystem()
|
||
{
|
||
#if UNITY_WEBGL
|
||
try
|
||
{
|
||
// 确保系统只初始化一次
|
||
if (!_systemInitialized)
|
||
{
|
||
Log("初始化Worker系统...");
|
||
InitializeLeviathanWorkerSystem();
|
||
#if ENABLE_LOG
|
||
LeviathanLogConfig.EnableAll();
|
||
#endif
|
||
_systemInitialized = true;
|
||
_isWorkerSystemReady = true;
|
||
Log("Worker系统初始化完成");
|
||
}
|
||
else
|
||
{
|
||
_isWorkerSystemReady = true;
|
||
Log("Worker系统已经初始化");
|
||
}
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
LogError($"Worker系统初始化异常: {e.Message}");
|
||
_isWorkerSystemReady = false;
|
||
}
|
||
#endif
|
||
}
|
||
|
||
#if UNITY_WEBGL
|
||
// 状态回调处理(实例方法)
|
||
private void HandleStatusChanged(int status)
|
||
{
|
||
// 如果正在销毁,忽略所有状态变更
|
||
if (_isDisposing)
|
||
{
|
||
return;
|
||
}
|
||
|
||
_currentStatus = (VideoStatus)status;
|
||
Log($"实例{_instanceId}状态变更: {_currentStatus}");
|
||
|
||
// 根据状态更新内部数据
|
||
switch (_currentStatus)
|
||
{
|
||
case VideoStatus.Prepared:
|
||
// 视频准备完成,更新视频信息
|
||
UpdateVideoInfo();
|
||
break;
|
||
case VideoStatus.Error:
|
||
LogError($"实例{_instanceId}视频播放出错");
|
||
break;
|
||
}
|
||
}
|
||
|
||
private void HandleFirstFrameDisplay()
|
||
{
|
||
// 如果正在销毁,忽略首帧回调
|
||
if (_isDisposing)
|
||
{
|
||
return;
|
||
}
|
||
|
||
Log($"实例{_instanceId}首帧显示");
|
||
// 触发第一帧回调
|
||
OnFirstFrameDecoded?.Invoke();
|
||
}
|
||
#endif
|
||
|
||
private void UpdateVideoInfo()
|
||
{
|
||
#if UNITY_WEBGL
|
||
if (_instanceId >= 0)
|
||
{
|
||
_frameCount = GetWorkerVideoFrameCount(_instanceId);
|
||
_duration = GetWorkerVideoDuration(_instanceId);
|
||
_currentFrameWidth = GetWorkerVideoWidth(_instanceId);
|
||
_currentFrameHeight = GetWorkerVideoHeight(_instanceId);
|
||
|
||
// 获取纹理尺寸信息
|
||
int yTextureWidth = GetWorkerVideoYTextureWidth(_instanceId);
|
||
int yTextureHeight = GetWorkerVideoYTextureHeight(_instanceId);
|
||
int uvTextureWidth = GetWorkerVideoUVTextureWidth(_instanceId);
|
||
int uvTextureHeight = GetWorkerVideoUVTextureHeight(_instanceId);
|
||
|
||
// 更新AVFrame信息
|
||
if (_videoFrame != null)
|
||
{
|
||
_videoFrame->width = _currentFrameWidth;
|
||
_videoFrame->height = _currentFrameHeight;
|
||
|
||
// 设置linesize信息 - 这是关键修复
|
||
_videoFrame->linesize[0] = yTextureWidth; // Y平面的linesize
|
||
_videoFrame->linesize[1] = uvTextureWidth; // U平面的linesize
|
||
_videoFrame->linesize[2] = uvTextureWidth; // V平面的linesize
|
||
|
||
Log($"更新视频信息: {_currentFrameWidth}x{_currentFrameHeight}, Y纹理: {yTextureWidth}x{yTextureHeight}, UV纹理: {uvTextureWidth}x{uvTextureHeight}");
|
||
}
|
||
|
||
// 计算帧间隔
|
||
if (_frameCount > 0 && _duration > 0)
|
||
{
|
||
_frameInterval = (double)_duration / _frameCount / 1000000.0; // 转换为秒
|
||
}
|
||
|
||
// 初始化共享缓冲区
|
||
InitializeSharedBuffers();
|
||
|
||
Log($"视频信息更新: {_currentFrameWidth}x{_currentFrameHeight}, 帧数: {_frameCount}, 时长: {_duration}");
|
||
}
|
||
#endif
|
||
}
|
||
|
||
private void InitializeSharedBuffers()
|
||
{
|
||
#if UNITY_WEBGL
|
||
if (_instanceId < 0 || _sharedBuffersInitialized)
|
||
{
|
||
return;
|
||
}
|
||
|
||
try
|
||
{
|
||
// 检查是否使用JS端GPU纹理上传
|
||
_useJSTextureUpload = IsWorkerVideoUsingJSTextureUpload(_instanceId);
|
||
|
||
// 获取共享缓冲区指针和大小
|
||
_sharedYBuffer = GetWorkerVideoSharedYBuffer(_instanceId);
|
||
_sharedUBuffer = GetWorkerVideoSharedUBuffer(_instanceId);
|
||
_sharedVBuffer = GetWorkerVideoSharedVBuffer(_instanceId);
|
||
|
||
_sharedYBufferSize = GetWorkerVideoSharedBufferSize(_instanceId, "Y");
|
||
_sharedUBufferSize = GetWorkerVideoSharedBufferSize(_instanceId, "U");
|
||
_sharedVBufferSize = GetWorkerVideoSharedBufferSize(_instanceId, "V");
|
||
|
||
if (_sharedYBuffer != System.IntPtr.Zero && _sharedUBuffer != System.IntPtr.Zero && _sharedVBuffer != System.IntPtr.Zero)
|
||
{
|
||
_sharedBuffersInitialized = true;
|
||
Log($"共享缓冲区初始化成功: Y({_sharedYBufferSize}), U({_sharedUBufferSize}), V({_sharedVBufferSize})");
|
||
}
|
||
else
|
||
{
|
||
if (_useJSTextureUpload)
|
||
{
|
||
Log($"使用JS端GPU纹理上传,跳过C#端纹理处理");
|
||
}
|
||
else
|
||
{
|
||
LogWarning($"共享缓冲区不可用,使用传统纹理上传方式");
|
||
}
|
||
_sharedBuffersInitialized = true; // 设置为true以启用纹理上传逻辑
|
||
}
|
||
}
|
||
catch (System.Exception e)
|
||
{
|
||
LogError($"初始化共享缓冲区异常: {e.Message}");
|
||
}
|
||
#endif
|
||
}
|
||
|
||
public void SetTextureIds(UnityEngine.Texture2D[] textures)
|
||
{
|
||
#if UNITY_WEBGL
|
||
if (_instanceId < 0 || textures == null || textures.Length < 3)
|
||
{
|
||
LogError("SetTextureIds: 参数无效");
|
||
return;
|
||
}
|
||
|
||
try
|
||
{
|
||
// 获取纹理的Native指针作为ID
|
||
int yTextureId = (int)textures[0].GetNativeTexturePtr();
|
||
int uTextureId = (int)textures[1].GetNativeTexturePtr();
|
||
int vTextureId = (int)textures[2].GetNativeTexturePtr();
|
||
|
||
// 传递给JSLIB
|
||
SetWorkerVideoTextureId(_instanceId, "Y", yTextureId);
|
||
SetWorkerVideoTextureId(_instanceId, "U", uTextureId);
|
||
SetWorkerVideoTextureId(_instanceId, "V", vTextureId);
|
||
|
||
Log($"设置纹理ID成功: Y({yTextureId}), U({uTextureId}), V({vTextureId})");
|
||
|
||
// 如果共享缓冲区已初始化,开始纹理上传处理
|
||
if (_sharedBuffersInitialized)
|
||
{
|
||
StartTextureUploadProcessing(textures);
|
||
}
|
||
}
|
||
catch (System.Exception e)
|
||
{
|
||
LogError($"SetTextureIds异常: {e.Message}");
|
||
}
|
||
#endif
|
||
}
|
||
|
||
private void StartTextureUploadProcessing(UnityEngine.Texture2D[] textures)
|
||
{
|
||
#if UNITY_WEBGL
|
||
if (!_sharedBuffersInitialized || textures == null || textures.Length < 3)
|
||
{
|
||
return;
|
||
}
|
||
|
||
// 存储纹理引用,在DecodeNextFrame中处理纹理上传
|
||
_textureReferences = textures;
|
||
Log($"纹理上传处理已启动,纹理数量: {textures.Length}");
|
||
#endif
|
||
}
|
||
|
||
|
||
private unsafe void UploadTextureFromSharedBuffer(UnityEngine.Texture2D[] textures)
|
||
{
|
||
#if UNITY_WEBGL
|
||
if (!_sharedBuffersInitialized || textures == null || textures.Length < 3)
|
||
{
|
||
return;
|
||
}
|
||
|
||
// 使用JS端GPU纹理上传,C#端不需要处理
|
||
if (_useJSTextureUpload)
|
||
{
|
||
return;
|
||
}
|
||
|
||
try
|
||
{
|
||
// 获取纹理尺寸
|
||
int yWidth = GetWorkerVideoYTextureWidth(_instanceId);
|
||
int yHeight = GetWorkerVideoYTextureHeight(_instanceId);
|
||
int uvWidth = GetWorkerVideoUVTextureWidth(_instanceId);
|
||
int uvHeight = GetWorkerVideoUVTextureHeight(_instanceId);
|
||
|
||
int ySize = yWidth * yHeight;
|
||
int uvSize = uvWidth * uvHeight;
|
||
|
||
// 检查是否有共享缓冲区(已废弃,保留用于向后兼容)
|
||
if (_sharedYBuffer != System.IntPtr.Zero && _sharedUBuffer != System.IntPtr.Zero && _sharedVBuffer != System.IntPtr.Zero)
|
||
{
|
||
if (_sharedYBufferSize > 0)
|
||
{
|
||
NativeArray<byte> yData = NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray<byte>(
|
||
(void*)_sharedYBuffer, _sharedYBufferSize, Allocator.None);
|
||
|
||
#if ENABLE_UNITY_COLLECTIONS_CHECKS
|
||
NativeArrayUnsafeUtility.SetAtomicSafetyHandle(ref yData, AtomicSafetyHandle.Create());
|
||
#endif
|
||
textures[0].SetPixelData(yData, 0);
|
||
}
|
||
|
||
if (_sharedUBufferSize > 0)
|
||
{
|
||
NativeArray<byte> uData = NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray<byte>(
|
||
(void*)_sharedUBuffer, _sharedUBufferSize, Allocator.None);
|
||
|
||
#if ENABLE_UNITY_COLLECTIONS_CHECKS
|
||
NativeArrayUnsafeUtility.SetAtomicSafetyHandle(ref uData, AtomicSafetyHandle.Create());
|
||
#endif
|
||
textures[1].SetPixelData(uData, 0);
|
||
}
|
||
|
||
if (_sharedVBufferSize > 0)
|
||
{
|
||
NativeArray<byte> vData = NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray<byte>(
|
||
(void*)_sharedVBuffer, _sharedVBufferSize, Allocator.None);
|
||
|
||
#if ENABLE_UNITY_COLLECTIONS_CHECKS
|
||
NativeArrayUnsafeUtility.SetAtomicSafetyHandle(ref vData, AtomicSafetyHandle.Create());
|
||
#endif
|
||
textures[2].SetPixelData(vData, 0);
|
||
}
|
||
|
||
// 注意:不在这里调用Apply,统一在LateUpdate的DisplayFrameApply中执行
|
||
// 这样可以让所有视频对象在同一时机上传GPU,减少CPU/GPU切换
|
||
}
|
||
}
|
||
catch (System.Exception e)
|
||
{
|
||
LogError($"纹理上传失败: {e.Message}");
|
||
}
|
||
#endif
|
||
}
|
||
|
||
public int Init(NativeArray<byte> fileData, int beginFrameIndex = 1, bool syncDecodeFirstFrame = true, bool fastDecode = true)
|
||
{
|
||
#if UNITY_WEBGL
|
||
_videoData = fileData;
|
||
#endif
|
||
_fastDecode = fastDecode;
|
||
return InternalPlay(beginFrameIndex, syncDecodeFirstFrame);
|
||
}
|
||
|
||
|
||
public int InternalPlay(int beginFrameIndex = 1, bool syncDecodeFirstFrame = true)
|
||
{
|
||
#if UNITY_WEBGL
|
||
try
|
||
{
|
||
// 检查Worker系统是否准备就绪
|
||
if (!_isWorkerSystemReady)
|
||
{
|
||
LogError("Worker系统未准备就绪,无法创建解码器");
|
||
return -1;
|
||
}
|
||
|
||
// 创建Worker视频实例
|
||
_instanceId = CreateWorkerVideoInstance();
|
||
if (_instanceId < 0)
|
||
{
|
||
LogError("创建Worker视频实例失败");
|
||
return -1;
|
||
}
|
||
|
||
// 注册实例
|
||
_instances[_instanceId] = this;
|
||
|
||
// 先设置改进的静态回调(传递instanceId参数),确保在数据处理前回调已就绪
|
||
SetWorkerVideoStatusCallback(_instanceId, StaticOnStatusChanged);
|
||
SetWorkerVideoFirstFrameDisplayCallback(_instanceId, StaticOnFirstFrameDisplay);
|
||
|
||
Log($"实例{_instanceId}回调设置完成,开始设置视频数据");
|
||
|
||
// 设置视频数据
|
||
if (_videoData.IsCreated && _videoData.Length > 0)
|
||
{
|
||
// 将NativeArray转换为byte[]
|
||
byte[] dataArray = _videoData.ToArray();
|
||
Log($"准备设置视频数据,大小: {dataArray.Length} bytes");
|
||
bool setDataResult = SetWorkerVideoData(_instanceId, dataArray, dataArray.Length);
|
||
if (!setDataResult)
|
||
{
|
||
LogError("设置视频数据失败");
|
||
return -1;
|
||
}
|
||
Log($"视频数据设置成功");
|
||
}
|
||
else
|
||
{
|
||
LogError("视频数据无效");
|
||
return -1;
|
||
}
|
||
|
||
// 准备视频
|
||
bool prepareResult = PrepareWorkerVideo(_instanceId, _fastDecode);
|
||
if (!prepareResult)
|
||
{
|
||
LogError("准备视频失败");
|
||
return -1;
|
||
}
|
||
|
||
// 开始播放
|
||
bool playResult = PlayWorkerVideo(_instanceId);
|
||
if (!playResult)
|
||
{
|
||
LogError("开始播放失败");
|
||
return -1;
|
||
}
|
||
|
||
// 设置循环播放状态(在播放开始后设置)
|
||
if (_isLooping)
|
||
{
|
||
SetWorkerVideoLooping(_instanceId, _isLooping);
|
||
}
|
||
|
||
if (beginFrameIndex > 1)
|
||
{
|
||
SeekToFrame(beginFrameIndex);
|
||
}
|
||
|
||
_isInitialized = true;
|
||
_lastUploadedFrameIndex = -1; // 重置帧上传追踪
|
||
Log($"Worker解码器初始化成功,ID: {_instanceId}, 数据大小: {_videoData.Length} bytes");
|
||
return 0;
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
LogError($"Worker解码器初始化异常: {e.Message}");
|
||
return -1;
|
||
}
|
||
#else
|
||
return 0;
|
||
#endif
|
||
}
|
||
|
||
public int DecodeNextFrame()
|
||
{
|
||
#if UNITY_WEBGL
|
||
// 如果正在销毁,立即返回
|
||
if (_isDisposing)
|
||
{
|
||
return -1;
|
||
}
|
||
|
||
if (!_isInitialized || _instanceId < 0 || !_isWorkerSystemReady)
|
||
{
|
||
return -1;
|
||
}
|
||
|
||
// 只有在Playing状态时才执行Update
|
||
if (_currentStatus != VideoStatus.Playing)
|
||
{
|
||
// 非播放状态,返回0表示没有新帧但不是错误
|
||
return 0;
|
||
}
|
||
|
||
// Worker版本通过Update方法获取帧数据
|
||
float deltaTime = Time.deltaTime * 1000; // 转换为毫秒
|
||
bool updateResult = UpdateWorkerVideo(_instanceId, deltaTime);
|
||
|
||
if (updateResult)
|
||
{
|
||
// 更新当前帧索引
|
||
_decodedFrameIndex = GetWorkerVideoCurrentFrameIndex(_instanceId);
|
||
|
||
// 只有当帧索引发生变化时,才上传新的纹理数据(避免重复上传同一帧)
|
||
if (_decodedFrameIndex != _lastUploadedFrameIndex &&
|
||
_textureReferences != null &&
|
||
_sharedBuffersInitialized)
|
||
{
|
||
try
|
||
{
|
||
UploadTextureFromSharedBuffer(_textureReferences);
|
||
_lastUploadedFrameIndex = _decodedFrameIndex;
|
||
// 注意:现在使用基于FPS的帧率控制,JS端会自动触发下一帧
|
||
// 不需要再调用NotifyWorkerVideoTextureReady
|
||
}
|
||
catch (System.Exception e)
|
||
{
|
||
LogError($"DecodeNextFrame纹理上传异常: {e.Message}");
|
||
}
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
else
|
||
{
|
||
return -1;
|
||
}
|
||
#else
|
||
return 0;
|
||
#endif
|
||
}
|
||
|
||
public void InternalStop()
|
||
{
|
||
#if UNITY_WEBGL
|
||
// 如果正在销毁,跳过Stop调用(Dispose会处理)
|
||
if (_isDisposing)
|
||
{
|
||
Log($"实例{_instanceId}正在销毁,跳过InternalStop");
|
||
return;
|
||
}
|
||
|
||
if (_instanceId >= 0)
|
||
{
|
||
// 立即更新状态,防止DecodeNextFrame继续调用UpdateWorkerVideo
|
||
_currentStatus = VideoStatus.Stopped;
|
||
Log($"实例{_instanceId}状态立即设置为Stopped");
|
||
|
||
StopWorkerVideo(_instanceId);
|
||
}
|
||
_currentFrameData = System.IntPtr.Zero;
|
||
_lastUploadedFrameIndex = -1; // 重置帧上传追踪
|
||
#endif
|
||
}
|
||
|
||
|
||
public int InternalRestart(NativeArray<byte> newData)
|
||
{
|
||
#if UNITY_WEBGL
|
||
if (_instanceId >= 0 && _isInitialized)
|
||
{
|
||
// 先停止当前播放
|
||
StopWorkerVideo(_instanceId);
|
||
|
||
// 清理旧的帧数据
|
||
_currentFrameData = System.IntPtr.Zero;
|
||
_currentFrameWidth = 0;
|
||
_currentFrameHeight = 0;
|
||
_decodedFrameIndex = 0;
|
||
_lastUploadedFrameIndex = -1; // 重置帧上传追踪
|
||
|
||
// 重置AVFrame信息
|
||
if (_videoFrame != null)
|
||
{
|
||
_videoFrame->width = 0;
|
||
_videoFrame->height = 0;
|
||
_videoFrame->linesize[0] = 0;
|
||
_videoFrame->linesize[1] = 0;
|
||
_videoFrame->linesize[2] = 0;
|
||
_videoFrame->pts = 0;
|
||
}
|
||
|
||
_videoData = newData;
|
||
Log($"Worker解码器重启: 清理旧帧数据,新数据大小: {newData.Length} bytes");
|
||
|
||
// 设置新的视频数据
|
||
byte[] dataArray = _videoData.ToArray();
|
||
bool setDataResult = SetWorkerVideoData(_instanceId, dataArray, dataArray.Length);
|
||
if (!setDataResult)
|
||
{
|
||
LogError("重启时设置视频数据失败");
|
||
return -1;
|
||
}
|
||
|
||
// 重新准备和播放
|
||
bool prepareResult = PrepareWorkerVideo(_instanceId, _fastDecode);
|
||
if (!prepareResult)
|
||
{
|
||
LogError("重启时准备视频失败");
|
||
return -1;
|
||
}
|
||
|
||
bool playResult = PlayWorkerVideo(_instanceId);
|
||
if (!playResult)
|
||
{
|
||
LogError("重启时开始播放失败");
|
||
return -1;
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
else
|
||
{
|
||
// 如果decoder未初始化,重新创建
|
||
Log($"Worker解码器重新创建: 数据大小: {newData.Length} bytes");
|
||
_videoData = newData;
|
||
return InternalPlay();
|
||
}
|
||
#else
|
||
return 0;
|
||
#endif
|
||
}
|
||
|
||
public void Seek(double ms)
|
||
{
|
||
#if UNITY_WEBGL
|
||
if (_isInitialized && _instanceId >= 0 && _isWorkerSystemReady)
|
||
{
|
||
SeekWorkerVideo(_instanceId, (float)ms);
|
||
_lastUploadedFrameIndex = -1; // Seek后重置帧上传追踪,确保下一帧会被上传
|
||
}
|
||
#endif
|
||
}
|
||
|
||
public void SeekToFrame(long frame)
|
||
{
|
||
#if UNITY_WEBGL
|
||
if (_isInitialized && _instanceId >= 0 && _isWorkerSystemReady)
|
||
{
|
||
SeekWorkerVideoToFrame(_instanceId, (int)frame);
|
||
_lastUploadedFrameIndex = -1; // Seek后重置帧上传追踪,确保下一帧会被上传
|
||
}
|
||
#endif
|
||
}
|
||
|
||
// 获取当前帧的数据指针
|
||
public System.IntPtr GetCurrentFrameDataPtr()
|
||
{
|
||
#if UNITY_WEBGL
|
||
return _currentFrameData;
|
||
#else
|
||
return System.IntPtr.Zero;
|
||
#endif
|
||
}
|
||
|
||
// 获取当前帧尺寸
|
||
public void GetCurrentFrameSize(out int width, out int height)
|
||
{
|
||
#if UNITY_WEBGL
|
||
width = _currentFrameWidth;
|
||
height = _currentFrameHeight;
|
||
#else
|
||
width = 0;
|
||
height = 0;
|
||
#endif
|
||
}
|
||
|
||
// 获取解码器状态
|
||
public int GetDecoderStatus()
|
||
{
|
||
#if UNITY_WEBGL
|
||
if (_instanceId >= 0 && _isWorkerSystemReady)
|
||
{
|
||
// 根据当前状态返回对应的状态码
|
||
switch (_currentStatus)
|
||
{
|
||
case VideoStatus.Initialized:
|
||
return 0; // 停止状态
|
||
case VideoStatus.Prepared:
|
||
return 1; // 准备中
|
||
case VideoStatus.Playing:
|
||
return 2; // 播放中
|
||
case VideoStatus.Paused:
|
||
return 1; // 暂停状态
|
||
case VideoStatus.Stopped:
|
||
return 0; // 停止状态
|
||
case VideoStatus.Error:
|
||
return -1; // 错误状态
|
||
default:
|
||
return -1;
|
||
}
|
||
}
|
||
return -1;
|
||
#else
|
||
return 2; // 非WebGL平台默认返回已启动状态
|
||
#endif
|
||
}
|
||
|
||
// 等待解码器启动完成
|
||
public bool WaitForStartComplete(float timeoutSeconds = 5.0f)
|
||
{
|
||
#if UNITY_WEBGL
|
||
if (_instanceId < 0 || !_isWorkerSystemReady) return false;
|
||
|
||
float startTime = Time.time;
|
||
while (Time.time - startTime < timeoutSeconds)
|
||
{
|
||
int status = GetDecoderStatus();
|
||
if (status == 2) // 已启动
|
||
{
|
||
return true;
|
||
}
|
||
else if (status == -1) // 错误
|
||
{
|
||
return false;
|
||
}
|
||
// 状态为0(停止)或1(启动中)时继续等待
|
||
}
|
||
return false; // 超时
|
||
#else
|
||
return true; // 非WebGL平台直接返回成功
|
||
#endif
|
||
}
|
||
|
||
public ref long GetDecodedFrameIndexRef()
|
||
{
|
||
return ref _decodedFrameIndex;
|
||
}
|
||
|
||
// 暂停/恢复播放的公共方法
|
||
public void Pause(bool pause)
|
||
{
|
||
#if UNITY_WEBGL
|
||
if (_instanceId >= 0 && _isWorkerSystemReady)
|
||
{
|
||
if (pause)
|
||
{
|
||
// 立即更新状态为暂停
|
||
_currentStatus = VideoStatus.Paused;
|
||
Log($"实例{_instanceId}状态立即设置为Paused");
|
||
PauseWorkerVideo(_instanceId);
|
||
}
|
||
else
|
||
{
|
||
// 恢复播放时,状态会通过Worker回调更新为Playing
|
||
ResumeWorkerVideo(_instanceId);
|
||
}
|
||
}
|
||
#endif
|
||
}
|
||
|
||
public void CopyFrameDataToTextures(UnityEngine.Texture2D[] textures)
|
||
{
|
||
// Worker解码器在UploadTextureFromSharedBuffer中已经调用了SetPixelData
|
||
// 这里不需要额外操作
|
||
}
|
||
|
||
public void ApplyTextures(UnityEngine.Texture2D[] textures)
|
||
{
|
||
#if UNITY_WEBGL
|
||
// 使用JS端GPU纹理上传,C#端不需要调用Apply
|
||
if (_useJSTextureUpload)
|
||
{
|
||
return;
|
||
}
|
||
|
||
// 统一在LateUpdate中执行Apply,避免多个视频对象频繁切换CPU/GPU
|
||
if (textures != null && textures.Length >= 3)
|
||
{
|
||
if (_sharedYBufferSize > 0) textures[0].Apply(false, false);
|
||
if (_sharedUBufferSize > 0) textures[1].Apply(false, false);
|
||
if (_sharedVBufferSize > 0) textures[2].Apply(false, false);
|
||
}
|
||
#endif
|
||
}
|
||
|
||
public void Dispose()
|
||
{
|
||
#if UNITY_WEBGL
|
||
// 立即设置销毁标志,防止任何回调或异步操作继续执行
|
||
_isDisposing = true;
|
||
|
||
if (_instanceId >= 0)
|
||
{
|
||
// 立即更新状态,防止DecodeNextFrame继续调用UpdateWorkerVideo
|
||
_currentStatus = VideoStatus.Stopped;
|
||
Log($"实例{_instanceId}状态立即设置为Stopped (Dispose)");
|
||
|
||
// 从实例字典中移除,防止回调访问
|
||
_instances.Remove(_instanceId);
|
||
|
||
// 销毁Worker视频实例(这会发送DESTROY消息到worker,清理WASM资源)
|
||
DestroyWorkerVideo(_instanceId);
|
||
_instanceId = -1;
|
||
}
|
||
_isInitialized = false;
|
||
_isWorkerSystemReady = false;
|
||
_currentFrameData = System.IntPtr.Zero;
|
||
|
||
// 【修复内存泄漏】清理SharedBuffer指针引用
|
||
_sharedYBuffer = System.IntPtr.Zero;
|
||
_sharedUBuffer = System.IntPtr.Zero;
|
||
_sharedVBuffer = System.IntPtr.Zero;
|
||
_sharedBuffersInitialized = false;
|
||
|
||
// 【修复内存泄漏】清理纹理引用
|
||
_textureReferences = null;
|
||
|
||
// 【修复内存泄漏】重置帧追踪
|
||
_lastUploadedFrameIndex = -1;
|
||
|
||
// 清理视频数据
|
||
if (_videoData.IsCreated)
|
||
{
|
||
_videoData.Dispose();
|
||
}
|
||
|
||
// 清理定时器
|
||
if (_statusCheckTimer != null)
|
||
{
|
||
_statusCheckTimer.Dispose();
|
||
_statusCheckTimer = null;
|
||
}
|
||
#endif
|
||
if (_videoFrame != null)
|
||
{
|
||
Marshal.FreeHGlobal((System.IntPtr)_videoFrame);
|
||
_videoFrame = null;
|
||
}
|
||
}
|
||
|
||
// 状态检查相关
|
||
private System.Threading.Timer _statusCheckTimer;
|
||
|
||
|
||
private void ___unuseFunction() { OnFirstFrameDecoded?.Invoke(); }
|
||
}
|