/*---------------------------------------------------------------- // Copyright (C) 2025 Beijing All rights reserved. // // Author: huachangmiao // Create Date: 2025/04/11 // Module Describe: 视频解码器抽象基类 //----------------------------------------------------------------*/ using System.Threading; using UnityEngine; using Unity.Collections; #if UNITY_WEBGL && !UNITY_EDITOR using System.Runtime.InteropServices; #endif /// /// 视频解码器抽象基类 /// 包含解码逻辑、播放控制、多线程解码等公共功能 /// 子类需要实现具体的渲染逻辑 /// public abstract unsafe class LeviathanVideoDecoderBase : MonoBehaviour { protected static readonly int ValidWidthRatioIdx = Shader.PropertyToID("_ValidWidthRatio"); protected static readonly int UTexIdx = Shader.PropertyToID("_UTex"); protected static readonly int VTexIdx = Shader.PropertyToID("_VTex"); protected static readonly int AlphaYTexIdx = Shader.PropertyToID("_AYTex"); protected static readonly int AlphaUTexIdx = Shader.PropertyToID("_AUTex"); #if UNITY_WEBGL && !UNITY_EDITOR [DllImport("__Internal")] private static extern bool IsWechatMiniGameEnvironment(); #endif // 日志辅助方法 protected static void Log(string message) { LeviathanLogConfig.Log("VideoDecoderBase", message); } protected static void LogWarning(string message) { LeviathanLogConfig.LogWarning("VideoDecoderBase", message); } protected static void LogError(string message) { LeviathanLogConfig.LogError("VideoDecoderBase", message); } /// /// 静态构造函数 - 在类首次使用时执行平台检测 /// static LeviathanVideoDecoderBase() { #if UNITY_WEBGL && !UNITY_EDITOR try { IsWCGame = IsWechatMiniGameEnvironment(); Log($"微信小游戏平台静态检测: {IsWCGame}"); } catch (System.Exception e) { LogError($"微信小游戏平台静态检测异常: {e}"); IsWCGame = false; } #else IsWCGame = false; #endif } // [LabelText("视频文件(.bytes)")] public TextAsset bytesFile; #if UNITY_EDITOR [System.NonSerialized] protected TextAsset _lastBytesFile; #endif private ILeviathanDecoder _decoder; /// /// 获取当前视频帧指针(子类使用) /// protected LeviathanVideo.Abstractions.AVFrame* VideoFrame => _decoder != null ? _decoder.VideoFrame : null; #if !UNITY_2022_1_OR_NEWER // Unity 2020/2021 兼容:保存数据副本以供解码器使用 protected NativeArray _videoDataBuffer; protected bool _hasVideoDataBuffer = false; #endif // 平台检测 public static bool IsWCGame = false; protected const int TextureCount = 3; protected Texture2D[] _textures; protected double _timeAccumulated; protected double _frameInterval; public double FrameInterval => _frameInterval; protected bool _needInitTexture = true; protected bool _needApplyTexture = false; // 是否为多线程解码 protected bool _isMultithreadedDecode; // 多线程相关 protected Thread _decodeThread; // 帧状态:0=允许解码下一帧, 1=帧已准备好等待显示 protected long _hasFrameReady; protected volatile bool _isPlaying; public bool IsPlaying => _isPlaying; protected bool _isPause; public bool IsPause => _isPause; // 销毁状态标志(防止销毁期间的异步操作继续执行) protected bool _isDestroying = false; // 第一帧回调待执行标志(用于从子线程安全地在主线程执行回调) protected volatile bool _pendingFirstFrameCallback = false; // ==区间循环专用 // 每次播放到End, 跳转到的frame protected long _loopBeginFrameIndex = -1; public long LoopBeginFrameIndex => _loopBeginFrameIndex; protected long _loopEndFrameIndex = -1; public long LoopEndFrameIndex => _loopEndFrameIndex; // 是否正在循环seek中(防止SeekToFrame被多次调用) protected volatile bool _isLoopSeeking = false; protected string _codecName = ""; // 总时长 微秒 protected long _duration; public long Duration => _duration; protected int _videoWidth; public int VideoWidth => _videoWidth; protected int _videoHeight; public int VideoHeight => _videoHeight; protected int _videoValidWidth; public int VideoValidWidth => _videoValidWidth; public long PlayFrameIndex => _playFrameIndex; protected long _playFrameIndex; // 总帧数 protected long _frameCount; public long FrameCount => _frameCount; protected float _playSpeed = 1.0f; public enum PlayAlphaType { None = 0, RightSide = 1, BottomSide = 2, ChromaKey = 3, } public PlayAlphaType AlphaType { get => _alphaType; set { if (_alphaType != value) { SetAlphaType(value); } } } [SerializeField] protected bool disableAutoSize; [SerializeField] protected bool autoPlayOnStart; public bool DisableAutoSize { get => disableAutoSize; set => disableAutoSize = value; } [SerializeField] protected bool _isLooping = true; public bool IsLooping => _isLooping; /// /// 播放完成回调事件(非循环播放时触发) /// public System.Action OnPlayCompleted; /// /// 首帧渲染完毕回调事件 /// 无论同步或异步解码首帧模式都会触发 /// public System.Action OnFirstFrameRendered; [SerializeField] protected PlayAlphaType _alphaType = PlayAlphaType.None; // 上次应用的alphaType,用于检测是否需要更新材质 protected PlayAlphaType _lastAppliedAlphaType = PlayAlphaType.None; protected virtual void Start() { if (autoPlayOnStart && bytesFile) Play(); } /// /// 第一帧解码完成回调(由解码器触发,可能在子线程中调用) /// 设置标志位,实际处理在主线程的 Update 中执行 /// protected virtual void OnFirstFrameDecodedCallback() { _pendingFirstFrameCallback = true; } /// /// 处理第一帧解码完成的实际逻辑(必须在主线程调用) /// 子类需要重写此方法来处理渲染目标的显示 /// protected virtual void HandleFirstFrameDecoded() { // 先更新纹理,确保显示的是新视频的第一帧 DisplayFrame(); // 子类负责启用渲染目标 OnEnableRenderTarget(); // 触发首帧渲染完毕回调 OnFirstFrameRendered?.Invoke(); } /// /// 启用渲染目标(子类实现) /// protected abstract void OnEnableRenderTarget(); /// /// 禁用渲染目标(子类实现) /// protected abstract void OnDisableRenderTarget(); protected virtual void OnDestroy() { Log("开始销毁,清理所有资源"); // 立即设置销毁标志,阻止任何新的操作 _isDestroying = true; _isPlaying = false; // 强制同步停止,避免异步操作 try { InternalStopSync(); } catch (System.Exception e) { LogWarning($"同步停止时出现异常: {e.Message}"); } // Release textures if (_textures != null && _textures.Length > 0) { foreach (var tex in _textures) { if (tex) Destroy(tex); } } _textures = null; } /// /// 播放视频 /// /// 视频文件 /// 播放透明度 /// 是否多线程解码 /// 开始播放的帧索引 /// 是否同步解码第一帧, 区别在于是否当前帧就能立马看到视频, 会耗时10ms /// 是否启用快速解码(跳过环路滤波器),默认开启以提升性能 /// 返回值: 0表示成功, 其他表示错误码 public int PlayVideo(TextAsset fileData, PlayAlphaType playAlpha, bool multithreadedDecode, int beginFrameIndex = 1, bool syncDecodeFirstFrame = true, bool fastDecode = true) { // 如果正在销毁,立即返回 if (_isDestroying) { LogWarning("PlayVideo: 组件正在销毁,取消播放"); return -1; } if (fileData == null) { return -1; } var ret = 0; if (_isPlaying) return ret; _playFrameIndex = 0; _timeAccumulated = 0; _isLoopSeeking = false; _alphaType = playAlpha; #if UNITY_WEBGL // webGL不支持c#多线程 _isMultithreadedDecode = false; #else _isMultithreadedDecode = multithreadedDecode; #endif // 根据平台选择解码器 if (IsWCGame) { OnDisableRenderTarget(); // 创建新的decoder - 使用Worker版本 var workerDecoder = new LeviathanWorkerDecoder(); workerDecoder.IsLooping = _isLooping; // 设置循环播放状态 _decoder = workerDecoder; Log($"[PlayWithData] 创建新的微信小游戏Worker解码器 (静态检测: {IsWCGame}, 循环播放: {_isLooping})"); } else { // 只有同步解码第一帧时才立即启用,否则等待第一帧解码完成后再启用 if (syncDecodeFirstFrame) { OnEnableRenderTarget(); } else { OnDisableRenderTarget(); } _decoder = new LeviathanSoftwareDecoder(); Log($"[PlayWithData] 使用软件解码器 (静态检测: {IsWCGame})"); } _needInitTexture = true; // 订阅第一帧回调事件 _decoder.OnFirstFrameDecoded += OnFirstFrameDecodedCallback; // 初始化解码器 bytesFile = fileData; #if UNITY_EDITOR _lastBytesFile = bytesFile; #endif #if UNITY_2022_1_OR_NEWER ret = _decoder.Init(fileData.GetData(), beginFrameIndex, syncDecodeFirstFrame, fastDecode); #else // Unity 2020/2021: 创建持久的数据副本供解码器使用 if (_hasVideoDataBuffer) { _videoDataBuffer.Dispose(); } _videoDataBuffer = new NativeArray(fileData.bytes, Allocator.Persistent); _hasVideoDataBuffer = true; ret = _decoder.Init(_videoDataBuffer, beginFrameIndex, syncDecodeFirstFrame, fastDecode); #endif _frameCount = _decoder.FrameCount; _loopBeginFrameIndex = beginFrameIndex; _loopEndFrameIndex = _frameCount; _duration = _decoder.Duration; _frameInterval = _decoder.FrameInterval; _codecName = _decoder.CodecName; if (ret != 0) { LogError($"DecodeNextFrame ret: {ret}"); } OnUpdateRenderTargetSize(); // 只有同步解码第一帧时才立即显示,否则等待第一帧解码完成后在 HandleFirstFrameDecoded 中显示 if (syncDecodeFirstFrame) { DisplayFrame(); // 同步模式下立即触发首帧渲染完毕回调 OnFirstFrameRendered?.Invoke(); } if (_isMultithreadedDecode) { // 多线程模式 Interlocked.Exchange(ref _hasFrameReady, 1); // 第一帧已准备好 _isPlaying = true; _isPause = false; _decodeThread = new Thread(DecodeThreadFunction); #if UNITY_EDITOR _decodeThread.Name = $"{nameof(LeviathanVideoDecoderBase)}.{nameof(DecodeThreadFunction)}"; #endif _decodeThread.Start(); } else { // 单线程模式 _isPlaying = true; _isPause = false; } return ret; } /// /// 更新渲染目标尺寸(子类实现) /// protected abstract void OnUpdateRenderTargetSize(); protected void InternalStop() { if (!_isPlaying) return; // 立即设置状态,防止重复调用 _isPlaying = false; // 其他平台的同步停止逻辑 InternalStopSync(); } // 同步停止方法(非微信平台) protected void InternalStopSync() { // 重置第一帧回调标志,避免旧视频的回调影响新视频 _pendingFirstFrameCallback = false; _needApplyTexture = false; _isPause = false; if (_isMultithreadedDecode) { if (_decodeThread != null && _decodeThread.IsAlive) { _decodeThread.Join(1000); // 添加超时避免死锁 if (_decodeThread.IsAlive) { // 强制终止线程 _decodeThread.Abort(); } } _decodeThread = null; } // 其他平台正常销毁decoder if (_decoder != null) { _decoder.OnFirstFrameDecoded -= OnFirstFrameDecodedCallback; _decoder.InternalStop(); _decoder.Dispose(); _decoder = null; } #if !UNITY_2022_1_OR_NEWER // Unity 2020/2021: 释放数据缓冲区 if (_hasVideoDataBuffer) { _videoDataBuffer.Dispose(); _hasVideoDataBuffer = false; } #endif OnDisableRenderTarget(); } // 主线程 protected virtual void Update() { // 如果正在销毁,立即返回 if (_isDestroying) return; // 处理第一帧回调(从子线程安全地在主线程执行) if (_pendingFirstFrameCallback) { _pendingFirstFrameCallback = false; HandleFirstFrameDecoded(); } if (_isPause) return; if (!_isPlaying) return; #if UNITY_WEBGL && !UNITY_EDITOR _timeAccumulated += 1.0 / Application.targetFrameRate * _playSpeed; #else _timeAccumulated += Time.deltaTime * _playSpeed; #endif if (_isMultithreadedDecode) { // 多线程模式 if (_timeAccumulated >= _frameInterval) { // 检查是否有新帧准备好 if (Interlocked.Read(ref _hasFrameReady) > 0) { // 复制帧数据到纹理(不立即Apply) DisplayFrame(applyImmediately: false); _timeAccumulated -= _frameInterval; // DisplayFrame 完成后,通知解码线程可以解码下一帧 Interlocked.Exchange(ref _hasFrameReady, 0); } if (_timeAccumulated > _frameInterval * 3) { _timeAccumulated = _frameInterval; } } } else { // 单线程模式 if (IsWCGame) { // Worker解码器:每次Update解码一帧 if (_decoder.DecodeNextFrame() == 0) { DisplayFrame(applyImmediately: false); } } else { // 每次Update最多解码一帧,不追帧 if (_timeAccumulated >= _frameInterval) { if (_decoder.DecodeNextFrame() == 0) { _timeAccumulated -= _frameInterval; long currentFrame = _decoder.DecodedFrameIndex; if (currentFrame >= _loopEndFrameIndex) { if (_isLooping) { SeekToFrame(_loopBeginFrameIndex); } } DisplayFrame(applyImmediately: false); } if (_timeAccumulated > _frameInterval * 3) { _timeAccumulated = _frameInterval; } } } if (_playFrameIndex >= _loopEndFrameIndex && !_isLooping) { Pause(); OnPlayCompleted?.Invoke(); } } } protected virtual void LateUpdate() { if (_needApplyTexture) { _needApplyTexture = false; if (_decoder != null && _textures != null) { DisplayFrameApply(); } } } // 解码线程(仅多线程模式) protected void DecodeThreadFunction() { try { while (_isPlaying) { if (!_isPause) { // 等待主线程显示完当前帧(_hasFrameReady == 0 表示允许解码下一帧) while (Interlocked.Read(ref _hasFrameReady) != 0 && _isPlaying) { Thread.Sleep(1); } if (!_isPlaying) break; // 解码下一帧 if (_decoder.DecodeNextFrame() == 0) { // 解码成功,通知主线程帧已准备好 Interlocked.Exchange(ref _hasFrameReady, 1); } if (_playFrameIndex >= _loopEndFrameIndex && !_isLoopSeeking) { if (_isLooping) { _isLoopSeeking = true; SeekToFrame(_loopBeginFrameIndex); // Seek后继续解码,_hasFrameReady 保持为 1,等待主线程显示 } else { _isPause = true; Interlocked.Exchange(ref _hasFrameReady, 1); // 触发播放完成回调 OnPlayCompleted?.Invoke(); } } } else { Thread.Sleep(5); } } } catch (ThreadAbortException) { Thread.ResetAbort(); } } // 初始化纹理 protected virtual void InitTextures() { if (_textures != null && _textures.Length > 0) { Destroy(_textures[0]); Destroy(_textures[1]); Destroy(_textures[2]); } var videoFrame = _decoder.VideoFrame; // 软件解码器使用YUV格式 _textures = new Texture2D[TextureCount]; _textures[0] = new Texture2D(videoFrame->linesize[0], videoFrame->height, TextureFormat.Alpha8, false); _textures[1] = new Texture2D(videoFrame->linesize[1], videoFrame->height / 2, TextureFormat.Alpha8, false); _textures[2] = new Texture2D(videoFrame->linesize[2], videoFrame->height / 2, TextureFormat.Alpha8, false); _textures[0].wrapMode = TextureWrapMode.Clamp; _textures[1].wrapMode = TextureWrapMode.Clamp; _textures[2].wrapMode = TextureWrapMode.Clamp; _videoValidWidth = videoFrame->width; _videoWidth = videoFrame->linesize[0]; _videoHeight = videoFrame->height; OnInitMaterial(); OnUpdateRenderTargetSize(); } /// /// 初始化材质(子类实现) /// protected abstract void OnInitMaterial(); /// /// 更新纹理 /// /// 是否立即Apply上传到GPU。false时只复制数据,可以稍后调用DisplayFrameApply protected virtual void DisplayFrame(bool applyImmediately = true) { var videoFrame = _decoder.VideoFrame; if (videoFrame->linesize[0] <= 0) return; // 检查纹理是否需要重新创建 if (_textures == null || _textures[0].width != videoFrame->linesize[0] || _textures[0].height != videoFrame->height) { InitTextures(); _lastAppliedAlphaType = _alphaType; _needInitTexture = false; if (_decoder is LeviathanWorkerDecoder workerDecoder) { workerDecoder.SetTextureIds(_textures); } } else if (_needInitTexture) { // 只在alphaType变化时才更新材质 if (_alphaType != _lastAppliedAlphaType) { OnInitMaterial(); _lastAppliedAlphaType = _alphaType; } // 如果是Worker版本,将纹理ID传递给JSLIB if (_decoder is LeviathanWorkerDecoder workerDecoder) { workerDecoder.SetTextureIds(_textures); } _needInitTexture = false; } // 复制帧数据到纹理(需要保护_videoFrame) _decoder.CopyFrameDataToTextures(_textures); // 根据参数决定是否立即Apply if (applyImmediately) { _decoder.ApplyTextures(_textures); _needApplyTexture = false; // 立即Apply后,清除延迟标志 } else { _needApplyTexture = true; // 延迟Apply,设置标志在LateUpdate中执行 } // 更新播放帧索引 if (_isMultithreadedDecode) { _playFrameIndex = Interlocked.Read(ref _decoder.GetDecodedFrameIndexRef()); } else { _playFrameIndex = _decoder.DecodedFrameIndex; } // 重置循环seek标志 _isLoopSeeking = false; } /// /// 将纹理数据上传到GPU(可与下一帧解码并行) /// 配合 DisplayFrame(false) 使用,实现分步显示优化 /// protected virtual void DisplayFrameApply() { _decoder.ApplyTextures(_textures); } // 跳转到指定时间点 public void Seek(double ms) { _decoder.Seek(ms); } // 跳转到指定帧 public void SeekToFrame(long frame) { _decoder.SeekToFrame(frame); } public void SetPlaySpeed(float speed) { _playSpeed = speed; } public virtual void SetAlphaType(PlayAlphaType alphaTypeValue) { _alphaType = alphaTypeValue; if (_textures == null || _textures.Length <= 0) return; OnUpdateMaterial(); _lastAppliedAlphaType = _alphaType; OnUpdateRenderTargetSize(); } /// /// 更新材质(子类实现) /// protected abstract void OnUpdateMaterial(); public void SetLooping(bool isLooping) { _isLooping = isLooping; // 如果是微信小游戏平台,设置解码器的循环播放状态 if (IsWCGame) { _decoder.IsLooping = isLooping; } if (_isLooping && _isPause) { Resume(); } } /// /// 设置循环播放区间, 帧数范围为 1 ~ FrameCount /// /// 开始帧索引 /// 结束帧索引 public void SetLoopingFrame(long beginFrame, long endFrame) { _loopBeginFrameIndex = beginFrame; _loopEndFrameIndex = endFrame; } /// /// 切换视频 /// /// 视频文件 /// 播放透明度 /// 是否多线程解码 /// 开始播放的帧索引 /// 是否同步解码第一帧, 区别在于是否当前帧就能立马看到视频, 会耗时10ms /// 是否启用快速解码(跳过环路滤波器),默认开启以提升性能 /// 返回值: true表示成功, false表示失败 public bool ChangeVideo(TextAsset fileData, PlayAlphaType playAlpha, bool multithreadedDecode, int beginFrameIndex = 1, bool syncDecodeFirstFrame = true, bool fastDecode = true) { // 如果不同步解码第一帧,先隐藏渲染目标避免显示旧帧 if (!syncDecodeFirstFrame) { OnDisableRenderTarget(); } return ChangeVideoSync(fileData, playAlpha, multithreadedDecode, beginFrameIndex, syncDecodeFirstFrame, fastDecode); } protected bool ChangeVideoSync(TextAsset fileData, PlayAlphaType playAlpha, bool multithreadedDecode, int beginFrameIndex = 1, bool syncDecodeFirstFrame = true, bool fastDecode = true) { Log("开始同步切换视频"); try { // 同步停止当前视频 StopVideoSync(); // 同步播放新视频 int result = PlayVideoSync(fileData, playAlpha, multithreadedDecode, beginFrameIndex, syncDecodeFirstFrame, fastDecode); if (result != 0) { LogError($"同步播放视频失败,错误码: {result}"); return false; } Log("同步切换视频完成"); return true; } catch (System.Exception e) { LogError($"同步切换视频异常: {e.Message}"); return false; } } protected void StopVideoSync() { // 立即禁用渲染目标显示,避免显示旧帧 OnDisableRenderTarget(); if (_isPlaying) { if (_isPlaying) { InternalStop(); } // 同步等待停止操作完成 int maxWaitCount = 1000; // 最多等待1000次循环 int waitCount = 0; while ((_isPlaying) && waitCount < maxWaitCount) { // 在同步模式下,我们不能使用yield,所以使用Thread.Sleep进行短暂等待 Thread.Sleep(1); waitCount++; } if (waitCount >= maxWaitCount) { LogWarning("同步停止视频超时,继续执行后续操作"); } else { Log($"视频同步停止完成,等待了 {waitCount} 次循环"); } } } protected int PlayVideoSync(TextAsset fileData, PlayAlphaType playAlpha, bool multithreadedDecode, int beginFrameIndex = 1, bool syncDecodeFirstFrame = true, bool fastDecode = true) { // 播放新视频 int result; if (fileData != null) { // 使用文件数据播放 result = PlayVideo(fileData, playAlpha, multithreadedDecode, beginFrameIndex, syncDecodeFirstFrame, fastDecode); Log($"同步切换到视频资源: {fileData.name}"); } else { LogError("没有提供有效的视频源"); return -1; } if (result != 0) { LogError($"同步播放视频失败,错误码: {result}"); return result; } return result; } internal void Play() { if (!Application.isPlaying) return; if (bytesFile == null) return; PlayVideo(bytesFile, _alphaType, true); } public void Stop() { if (!Application.isPlaying) return; InternalStop(); } public void Pause() { if (!Application.isPlaying) return; _isPause = true; // 调用解码器的暂停方法 _decoder?.Pause(true); } public void Resume() { if (!Application.isPlaying) return; _isPause = false; // 调用解码器的恢复方法 _decoder?.Pause(false); } }