/*----------------------------------------------------------------
// 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);
}
}