video plugins
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
/*----------------------------------------------------------------
|
||||
// Copyright (C) 2025 Beijing All rights reserved.
|
||||
//
|
||||
// Author: huachangmiao
|
||||
// Create Date: 2025/04/11
|
||||
// Module Describe:
|
||||
//----------------------------------------------------------------*/
|
||||
|
||||
using System;
|
||||
using LeviathanVideo.Abstractions;
|
||||
using Unity.Collections;
|
||||
|
||||
/// <summary>
|
||||
/// Leviathan视频解码器接口
|
||||
/// </summary>
|
||||
internal unsafe interface ILeviathanDecoder : IDisposable
|
||||
{
|
||||
// 事件
|
||||
event Action OnFirstFrameDecoded;
|
||||
// 属性
|
||||
long FrameCount { get; }
|
||||
double FrameInterval { get; }
|
||||
string CodecName { get; }
|
||||
long Duration { get; }
|
||||
long DecodedFrameIndex { get; }
|
||||
AVFrame* VideoFrame { get; }
|
||||
bool IsLooping { get; set; }
|
||||
|
||||
// 方法
|
||||
int Init(NativeArray<byte> fileData, int beginFrameIndex = 1, bool syncDecodeFirstFrame = true, bool fastDecode = true);
|
||||
int InternalPlay(int beginFrameIndex = 1, bool syncDecodeFirstFrame = true);
|
||||
int DecodeNextFrame();
|
||||
void InternalStop();
|
||||
void Seek(double ms);
|
||||
void SeekToFrame(long frame);
|
||||
void Pause(bool pause);
|
||||
ref long GetDecodedFrameIndexRef();
|
||||
|
||||
// 微信平台特有方法(其他平台提供空实现)
|
||||
int GetDecoderStatus();
|
||||
|
||||
// 优化的分步显示方法
|
||||
/// <summary>
|
||||
/// 将视频帧数据复制到纹理(需要在信号保护下执行)
|
||||
/// </summary>
|
||||
void CopyFrameDataToTextures(UnityEngine.Texture2D[] textures);
|
||||
|
||||
/// <summary>
|
||||
/// 将纹理数据应用到GPU(可以和下一帧解码并行)
|
||||
/// </summary>
|
||||
void ApplyTextures(UnityEngine.Texture2D[] textures);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3aa07cec7daa4b94abd02bcd26cec3ca
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"name": "Leviathan.Runtime",
|
||||
"rootNamespace": "",
|
||||
"references": [
|
||||
"GUID:f1d67e105160e074c99d37c9215900e1"
|
||||
],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": true,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cb496f7121888314e838e8d8fd3425e6
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,149 @@
|
||||
/*----------------------------------------------------------------
|
||||
// Copyright (C) 2025 Beijing All rights reserved.
|
||||
//
|
||||
// Author: huachangmiao
|
||||
// Create Date: 2025/10/29
|
||||
// Module Describe: Leviathan日志配置类
|
||||
//----------------------------------------------------------------*/
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
using UnityEngine;
|
||||
|
||||
//******************************************************************
|
||||
// 日志配置类
|
||||
//******************************************************************
|
||||
public static class LeviathanLogConfig
|
||||
{
|
||||
// 日志开关
|
||||
private static bool _enableWorkerManagerLog = false;
|
||||
private static bool _enableWorkerVideoLog = false;
|
||||
private static bool _enableWorkerDecoderLog = false;
|
||||
private static bool _enableSoftwareDecoderLog = false;
|
||||
private static bool _enableVideoDecoderLog = false;
|
||||
private static bool _enableAllLog = false;
|
||||
|
||||
// 公开属性
|
||||
public static bool EnableWorkerManagerLog
|
||||
{
|
||||
get => _enableWorkerManagerLog;
|
||||
set
|
||||
{
|
||||
_enableWorkerManagerLog = value;
|
||||
UpdateJSLogConfig();
|
||||
}
|
||||
}
|
||||
|
||||
public static bool EnableWorkerVideoLog
|
||||
{
|
||||
get => _enableWorkerVideoLog;
|
||||
set
|
||||
{
|
||||
_enableWorkerVideoLog = value;
|
||||
UpdateJSLogConfig();
|
||||
}
|
||||
}
|
||||
|
||||
public static bool EnableWorkerDecoderLog
|
||||
{
|
||||
get => _enableWorkerDecoderLog;
|
||||
set
|
||||
{
|
||||
_enableWorkerDecoderLog = value;
|
||||
UpdateJSLogConfig();
|
||||
}
|
||||
}
|
||||
|
||||
public static bool EnableSoftwareDecoderLog
|
||||
{
|
||||
get => _enableSoftwareDecoderLog;
|
||||
set => _enableSoftwareDecoderLog = value;
|
||||
}
|
||||
|
||||
public static bool EnableVideoDecoderLog
|
||||
{
|
||||
get => _enableVideoDecoderLog;
|
||||
set => _enableVideoDecoderLog = value;
|
||||
}
|
||||
|
||||
public static bool EnableAllLog
|
||||
{
|
||||
get => _enableAllLog;
|
||||
set
|
||||
{
|
||||
_enableAllLog = value;
|
||||
UpdateJSLogConfig();
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_WEBGL && !UNITY_EDITOR
|
||||
[DllImport("__Internal")]
|
||||
private static extern void SetLeviathanLogConfig(bool workerManager, bool workerVideo, bool workerDecoder, bool all);
|
||||
#endif
|
||||
|
||||
// 更新JS端的日志配置
|
||||
private static void UpdateJSLogConfig()
|
||||
{
|
||||
#if UNITY_WEBGL && !UNITY_EDITOR
|
||||
try
|
||||
{
|
||||
SetLeviathanLogConfig(_enableWorkerManagerLog, _enableWorkerVideoLog, _enableWorkerDecoderLog, _enableAllLog);
|
||||
}
|
||||
catch (System.Exception e)
|
||||
{
|
||||
Debug.LogWarning($"[LeviathanLogConfig] 更新JS日志配置失败: {e.Message}");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// 便捷方法:开启所有日志
|
||||
public static void EnableAll()
|
||||
{
|
||||
EnableAllLog = true;
|
||||
}
|
||||
|
||||
// 便捷方法:关闭所有日志
|
||||
public static void DisableAll()
|
||||
{
|
||||
EnableAllLog = false;
|
||||
EnableWorkerManagerLog = false;
|
||||
EnableWorkerVideoLog = false;
|
||||
EnableWorkerDecoderLog = false;
|
||||
EnableSoftwareDecoderLog = false;
|
||||
EnableVideoDecoderLog = false;
|
||||
}
|
||||
|
||||
// C#端日志辅助方法
|
||||
public static void Log(string module, object message)
|
||||
{
|
||||
if (!ShouldLog(module)) return;
|
||||
Debug.Log($"[Leviathan{module}] {message}");
|
||||
}
|
||||
|
||||
public static void LogWarning(string module, object message)
|
||||
{
|
||||
if (!ShouldLog(module)) return;
|
||||
Debug.LogWarning($"[Leviathan{module}] {message}");
|
||||
}
|
||||
|
||||
public static void LogError(string module, object message)
|
||||
{
|
||||
if (!ShouldLog(module)) return;
|
||||
Debug.LogError($"[Leviathan{module}] {message}");
|
||||
}
|
||||
|
||||
private static bool ShouldLog(string module)
|
||||
{
|
||||
if (_enableAllLog) return true;
|
||||
|
||||
return module switch
|
||||
{
|
||||
"WorkerManager" => _enableWorkerManagerLog,
|
||||
"WorkerVideo" => _enableWorkerVideoLog,
|
||||
"WorkerDecoder" => _enableWorkerDecoderLog,
|
||||
"SoftwareDecoder" => _enableSoftwareDecoderLog,
|
||||
"VideoDecoder" => _enableVideoDecoderLog,
|
||||
_ => false
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 52a41d7bdc4a88241888a81be912b716
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,301 @@
|
||||
/*----------------------------------------------------------------
|
||||
// Copyright (C) 2025 Beijing All rights reserved.
|
||||
//
|
||||
// Author: huachangmiao
|
||||
// Create Date: 2025/04/11
|
||||
// Module Describe: 3D MeshRenderer 视频渲染组件
|
||||
//----------------------------------------------------------------*/
|
||||
|
||||
using UnityEngine;
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// 3D MeshRenderer 视频渲染组件
|
||||
/// 继承自 LeviathanVideoDecoderBase,用于在 3D 物体(如 Cube、Plane)上显示视频
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(MeshRenderer))]
|
||||
public unsafe class LeviathanVideo3DRenderer : LeviathanVideoDecoderBase
|
||||
{
|
||||
private static readonly int MainTexIdx = Shader.PropertyToID("_MainTex");
|
||||
|
||||
[Header("3D Renderer Settings")]
|
||||
[SerializeField]
|
||||
[Tooltip("是否翻转Y轴UV,用于修正不同模型的UV方向")]
|
||||
private bool _reverseY = true;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("材质所在的索引(用于多材质渲染器)")]
|
||||
private int _materialIndex = 0;
|
||||
|
||||
private Material _material;
|
||||
|
||||
private new static void Log(string message)
|
||||
{
|
||||
LeviathanLogConfig.Log("Video3DRenderer", message);
|
||||
}
|
||||
|
||||
private new static void LogWarning(string message)
|
||||
{
|
||||
LeviathanLogConfig.LogWarning("Video3DRenderer", message);
|
||||
}
|
||||
|
||||
private new static void LogError(string message)
|
||||
{
|
||||
LeviathanLogConfig.LogError("Video3DRenderer", message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 启用渲染目标
|
||||
/// </summary>
|
||||
protected override void OnEnableRenderTarget()
|
||||
{
|
||||
MeshRenderer meshRenderer = GetComponent<MeshRenderer>();
|
||||
if (meshRenderer)
|
||||
{
|
||||
meshRenderer.enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 禁用渲染目标
|
||||
/// </summary>
|
||||
protected override void OnDisableRenderTarget()
|
||||
{
|
||||
MeshRenderer meshRenderer = GetComponent<MeshRenderer>();
|
||||
if (meshRenderer)
|
||||
{
|
||||
meshRenderer.enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新渲染目标尺寸
|
||||
/// 3D渲染器不需要调整尺寸,由模型本身决定
|
||||
/// </summary>
|
||||
protected override void OnUpdateRenderTargetSize()
|
||||
{
|
||||
// 3D渲染器不需要像UI那样自动调整尺寸
|
||||
// 尺寸由模型本身决定
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 初始化材质
|
||||
/// </summary>
|
||||
protected override void OnInitMaterial()
|
||||
{
|
||||
MeshRenderer meshRenderer = GetComponent<MeshRenderer>();
|
||||
if (meshRenderer)
|
||||
{
|
||||
UpdateMeshMaterial(meshRenderer);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新材质
|
||||
/// </summary>
|
||||
protected override void OnUpdateMaterial()
|
||||
{
|
||||
MeshRenderer meshRenderer = GetComponent<MeshRenderer>();
|
||||
if (meshRenderer)
|
||||
{
|
||||
UpdateMeshMaterial(meshRenderer);
|
||||
}
|
||||
}
|
||||
|
||||
protected void UpdateMeshMaterial(MeshRenderer meshRenderer)
|
||||
{
|
||||
if (_textures == null || _textures.Length < 3) return;
|
||||
|
||||
// 创建新材质或更新现有材质
|
||||
Material newMaterial;
|
||||
switch (_alphaType)
|
||||
{
|
||||
case PlayAlphaType.RightSide:
|
||||
newMaterial = new Material(Shader.Find("LeviathanVideo/YUV420P_3D_Alpha_Right"));
|
||||
newMaterial.SetFloat(ValidWidthRatioIdx, (float)_videoValidWidth / _videoWidth);
|
||||
break;
|
||||
case PlayAlphaType.BottomSide:
|
||||
newMaterial = new Material(Shader.Find("LeviathanVideo/YUV420P_3D_Alpha_Bottom"));
|
||||
break;
|
||||
case PlayAlphaType.ChromaKey:
|
||||
newMaterial = new Material(Shader.Find("LeviathanVideo/YUV420P_3D_ChromaKey"));
|
||||
break;
|
||||
case PlayAlphaType.None:
|
||||
default:
|
||||
newMaterial = new Material(Shader.Find("LeviathanVideo/YUV420P_3D"));
|
||||
break;
|
||||
}
|
||||
|
||||
// 设置纹理
|
||||
newMaterial.SetTexture(MainTexIdx, _textures[0]);
|
||||
newMaterial.SetTexture(UTexIdx, _textures[1]);
|
||||
newMaterial.SetTexture(VTexIdx, _textures[2]);
|
||||
|
||||
// 设置Y轴翻转
|
||||
newMaterial.SetFloat("_ReverseY", _reverseY ? 1 : 0);
|
||||
|
||||
// 清理旧材质
|
||||
if (_material != null)
|
||||
{
|
||||
Destroy(_material);
|
||||
}
|
||||
_material = newMaterial;
|
||||
|
||||
// 应用材质到渲染器
|
||||
var materials = meshRenderer.materials;
|
||||
if (_materialIndex >= 0 && _materialIndex < materials.Length)
|
||||
{
|
||||
materials[_materialIndex] = newMaterial;
|
||||
meshRenderer.materials = materials;
|
||||
}
|
||||
else
|
||||
{
|
||||
meshRenderer.material = newMaterial;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnDestroy()
|
||||
{
|
||||
base.OnDestroy();
|
||||
|
||||
// 清理材质
|
||||
if (_material != null)
|
||||
{
|
||||
Destroy(_material);
|
||||
_material = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置是否翻转Y轴
|
||||
/// </summary>
|
||||
public void SetReverseY(bool reverseY)
|
||||
{
|
||||
_reverseY = reverseY;
|
||||
if (_material != null)
|
||||
{
|
||||
_material.SetFloat("_ReverseY", _reverseY ? 1 : 0);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置材质索引
|
||||
/// </summary>
|
||||
public void SetMaterialIndex(int index)
|
||||
{
|
||||
_materialIndex = index;
|
||||
if (_textures != null && _textures.Length > 0)
|
||||
{
|
||||
OnUpdateMaterial();
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
private void OnValidate()
|
||||
{
|
||||
// 使用 delayCall 避免在 OnValidate 中调用 SendMessage 相关操作
|
||||
var currentBytesFile = bytesFile;
|
||||
var lastBytesFile = _lastBytesFile;
|
||||
UnityEditor.EditorApplication.delayCall += () =>
|
||||
{
|
||||
if (this == null) return;
|
||||
|
||||
SetAlphaType(_alphaType);
|
||||
|
||||
// 检测 bytesFile 是否发生变化
|
||||
if (currentBytesFile != lastBytesFile)
|
||||
{
|
||||
_lastBytesFile = currentBytesFile;
|
||||
if (IsPlaying && currentBytesFile != null)
|
||||
{
|
||||
Log($"编辑器中检测到 bytesFile 变化,自动播放新视频");
|
||||
Stop();
|
||||
Play();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
[CustomEditor(typeof(LeviathanVideo3DRenderer))]
|
||||
public class LeviathanVideo3DRendererEditor : Editor
|
||||
{
|
||||
// 格式化时间:秒转换为 HH:MM:SS.mmm 格式
|
||||
private static string FormatTimeSeconds(double totalSeconds)
|
||||
{
|
||||
int hours = (int)(totalSeconds / 3600);
|
||||
int minutes = (int)((totalSeconds % 3600) / 60);
|
||||
int seconds = (int)(totalSeconds % 60);
|
||||
int milliseconds = (int)((totalSeconds % 1) * 1000);
|
||||
return $"{hours:D2}:{minutes:D2}:{seconds:D2}.{milliseconds:D3}";
|
||||
}
|
||||
|
||||
// 格式化时间:微秒转换为 HH:MM:SS.mmm 格式
|
||||
private static string FormatTimeMicroseconds(long microseconds)
|
||||
{
|
||||
return FormatTimeSeconds(microseconds / 1000000.0);
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
DrawDefaultInspector();
|
||||
var mono = (LeviathanVideo3DRenderer)target;
|
||||
|
||||
// Display video dimensions as read-only fields
|
||||
EditorGUI.BeginDisabledGroup(true);
|
||||
EditorGUILayout.TextField("帧数", $"{mono.PlayFrameIndex} / {mono.FrameCount}");
|
||||
// FrameInterval 单位是秒,Duration 单位是微秒
|
||||
EditorGUILayout.TextField("播放时间", FormatTimeSeconds(mono.PlayFrameIndex * mono.FrameInterval));
|
||||
EditorGUILayout.TextField("总时间", FormatTimeMicroseconds(mono.Duration));
|
||||
EditorGUILayout.TextField("FPS", mono.FrameInterval > 0 ? $"{1.0 / mono.FrameInterval:F2}" : "0");
|
||||
EditorGUILayout.TextField("纹理尺寸", $"{mono.VideoWidth} × {mono.VideoHeight}");
|
||||
EditorGUILayout.TextField("视频有效尺寸", $"{mono.VideoValidWidth} × {mono.VideoHeight}");
|
||||
EditorGUI.EndDisabledGroup();
|
||||
|
||||
{
|
||||
bool oldValue = mono.IsPlaying;
|
||||
// 绘制checkbox
|
||||
bool newValue = EditorGUILayout.Toggle("isPlaying", oldValue);
|
||||
|
||||
// 如果用户切换了checkbox
|
||||
if (oldValue != newValue)
|
||||
{
|
||||
if (newValue)
|
||||
{
|
||||
mono.Play();
|
||||
}
|
||||
else
|
||||
{
|
||||
mono.Stop();
|
||||
}
|
||||
EditorUtility.SetDirty(target);
|
||||
}
|
||||
}
|
||||
{
|
||||
bool oldValue = mono.IsPause;
|
||||
// 绘制checkbox
|
||||
bool newValue = EditorGUILayout.Toggle("isPause", oldValue);
|
||||
|
||||
// 如果用户切换了checkbox
|
||||
if (oldValue != newValue)
|
||||
{
|
||||
if (newValue)
|
||||
{
|
||||
mono.Pause();
|
||||
}
|
||||
else
|
||||
{
|
||||
mono.Resume();
|
||||
}
|
||||
EditorUtility.SetDirty(target);
|
||||
}
|
||||
}
|
||||
this.Repaint();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ee83c3bdcef32f449a94eec49cdf005c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,366 @@
|
||||
/*----------------------------------------------------------------
|
||||
// Copyright (C) 2025 Beijing All rights reserved.
|
||||
//
|
||||
// Author: huachangmiao
|
||||
// Create Date: 2025/04/11
|
||||
// Module Describe: UI RawImage 视频渲染组件
|
||||
//----------------------------------------------------------------*/
|
||||
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// UI RawImage 视频渲染组件
|
||||
/// 继承自 LeviathanVideoDecoderBase,用于在 UI RawImage 上显示视频
|
||||
/// </summary>
|
||||
///
|
||||
[RequireComponent(typeof(RawImage))]
|
||||
public unsafe class LeviathanVideoDecoder : LeviathanVideoDecoderBase
|
||||
{
|
||||
private static readonly int ChromaKeyColorId = Shader.PropertyToID("_KeyColor");
|
||||
private static readonly int ChromaKeyCutoffId = Shader.PropertyToID("_ColorCutoff");
|
||||
private static readonly int ChromaKeyColorFeatheringId = Shader.PropertyToID("_ColorFeathering");
|
||||
private static readonly int ChromaKeyMaskFeatheringId = Shader.PropertyToID("_MaskFeathering");
|
||||
private static readonly int ChromaKeySharpeningId = Shader.PropertyToID("_Sharpening");
|
||||
|
||||
private Material ChromaKeyMaterial
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_alphaType != PlayAlphaType.ChromaKey) return null;
|
||||
var rawImage = GetComponent<RawImage>();
|
||||
if (rawImage == null || rawImage.material == null) return null;
|
||||
return rawImage.material.HasProperty(ChromaKeyColorId) ? rawImage.material : null;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetChromaKeyColor(Color color)
|
||||
{
|
||||
var mat = ChromaKeyMaterial;
|
||||
if (mat != null) mat.SetColor(ChromaKeyColorId, color);
|
||||
}
|
||||
|
||||
public Color GetChromaKeyColor()
|
||||
{
|
||||
var mat = ChromaKeyMaterial;
|
||||
return mat != null ? mat.GetColor(ChromaKeyColorId) : Color.green;
|
||||
}
|
||||
|
||||
public void SetChromaKeyCutoff(float value)
|
||||
{
|
||||
var mat = ChromaKeyMaterial;
|
||||
if (mat != null) mat.SetFloat(ChromaKeyCutoffId, Mathf.Clamp01(value));
|
||||
}
|
||||
|
||||
public float GetChromaKeyCutoff()
|
||||
{
|
||||
var mat = ChromaKeyMaterial;
|
||||
return mat != null ? mat.GetFloat(ChromaKeyCutoffId) : 0.2f;
|
||||
}
|
||||
|
||||
public void SetChromaKeyColorFeathering(float value)
|
||||
{
|
||||
var mat = ChromaKeyMaterial;
|
||||
if (mat != null) mat.SetFloat(ChromaKeyColorFeatheringId, Mathf.Clamp01(value));
|
||||
}
|
||||
|
||||
public float GetChromaKeyColorFeathering()
|
||||
{
|
||||
var mat = ChromaKeyMaterial;
|
||||
return mat != null ? mat.GetFloat(ChromaKeyColorFeatheringId) : 0.33f;
|
||||
}
|
||||
|
||||
public void SetChromaKeyMaskFeathering(float value)
|
||||
{
|
||||
var mat = ChromaKeyMaterial;
|
||||
if (mat != null) mat.SetFloat(ChromaKeyMaskFeatheringId, Mathf.Clamp01(value));
|
||||
}
|
||||
|
||||
public float GetChromaKeyMaskFeathering()
|
||||
{
|
||||
var mat = ChromaKeyMaterial;
|
||||
return mat != null ? mat.GetFloat(ChromaKeyMaskFeatheringId) : 1f;
|
||||
}
|
||||
|
||||
public void SetChromaKeySharpening(float value)
|
||||
{
|
||||
var mat = ChromaKeyMaterial;
|
||||
if (mat != null) mat.SetFloat(ChromaKeySharpeningId, Mathf.Clamp01(value));
|
||||
}
|
||||
|
||||
public float GetChromaKeySharpening()
|
||||
{
|
||||
var mat = ChromaKeyMaterial;
|
||||
return mat != null ? mat.GetFloat(ChromaKeySharpeningId) : 0.5f;
|
||||
}
|
||||
|
||||
public void SetChromaKeyParams(Color keyColor, float cutoff, float colorFeathering, float maskFeathering, float sharpening)
|
||||
{
|
||||
var mat = ChromaKeyMaterial;
|
||||
if (mat == null) return;
|
||||
mat.SetColor(ChromaKeyColorId, keyColor);
|
||||
mat.SetFloat(ChromaKeyCutoffId, Mathf.Clamp01(cutoff));
|
||||
mat.SetFloat(ChromaKeyColorFeatheringId, Mathf.Clamp01(colorFeathering));
|
||||
mat.SetFloat(ChromaKeyMaskFeatheringId, Mathf.Clamp01(maskFeathering));
|
||||
mat.SetFloat(ChromaKeySharpeningId, Mathf.Clamp01(sharpening));
|
||||
}
|
||||
|
||||
private new static void Log(string message)
|
||||
{
|
||||
LeviathanLogConfig.Log("VideoDecoder", message);
|
||||
}
|
||||
|
||||
private new static void LogWarning(string message)
|
||||
{
|
||||
LeviathanLogConfig.LogWarning("VideoDecoder", message);
|
||||
}
|
||||
|
||||
private new static void LogError(string message)
|
||||
{
|
||||
LeviathanLogConfig.LogError("VideoDecoder", message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 启用渲染目标
|
||||
/// </summary>
|
||||
protected override void OnEnableRenderTarget()
|
||||
{
|
||||
RawImage rawImage = GetComponent<RawImage>();
|
||||
if (rawImage)
|
||||
{
|
||||
rawImage.enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 禁用渲染目标
|
||||
/// </summary>
|
||||
protected override void OnDisableRenderTarget()
|
||||
{
|
||||
RawImage rawImage = GetComponent<RawImage>();
|
||||
if (rawImage)
|
||||
{
|
||||
rawImage.enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新渲染目标尺寸
|
||||
/// </summary>
|
||||
protected override void OnUpdateRenderTargetSize()
|
||||
{
|
||||
RawImage rawImage = GetComponent<RawImage>();
|
||||
if (rawImage)
|
||||
{
|
||||
UpdateImageSize(rawImage);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 初始化材质
|
||||
/// </summary>
|
||||
protected override void OnInitMaterial()
|
||||
{
|
||||
RawImage rawImage = GetComponent<RawImage>();
|
||||
if (rawImage)
|
||||
{
|
||||
rawImage.texture = _textures[0];
|
||||
UpdateImageMaterial(rawImage);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新材质
|
||||
/// </summary>
|
||||
protected override void OnUpdateMaterial()
|
||||
{
|
||||
RawImage rawImage = GetComponent<RawImage>();
|
||||
if (rawImage)
|
||||
{
|
||||
UpdateImageMaterial(rawImage);
|
||||
}
|
||||
}
|
||||
|
||||
protected void UpdateImageSize(RawImage rawImage)
|
||||
{
|
||||
if (!disableAutoSize)
|
||||
{
|
||||
var videoFrame = VideoFrame;
|
||||
if (videoFrame != null && videoFrame->width > 0)
|
||||
{
|
||||
rawImage.SetNativeSize();
|
||||
rawImage.rectTransform.sizeDelta = _alphaType switch
|
||||
{
|
||||
PlayAlphaType.RightSide => new Vector2(videoFrame->width * 0.5f, videoFrame->height),
|
||||
PlayAlphaType.BottomSide => new Vector2(videoFrame->width, videoFrame->height * 0.5f),
|
||||
_ => new Vector2(videoFrame->width, videoFrame->height)
|
||||
};
|
||||
}
|
||||
}
|
||||
rawImage.uvRect = new Rect(0, 0, _alphaType == PlayAlphaType.RightSide ? 1 : (float)_videoValidWidth / _videoWidth, 1);
|
||||
}
|
||||
|
||||
protected void UpdateImageMaterial(RawImage rawImage)
|
||||
{
|
||||
// 软件解码器使用YUV纹理,需要YUV420P着色器
|
||||
Material newMaterial;
|
||||
switch (_alphaType)
|
||||
{
|
||||
case PlayAlphaType.RightSide:
|
||||
newMaterial = new Material(Shader.Find("LeviathanVideo/YUV420P_Alpha_Right"));
|
||||
newMaterial.SetFloat(ValidWidthRatioIdx, (float)_videoValidWidth / _videoWidth);
|
||||
break;
|
||||
case PlayAlphaType.BottomSide:
|
||||
newMaterial = new Material(Shader.Find("LeviathanVideo/YUV420P_Alpha_Bottom"));
|
||||
break;
|
||||
case PlayAlphaType.ChromaKey:
|
||||
newMaterial = new Material(Shader.Find("LeviathanVideo/YUV420P_ChromaKey"));
|
||||
break;
|
||||
case PlayAlphaType.None:
|
||||
default:
|
||||
newMaterial = new Material(Shader.Find("LeviathanVideo/YUV420P"));
|
||||
break;
|
||||
}
|
||||
newMaterial.SetTexture(UTexIdx, _textures[1]);
|
||||
newMaterial.SetTexture(VTexIdx, _textures[2]);
|
||||
rawImage.material = newMaterial;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
private void OnValidate()
|
||||
{
|
||||
// 使用 delayCall 避免在 OnValidate 中调用 SendMessage 相关操作
|
||||
var currentBytesFile = bytesFile;
|
||||
var lastBytesFile = _lastBytesFile;
|
||||
UnityEditor.EditorApplication.delayCall += () =>
|
||||
{
|
||||
if (this == null) return;
|
||||
|
||||
SetAlphaType(_alphaType);
|
||||
|
||||
// 检测 bytesFile 是否发生变化
|
||||
if (currentBytesFile != lastBytesFile)
|
||||
{
|
||||
_lastBytesFile = currentBytesFile;
|
||||
if (IsPlaying && currentBytesFile != null)
|
||||
{
|
||||
Log($"编辑器中检测到 bytesFile 变化,自动播放新视频");
|
||||
Stop();
|
||||
Play();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
[CustomEditor(typeof(LeviathanVideoDecoder))]
|
||||
public class LeviathanVideoDecoderEditor : Editor
|
||||
{
|
||||
// 格式化时间:秒转换为 HH:MM:SS.mmm 格式
|
||||
private static string FormatTimeSeconds(double totalSeconds)
|
||||
{
|
||||
int hours = (int)(totalSeconds / 3600);
|
||||
int minutes = (int)((totalSeconds % 3600) / 60);
|
||||
int seconds = (int)(totalSeconds % 60);
|
||||
int milliseconds = (int)((totalSeconds % 1) * 1000);
|
||||
return $"{hours:D2}:{minutes:D2}:{seconds:D2}.{milliseconds:D3}";
|
||||
}
|
||||
|
||||
// 格式化时间:微秒转换为 HH:MM:SS.mmm 格式
|
||||
private static string FormatTimeMicroseconds(long microseconds)
|
||||
{
|
||||
return FormatTimeSeconds(microseconds / 1000000.0);
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
DrawDefaultInspector();
|
||||
var mono = (LeviathanVideoDecoder)target;
|
||||
|
||||
// Display video dimensions as read-only fields
|
||||
EditorGUI.BeginDisabledGroup(true);
|
||||
EditorGUILayout.TextField("帧数", $"{mono.PlayFrameIndex} / {mono.FrameCount}");
|
||||
// FrameInterval 单位是秒,Duration 单位是微秒
|
||||
EditorGUILayout.TextField("播放时间", FormatTimeSeconds(mono.PlayFrameIndex * mono.FrameInterval));
|
||||
EditorGUILayout.TextField("总时间", FormatTimeMicroseconds(mono.Duration));
|
||||
EditorGUILayout.TextField("FPS", mono.FrameInterval > 0 ? $"{1.0 / mono.FrameInterval:F2}" : "0");
|
||||
EditorGUILayout.TextField("纹理尺寸", $"{mono.VideoWidth} × {mono.VideoHeight}");
|
||||
EditorGUILayout.TextField("视频有效尺寸", $"{mono.VideoValidWidth} × {mono.VideoHeight}");
|
||||
EditorGUI.EndDisabledGroup();
|
||||
|
||||
{
|
||||
bool oldValue = mono.IsPlaying;
|
||||
// 绘制checkbox
|
||||
bool newValue = EditorGUILayout.Toggle("isPlaying", oldValue);
|
||||
|
||||
// 如果用户切换了checkbox
|
||||
if (oldValue != newValue)
|
||||
{
|
||||
if (newValue)
|
||||
{
|
||||
mono.Play();
|
||||
}
|
||||
else
|
||||
{
|
||||
mono.Stop();
|
||||
}
|
||||
EditorUtility.SetDirty(target);
|
||||
}
|
||||
}
|
||||
{
|
||||
bool oldValue = mono.IsPause;
|
||||
// 绘制checkbox
|
||||
bool newValue = EditorGUILayout.Toggle("isPause", oldValue);
|
||||
|
||||
// 如果用户切换了checkbox
|
||||
if (oldValue != newValue)
|
||||
{
|
||||
if (newValue)
|
||||
{
|
||||
mono.Pause();
|
||||
}
|
||||
else
|
||||
{
|
||||
mono.Resume();
|
||||
}
|
||||
EditorUtility.SetDirty(target);
|
||||
}
|
||||
}
|
||||
|
||||
if (mono.AlphaType == LeviathanVideoDecoderBase.PlayAlphaType.ChromaKey)
|
||||
{
|
||||
DrawChromaKeyParameters(mono);
|
||||
}
|
||||
|
||||
this.Repaint();
|
||||
}
|
||||
|
||||
private void DrawChromaKeyParameters(LeviathanVideoDecoder mono)
|
||||
{
|
||||
var rawImage = mono.GetComponent<RawImage>();
|
||||
if (rawImage == null || rawImage.material == null) return;
|
||||
if (!rawImage.material.HasProperty(Shader.PropertyToID("_KeyColor"))) return;
|
||||
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.LabelField("ChromaKey 参数", EditorStyles.boldLabel);
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
|
||||
Color keyColor = EditorGUILayout.ColorField("KeyColor", mono.GetChromaKeyColor());
|
||||
float cutoff = EditorGUILayout.Slider("Cutoff", mono.GetChromaKeyCutoff(), 0f, 1f);
|
||||
float colorFeathering = EditorGUILayout.Slider("ColorFeathering", mono.GetChromaKeyColorFeathering(), 0f, 1f);
|
||||
float maskFeathering = EditorGUILayout.Slider("MaskFeathering", mono.GetChromaKeyMaskFeathering(), 0f, 1f);
|
||||
float sharpening = EditorGUILayout.Slider("Sharpening", mono.GetChromaKeySharpening(), 0f, 1f);
|
||||
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
mono.SetChromaKeyParams(keyColor, cutoff, colorFeathering, maskFeathering, sharpening);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 56ffe7905c6ea984184ed1b60a985acd
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,927 @@
|
||||
/*----------------------------------------------------------------
|
||||
// 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
|
||||
|
||||
/// <summary>
|
||||
/// 视频解码器抽象基类
|
||||
/// 包含解码逻辑、播放控制、多线程解码等公共功能
|
||||
/// 子类需要实现具体的渲染逻辑
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 静态构造函数 - 在类首次使用时执行平台检测
|
||||
/// </summary>
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前视频帧指针(子类使用)
|
||||
/// </summary>
|
||||
protected LeviathanVideo.Abstractions.AVFrame* VideoFrame => _decoder != null ? _decoder.VideoFrame : null;
|
||||
|
||||
#if !UNITY_2022_1_OR_NEWER
|
||||
// Unity 2020/2021 兼容:保存数据副本以供解码器使用
|
||||
protected NativeArray<byte> _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;
|
||||
|
||||
/// <summary>
|
||||
/// 播放完成回调事件(非循环播放时触发)
|
||||
/// </summary>
|
||||
public System.Action OnPlayCompleted;
|
||||
|
||||
/// <summary>
|
||||
/// 首帧渲染完毕回调事件
|
||||
/// 无论同步或异步解码首帧模式都会触发
|
||||
/// </summary>
|
||||
public System.Action OnFirstFrameRendered;
|
||||
|
||||
[SerializeField]
|
||||
protected PlayAlphaType _alphaType = PlayAlphaType.None;
|
||||
|
||||
// 上次应用的alphaType,用于检测是否需要更新材质
|
||||
protected PlayAlphaType _lastAppliedAlphaType = PlayAlphaType.None;
|
||||
|
||||
protected virtual void Start()
|
||||
{
|
||||
if (autoPlayOnStart && bytesFile)
|
||||
Play();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 第一帧解码完成回调(由解码器触发,可能在子线程中调用)
|
||||
/// 设置标志位,实际处理在主线程的 Update 中执行
|
||||
/// </summary>
|
||||
protected virtual void OnFirstFrameDecodedCallback()
|
||||
{
|
||||
_pendingFirstFrameCallback = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理第一帧解码完成的实际逻辑(必须在主线程调用)
|
||||
/// 子类需要重写此方法来处理渲染目标的显示
|
||||
/// </summary>
|
||||
protected virtual void HandleFirstFrameDecoded()
|
||||
{
|
||||
// 先更新纹理,确保显示的是新视频的第一帧
|
||||
DisplayFrame();
|
||||
|
||||
// 子类负责启用渲染目标
|
||||
OnEnableRenderTarget();
|
||||
|
||||
// 触发首帧渲染完毕回调
|
||||
OnFirstFrameRendered?.Invoke();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 启用渲染目标(子类实现)
|
||||
/// </summary>
|
||||
protected abstract void OnEnableRenderTarget();
|
||||
|
||||
/// <summary>
|
||||
/// 禁用渲染目标(子类实现)
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 播放视频
|
||||
/// </summary>
|
||||
/// <param name="fileData">视频文件</param>
|
||||
/// <param name="playAlpha">播放透明度</param>
|
||||
/// <param name="multithreadedDecode">是否多线程解码</param>
|
||||
/// <param name="beginFrameIndex">开始播放的帧索引</param>
|
||||
/// <param name="syncDecodeFirstFrame">是否同步解码第一帧, 区别在于是否当前帧就能立马看到视频, 会耗时10ms</param>
|
||||
/// <param name="fastDecode">是否启用快速解码(跳过环路滤波器),默认开启以提升性能</param>
|
||||
/// <returns>返回值: 0表示成功, 其他表示错误码</returns>
|
||||
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<byte>(), beginFrameIndex, syncDecodeFirstFrame, fastDecode);
|
||||
#else
|
||||
// Unity 2020/2021: 创建持久的数据副本供解码器使用
|
||||
if (_hasVideoDataBuffer)
|
||||
{
|
||||
_videoDataBuffer.Dispose();
|
||||
}
|
||||
_videoDataBuffer = new NativeArray<byte>(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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新渲染目标尺寸(子类实现)
|
||||
/// </summary>
|
||||
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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 初始化材质(子类实现)
|
||||
/// </summary>
|
||||
protected abstract void OnInitMaterial();
|
||||
|
||||
/// <summary>
|
||||
/// 更新纹理
|
||||
/// </summary>
|
||||
/// <param name="applyImmediately">是否立即Apply上传到GPU。false时只复制数据,可以稍后调用DisplayFrameApply</param>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将纹理数据上传到GPU(可与下一帧解码并行)
|
||||
/// 配合 DisplayFrame(false) 使用,实现分步显示优化
|
||||
/// </summary>
|
||||
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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新材质(子类实现)
|
||||
/// </summary>
|
||||
protected abstract void OnUpdateMaterial();
|
||||
|
||||
public void SetLooping(bool isLooping)
|
||||
{
|
||||
_isLooping = isLooping;
|
||||
|
||||
// 如果是微信小游戏平台,设置解码器的循环播放状态
|
||||
if (IsWCGame)
|
||||
{
|
||||
_decoder.IsLooping = isLooping;
|
||||
}
|
||||
|
||||
if (_isLooping && _isPause)
|
||||
{
|
||||
Resume();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置循环播放区间, 帧数范围为 1 ~ FrameCount
|
||||
/// </summary>
|
||||
/// <param name="beginFrame">开始帧索引</param>
|
||||
/// <param name="endFrame">结束帧索引</param>
|
||||
public void SetLoopingFrame(long beginFrame, long endFrame)
|
||||
{
|
||||
_loopBeginFrameIndex = beginFrame;
|
||||
_loopEndFrameIndex = endFrame;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 切换视频
|
||||
/// </summary>
|
||||
/// <param name="fileData">视频文件</param>
|
||||
/// <param name="playAlpha">播放透明度</param>
|
||||
/// <param name="multithreadedDecode">是否多线程解码</param>
|
||||
/// <param name="beginFrameIndex">开始播放的帧索引</param>
|
||||
/// <param name="syncDecodeFirstFrame">是否同步解码第一帧, 区别在于是否当前帧就能立马看到视频, 会耗时10ms</param>
|
||||
/// <param name="fastDecode">是否启用快速解码(跳过环路滤波器),默认开启以提升性能</param>
|
||||
/// <returns>返回值: true表示成功, false表示失败</returns>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5515e1e1db39d4348a3a57571c7bc460
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: edd271292fe97374fa02eaba7183c305
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,590 @@
|
||||
/*----------------------------------------------------------------
|
||||
// Copyright (C) 2025 Beijing All rights reserved.
|
||||
//
|
||||
// Author: huachangmiao
|
||||
// Create Date: 2025/04/11
|
||||
// Module Describe:
|
||||
//----------------------------------------------------------------*/
|
||||
|
||||
//******************************************************************
|
||||
// CPU软解码
|
||||
// 适用平台
|
||||
// Editor
|
||||
// Windows
|
||||
// MacOS
|
||||
// Android
|
||||
// iOS
|
||||
// WebGL
|
||||
//******************************************************************
|
||||
|
||||
// #define ENABLE_COPY_NATIVE_DATA //复制Native数据到托管内存
|
||||
|
||||
#if UNITY_WEBGL
|
||||
#else
|
||||
#define ENABLE_AV_MALLOC //使用AV_MALLOC分配内存
|
||||
#endif
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using LeviathanVideo.Abstractions;
|
||||
using LeviathanVideo.Bindings.Linked;
|
||||
using Unity.Collections;
|
||||
using Unity.Collections.LowLevel.Unsafe;
|
||||
using UnityEngine;
|
||||
using System.Runtime.InteropServices;
|
||||
using AOT;
|
||||
|
||||
internal unsafe class LeviathanSoftwareDecoder : ILeviathanDecoder
|
||||
{
|
||||
// 日志辅助方法
|
||||
private static void Log(string message)
|
||||
{
|
||||
LeviathanLogConfig.Log("SoftwareDecoder", message);
|
||||
}
|
||||
|
||||
private static void LogWarning(string message)
|
||||
{
|
||||
LeviathanLogConfig.LogWarning("SoftwareDecoder", message);
|
||||
}
|
||||
|
||||
private static void LogError(string message)
|
||||
{
|
||||
LeviathanLogConfig.LogError("SoftwareDecoder", message);
|
||||
}
|
||||
|
||||
// 事件实现
|
||||
public event Action OnFirstFrameDecoded;
|
||||
private AVFormatContext* _pFormatContext;
|
||||
private AVCodecContext* _pVideoContext;
|
||||
|
||||
private int _videoStreamIndex;
|
||||
private AVFrame* _videoFrame;
|
||||
private AVPacket* _packet;
|
||||
|
||||
private AVIOContext* _pAvioCtx;
|
||||
private byte* _avioCtxBuffer;
|
||||
private BufferData _bufferData;
|
||||
private GCHandle _bdHandle;
|
||||
|
||||
#if ENABLE_AV_MALLOC
|
||||
#else
|
||||
private GCHandle _hAvioBuffer;
|
||||
#endif
|
||||
#if ENABLE_COPY_NATIVE_DATA
|
||||
private IntPtr _unmanagedPtr;
|
||||
#endif
|
||||
|
||||
// 私有字段
|
||||
private long _frameCount;
|
||||
private double _frameInterval;
|
||||
private string _codecName = "";
|
||||
private long _duration;
|
||||
private long _decodedFrameIndex;
|
||||
private bool _firstFrameDecoded = false;
|
||||
|
||||
// 线程安全锁 - 保护 Seek 和 Decode 操作不并发执行
|
||||
private readonly object _codecLock = new object();
|
||||
|
||||
// 实现接口属性
|
||||
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; set; } = false;
|
||||
|
||||
// 快速解码模式(跳过环路滤波器)
|
||||
private bool _fastDecode = true;
|
||||
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct BufferData
|
||||
{
|
||||
public byte* ptr;
|
||||
public byte* originPtr;
|
||||
public ulong size;
|
||||
public ulong totalSize;
|
||||
}
|
||||
|
||||
[MonoPInvokeCallback(typeof(avio_alloc_context_read_packet))]
|
||||
private static int ReadPacket(void* opaque, byte* buf, int bufSize)
|
||||
{
|
||||
BufferData* bd = (BufferData*)opaque;
|
||||
bufSize = (int)Math.Min(bd->size, (ulong)bufSize);
|
||||
|
||||
if (bufSize <= 0)
|
||||
return leviathan.AVERROR_EOF;
|
||||
|
||||
// Buffer.MemoryCopy(bd->ptr, buf, bufSize, bufSize);
|
||||
UnsafeUtility.MemCpy(buf, bd->ptr, bufSize);
|
||||
bd->ptr += bufSize;
|
||||
bd->size -= (ulong)bufSize;
|
||||
|
||||
return bufSize;
|
||||
}
|
||||
|
||||
[MonoPInvokeCallback(typeof(avio_alloc_context_seek))]
|
||||
private static long SeekPacket(void* opaque, long offset, int whence)
|
||||
{
|
||||
BufferData* bd = (BufferData*)opaque;
|
||||
switch (whence)
|
||||
{
|
||||
case 0: //SEEK_SET
|
||||
if (offset < 0 || (ulong)offset > bd->totalSize)
|
||||
return leviathan.AVERROR_Enum(leviathan.EINVAL);
|
||||
bd->ptr = bd->originPtr + offset;
|
||||
bd->size = bd->totalSize - (ulong)offset;
|
||||
return offset;
|
||||
|
||||
case 1: //SEEK_CUR
|
||||
long newPos = bd->ptr - bd->originPtr + offset;
|
||||
if (newPos < 0 || (ulong)newPos > bd->totalSize)
|
||||
return leviathan.AVERROR_Enum(leviathan.EINVAL);
|
||||
bd->ptr += offset;
|
||||
bd->size = bd->totalSize - (ulong)newPos;
|
||||
return newPos;
|
||||
|
||||
case 2: //SEEK_END
|
||||
long endPos = (long)bd->totalSize + offset;
|
||||
if (endPos < 0 || endPos > (long)bd->totalSize)
|
||||
return leviathan.AVERROR_Enum(leviathan.EINVAL);
|
||||
bd->ptr = bd->originPtr + endPos;
|
||||
bd->size = bd->totalSize - (ulong)endPos;
|
||||
return endPos;
|
||||
|
||||
default:
|
||||
return leviathan.AVERROR_Enum(leviathan.EINVAL);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public int Init(NativeArray<byte> fileData, int beginFrameIndex = 1, bool syncDecodeFirstFrame = true, bool fastDecode = true)
|
||||
{
|
||||
LinkedBindings.Initialize();
|
||||
_fastDecode = fastDecode;
|
||||
var ret = 0;
|
||||
var src = fileData.GetUnsafeReadOnlyPtr();
|
||||
|
||||
#if ENABLE_COPY_NATIVE_DATA
|
||||
_unmanagedPtr = Marshal.AllocHGlobal(fileData.Length);
|
||||
var dst = _unmanagedPtr.ToPointer();
|
||||
UnsafeUtility.MemCpy(dst, src, fileData.Length);
|
||||
src = dst;
|
||||
#endif
|
||||
|
||||
ulong bufferSize = (ulong)fileData.Length;
|
||||
|
||||
_bufferData = new BufferData
|
||||
{
|
||||
originPtr = (byte*)src, // 初始化原始指针
|
||||
ptr = (byte*)src,
|
||||
size = bufferSize,
|
||||
totalSize = bufferSize
|
||||
};
|
||||
_bdHandle = GCHandle.Alloc(_bufferData, GCHandleType.Pinned);
|
||||
|
||||
avio_alloc_context_read_packet readCallback = ReadPacket;
|
||||
avio_alloc_context_seek seekCallback = SeekPacket;
|
||||
|
||||
// 为AVIOContext分配缓冲区
|
||||
const int avioCtxBufferSize = 4096;
|
||||
#if ENABLE_AV_MALLOC
|
||||
_avioCtxBuffer = (byte*)leviathan.av_malloc(avioCtxBufferSize);
|
||||
|
||||
// 分配AVIOContext并将其与AVFormatContext关联
|
||||
_pAvioCtx = leviathan.avio_alloc_context(
|
||||
_avioCtxBuffer,
|
||||
avioCtxBufferSize,
|
||||
0, // 不可写
|
||||
(void*)_bdHandle.AddrOfPinnedObject(),
|
||||
readCallback,
|
||||
null, // 无写回调
|
||||
seekCallback
|
||||
);
|
||||
#else
|
||||
// wasm无法直接使用av_malloc
|
||||
int alignment = 64;
|
||||
byte[] avioBufferManaged = new byte[avioCtxBufferSize + alignment - 1];
|
||||
_hAvioBuffer = GCHandle.Alloc(avioBufferManaged, GCHandleType.Pinned);
|
||||
IntPtr avioBufferPtr = _hAvioBuffer.AddrOfPinnedObject();
|
||||
// 调整指针到对齐地址
|
||||
IntPtr alignedPtr = new IntPtr((avioBufferPtr.ToInt64() + alignment - 1) & ~(alignment - 1));
|
||||
|
||||
// 分配AVIOContext并将其与AVFormatContext关联
|
||||
_pAvioCtx = leviathan.avio_alloc_context(
|
||||
(byte*)alignedPtr,
|
||||
avioCtxBufferSize,
|
||||
0, // 不可写
|
||||
(void*)_bdHandle.AddrOfPinnedObject(),
|
||||
readCallback,
|
||||
null, // 无写回调
|
||||
seekCallback
|
||||
);
|
||||
#endif
|
||||
if (_pAvioCtx == null)
|
||||
{
|
||||
ret = leviathan.AVERROR_Enum(leviathan.ENOMEM);
|
||||
LogError("无法分配AVIO上下文");
|
||||
return ret;
|
||||
}
|
||||
|
||||
_pFormatContext = leviathan.avformat_alloc_context();
|
||||
_pFormatContext->pb = _pAvioCtx;
|
||||
var pFormatContext = _pFormatContext;
|
||||
ret = leviathan.avformat_open_input(&pFormatContext, null, null, null);
|
||||
if (ret != 0)
|
||||
{
|
||||
LogError($"avformat_open_input failed: {ret}");
|
||||
return ret;
|
||||
}
|
||||
if (_pFormatContext == null)
|
||||
{
|
||||
return ret;
|
||||
}
|
||||
|
||||
return InternalPlay(beginFrameIndex, syncDecodeFirstFrame);
|
||||
}
|
||||
|
||||
public int InternalPlay(int beginFrameIndex = 1, bool syncDecodeFirstFrame = true)
|
||||
{
|
||||
leviathan.avformat_find_stream_info(_pFormatContext, null);
|
||||
AVCodec* videoCodec = null;
|
||||
_videoStreamIndex = leviathan.av_find_best_stream(_pFormatContext, AVMediaType.AVMEDIA_TYPE_VIDEO, -1, -1, &videoCodec, 0);
|
||||
// 获取视频流信息
|
||||
AVStream* videoStream = _pFormatContext->streams[_videoStreamIndex];
|
||||
// 计算帧间隔
|
||||
AVRational frameRate = videoStream->r_frame_rate;
|
||||
double fps = leviathan.av_q2d(frameRate);
|
||||
_frameInterval = 1.0 / fps;
|
||||
|
||||
_pVideoContext = leviathan.avcodec_alloc_context3(videoCodec);
|
||||
|
||||
#if UNITY_EDITOR
|
||||
_codecName = leviathan.avcodec_get_name(videoCodec->id);
|
||||
#endif
|
||||
if (videoCodec == null)
|
||||
{
|
||||
LogError("videoCodec is null");
|
||||
return 1;
|
||||
}
|
||||
_pVideoContext->thread_count = 0;
|
||||
_pVideoContext->thread_type = leviathan.FF_THREAD_FRAME | leviathan.FF_THREAD_SLICE;
|
||||
|
||||
// 根据 fastDecode 参数决定是否跳过环路滤波器以提升解码速度
|
||||
if (_fastDecode)
|
||||
{
|
||||
_pVideoContext->skip_loop_filter = (AVDiscard)48; //AVDISCARD_ALL
|
||||
}
|
||||
|
||||
// Debug.Log($"{_pVideoContext->codec_id} {_frameInterval} {_pFormatContext->duration}");
|
||||
leviathan.avcodec_open2(_pVideoContext, videoCodec, null);
|
||||
|
||||
_duration = _pFormatContext->duration;
|
||||
|
||||
if (_pFormatContext->streams[_videoStreamIndex]->nb_frames > 0)
|
||||
{
|
||||
_frameCount = _pFormatContext->streams[_videoStreamIndex]->nb_frames;
|
||||
}
|
||||
else
|
||||
{
|
||||
var dur = _duration / 1000.0 * leviathan.av_q2d(videoStream->time_base);
|
||||
int frNum = frameRate.num;
|
||||
int frDen = frameRate.den;
|
||||
if (dur > 0 && frNum > 0 && frDen > 0)
|
||||
{
|
||||
_frameCount = (long)(dur * frNum / frDen);
|
||||
}
|
||||
}
|
||||
// Debug.Log($"frameCount: {_frameCount}");
|
||||
|
||||
_videoFrame = leviathan.av_frame_alloc();
|
||||
_packet = leviathan.av_packet_alloc();
|
||||
|
||||
if (beginFrameIndex > 1)
|
||||
{
|
||||
SeekToFrame(beginFrameIndex);
|
||||
}
|
||||
|
||||
if (syncDecodeFirstFrame)
|
||||
{
|
||||
// 第一帧同步解码
|
||||
int ret = DecodeNextFrame();
|
||||
if (ret != 0)
|
||||
{
|
||||
LogError($"DecodeNextFrame ret: {ret}");
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
public int DecodeNextFrame()
|
||||
{
|
||||
lock (_codecLock)
|
||||
{
|
||||
if (_pVideoContext == null || _pFormatContext == null)
|
||||
return -1;
|
||||
|
||||
return DecodeNextFrameInternal();
|
||||
}
|
||||
}
|
||||
|
||||
private int DecodeNextFrameInternal()
|
||||
{
|
||||
var flushSent = false;
|
||||
while (true)
|
||||
{
|
||||
var error = leviathan.avcodec_receive_frame(_pVideoContext, _videoFrame);
|
||||
if (error == 0)
|
||||
{
|
||||
long frameIndex = 1;
|
||||
if (_frameInterval > 0)
|
||||
{
|
||||
var timeBase = _pFormatContext->streams[_videoStreamIndex]->time_base;
|
||||
double frameTime = _videoFrame->pts * leviathan.av_q2d(timeBase);
|
||||
frameIndex = (long)(frameTime / _frameInterval + 0.1) + 1;
|
||||
}
|
||||
Interlocked.Exchange(ref _decodedFrameIndex, frameIndex);
|
||||
|
||||
// 触发第一帧回调
|
||||
if (!_firstFrameDecoded)
|
||||
{
|
||||
_firstFrameDecoded = true;
|
||||
OnFirstFrameDecoded?.Invoke();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (error == leviathan.AVERROR_Enum(leviathan.EAGAIN))
|
||||
{
|
||||
if (flushSent)
|
||||
{
|
||||
// 已发送过flush,无更多数据
|
||||
return 1;
|
||||
}
|
||||
|
||||
error = leviathan.av_read_frame(_pFormatContext, _packet);
|
||||
if (error < 0)
|
||||
{
|
||||
if (error == leviathan.AVERROR_EOF)
|
||||
{
|
||||
// 发送flush packet以取出解码器中剩余帧
|
||||
error = leviathan.avcodec_send_packet(_pVideoContext, null);
|
||||
flushSent = true;
|
||||
if (error < 0 && error != leviathan.AVERROR_Enum(leviathan.EAGAIN))
|
||||
{
|
||||
return 2;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 其他错误,继续尝试读取
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (_packet->stream_index == _videoStreamIndex)
|
||||
{
|
||||
error = leviathan.avcodec_send_packet(_pVideoContext, _packet);
|
||||
if (error < 0)
|
||||
{
|
||||
leviathan.av_packet_unref(_packet);
|
||||
if (error == leviathan.AVERROR_Enum(leviathan.EAGAIN))
|
||||
{
|
||||
// 解码器需要先输出帧,继续循环处理
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
return 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
leviathan.av_packet_unref(_packet);
|
||||
}
|
||||
else if (error == leviathan.AVERROR_EOF)
|
||||
{
|
||||
// 解码器已无更多帧
|
||||
return 4;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 其他错误
|
||||
return error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void InternalStop()
|
||||
{
|
||||
lock (_codecLock)
|
||||
{
|
||||
// Release resources
|
||||
var pVideoContext = _pVideoContext;
|
||||
var pFormatContext = _pFormatContext;
|
||||
var videoFrame = _videoFrame;
|
||||
var packet = _packet;
|
||||
if (_pVideoContext != null) leviathan.avcodec_free_context(&pVideoContext);
|
||||
if (_pFormatContext != null) leviathan.avformat_close_input(&pFormatContext);
|
||||
if (_videoFrame != null) leviathan.av_frame_free(&videoFrame);
|
||||
if (_packet != null) leviathan.av_packet_free(&packet);
|
||||
|
||||
_pVideoContext = null;
|
||||
_pFormatContext = null;
|
||||
_videoFrame = null;
|
||||
_packet = null;
|
||||
}
|
||||
|
||||
if (_pAvioCtx != null)
|
||||
{
|
||||
#if ENABLE_AV_MALLOC
|
||||
leviathan.av_free(_pAvioCtx->buffer);
|
||||
#else
|
||||
_pAvioCtx->buffer = null; // 关键:阻止 FFmpeg 调用 av_free
|
||||
if (_hAvioBuffer.IsAllocated)
|
||||
{
|
||||
_hAvioBuffer.Free();
|
||||
}
|
||||
#endif
|
||||
var pAvioCtx = _pAvioCtx;
|
||||
leviathan.avio_context_free(&pAvioCtx);
|
||||
_pAvioCtx = null;
|
||||
}
|
||||
|
||||
if (_bdHandle.IsAllocated)
|
||||
{
|
||||
_bdHandle.Free();
|
||||
}
|
||||
_bdHandle = default;
|
||||
#if ENABLE_COPY_NATIVE_DATA
|
||||
if (_unmanagedPtr != IntPtr.Zero)
|
||||
{
|
||||
Marshal.FreeHGlobal(_unmanagedPtr);
|
||||
}
|
||||
_unmanagedPtr = default;
|
||||
#endif
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
InternalStop();
|
||||
}
|
||||
|
||||
// 跳转到指定时间点
|
||||
public void Seek(double ms)
|
||||
{
|
||||
lock (_codecLock)
|
||||
{
|
||||
if (_pVideoContext == null || _pFormatContext == null)
|
||||
return;
|
||||
|
||||
long targetFrame = (long)(_frameInterval * ms);
|
||||
// Debug.Log("Seek to " + targetFrame + "(" + ms + "ms)");
|
||||
leviathan.avformat_seek_file(_pFormatContext, _videoStreamIndex, Int64.MinValue, targetFrame, Int64.MaxValue, leviathan.AVSEEK_FLAG_FRAME);
|
||||
leviathan.avcodec_flush_buffers(_pVideoContext);
|
||||
}
|
||||
}
|
||||
|
||||
// 跳转到指定帧
|
||||
public void SeekToFrame(long frame)
|
||||
{
|
||||
lock (_codecLock)
|
||||
{
|
||||
if (_pVideoContext == null || _pFormatContext == null)
|
||||
return;
|
||||
|
||||
long pts = _pFormatContext->streams[_videoStreamIndex]->duration / _frameCount * frame - 1;
|
||||
leviathan.avformat_seek_file(_pFormatContext, _videoStreamIndex, Int64.MinValue, pts, Int64.MaxValue, leviathan.AVSEEK_FLAG_FRAME);
|
||||
leviathan.avcodec_flush_buffers(_pVideoContext);
|
||||
}
|
||||
}
|
||||
|
||||
// 获取decodedFrameIndex的引用,用于Interlocked操作
|
||||
public ref long GetDecodedFrameIndexRef()
|
||||
{
|
||||
return ref _decodedFrameIndex;
|
||||
}
|
||||
|
||||
// 暂停/恢复播放的空方法(软件解码器不需要特殊处理)
|
||||
public void Pause(bool pause)
|
||||
{
|
||||
// 软件解码器的暂停由LeviathanVideoDecoder的_isPause标志控制
|
||||
// 这里不需要额外的实现
|
||||
}
|
||||
|
||||
// 微信平台特有方法的空实现
|
||||
public int GetDecoderStatus()
|
||||
{
|
||||
// 软件解码器始终返回已启动状态
|
||||
return 2;
|
||||
}
|
||||
|
||||
// 将视频帧数据复制到纹理(需要在信号保护下执行,访问_videoFrame)
|
||||
public void CopyFrameDataToTextures(UnityEngine.Texture2D[] textures)
|
||||
{
|
||||
const int TextureCount = 3;
|
||||
|
||||
if (textures == null || textures.Length < TextureCount) return;
|
||||
|
||||
// 更新YUV纹理 - 只做SetPixelData,不做Apply
|
||||
for (uint i = 0; i < TextureCount; i++)
|
||||
{
|
||||
if (_videoFrame->data[i] == null || textures[i] == null) continue;
|
||||
|
||||
var nativeArray = Unity.Collections.LowLevel.Unsafe.NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray<byte>(
|
||||
_videoFrame->data[i],
|
||||
textures[i].width * textures[i].height,
|
||||
Unity.Collections.Allocator.None
|
||||
);
|
||||
|
||||
try
|
||||
{
|
||||
#if ENABLE_UNITY_COLLECTIONS_CHECKS
|
||||
Unity.Collections.LowLevel.Unsafe.NativeArrayUnsafeUtility.SetAtomicSafetyHandle(ref nativeArray, Unity.Collections.LowLevel.Unsafe.AtomicSafetyHandle.Create());
|
||||
#endif
|
||||
// 只复制数据到纹理的CPU缓冲区,不上传到GPU
|
||||
textures[i].SetPixelData(nativeArray, 0);
|
||||
#if ENABLE_UNITY_COLLECTIONS_CHECKS
|
||||
Unity.Collections.LowLevel.Unsafe.AtomicSafetyHandle.Release(Unity.Collections.LowLevel.Unsafe.NativeArrayUnsafeUtility.GetAtomicSafetyHandle(nativeArray));
|
||||
#endif
|
||||
}
|
||||
catch (UnityException)
|
||||
{
|
||||
|
||||
}
|
||||
finally
|
||||
{
|
||||
nativeArray.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 将纹理数据应用到GPU(可以和下一帧解码并行执行)
|
||||
public void ApplyTextures(UnityEngine.Texture2D[] textures)
|
||||
{
|
||||
const int TextureCount = 3;
|
||||
|
||||
if (textures == null || textures.Length < TextureCount) return;
|
||||
|
||||
// 将CPU数据上传到GPU - 不需要访问_videoFrame
|
||||
for (uint i = 0; i < TextureCount; i++)
|
||||
{
|
||||
if (textures[i] == null) continue;
|
||||
|
||||
try
|
||||
{
|
||||
textures[i].Apply(false, false);
|
||||
}
|
||||
catch (UnityException)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8483db06286d50244a0f52a17aa423e0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 373be47a4cbf74440b38b8262a3b7ded
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7e114e8876509a14b8fae1de0eef5764
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,175 @@
|
||||
Shader "LeviathanVideo/YUV420P"
|
||||
{
|
||||
Properties
|
||||
{
|
||||
[PerRendererData] _MainTex ("YTexture", 2D) = "white" {}
|
||||
_UTex("UTexture",2D)="white"{}
|
||||
_VTex("VTexture",2D)="white"{}
|
||||
[Toggle]_ReverseY("Reverse Y",Float)=1
|
||||
|
||||
_Color ("Tint", Color) = (1,1,1,1)
|
||||
|
||||
_StencilComp ("Stencil Comparison", Float) = 8
|
||||
_Stencil ("Stencil ID", Float) = 0
|
||||
_StencilOp ("Stencil Operation", Float) = 0
|
||||
_StencilWriteMask ("Stencil Write Mask", Float) = 255
|
||||
_StencilReadMask ("Stencil Read Mask", Float) = 255
|
||||
|
||||
_ColorMask ("Color Mask", Float) = 15
|
||||
|
||||
[Toggle(UNITY_UI_ALPHACLIP)] _UseUIAlphaClip ("Use Alpha Clip", Float) = 0
|
||||
}
|
||||
|
||||
SubShader
|
||||
{
|
||||
Tags
|
||||
{
|
||||
"Queue"="Transparent"
|
||||
"IgnoreProjector"="True"
|
||||
"RenderType"="Transparent"
|
||||
"PreviewType"="Plane"
|
||||
"CanUseSpriteAtlas"="True"
|
||||
}
|
||||
|
||||
Stencil
|
||||
{
|
||||
Ref [_Stencil]
|
||||
Comp [_StencilComp]
|
||||
Pass [_StencilOp]
|
||||
ReadMask [_StencilReadMask]
|
||||
WriteMask [_StencilWriteMask]
|
||||
}
|
||||
|
||||
Cull Off
|
||||
Lighting Off
|
||||
ZWrite Off
|
||||
ZTest [unity_GUIZTestMode]
|
||||
Blend SrcAlpha OneMinusSrcAlpha
|
||||
ColorMask [_ColorMask]
|
||||
|
||||
Pass
|
||||
{
|
||||
Name "Default"
|
||||
CGPROGRAM
|
||||
#pragma vertex vert
|
||||
#pragma fragment frag
|
||||
#pragma target 2.0
|
||||
|
||||
#include "UnityCG.cginc"
|
||||
#include "UnityUI.cginc"
|
||||
|
||||
#pragma multi_compile_local _ UNITY_UI_CLIP_RECT
|
||||
#pragma multi_compile_local _ UNITY_UI_ALPHACLIP
|
||||
|
||||
#if UNITY_VERSION < 202100
|
||||
inline half3 UIGammaToLinear(half3 value)
|
||||
{
|
||||
return value * (value * (value * 0.305306011h + 0.682171111h) + 0.012522878h);
|
||||
}
|
||||
#endif
|
||||
|
||||
//Rec.709
|
||||
static const half3x3 YUV_TO_RGB = half3x3(
|
||||
1.16438, 1.16438, 1.16438,
|
||||
0.0, -0.21325, 2.11240,
|
||||
1.79274, -0.53291, 0.0
|
||||
);
|
||||
|
||||
struct appdata_t
|
||||
{
|
||||
float4 vertex : POSITION;
|
||||
float4 color : COLOR;
|
||||
float2 texcoord : TEXCOORD0;
|
||||
UNITY_VERTEX_INPUT_INSTANCE_ID
|
||||
};
|
||||
|
||||
struct v2f
|
||||
{
|
||||
float4 vertex : SV_POSITION;
|
||||
fixed4 color : COLOR;
|
||||
float2 texcoord : TEXCOORD0;
|
||||
float4 worldPosition : TEXCOORD1;
|
||||
float4 mask : TEXCOORD2;
|
||||
UNITY_VERTEX_OUTPUT_STEREO
|
||||
};
|
||||
|
||||
sampler2D _MainTex,_UTex,_VTex;
|
||||
fixed4 _Color;
|
||||
fixed4 _TextureSampleAdd;
|
||||
float4 _ClipRect;
|
||||
float4 _MainTex_ST;
|
||||
float _UIMaskSoftnessX;
|
||||
float _UIMaskSoftnessY;
|
||||
int _UIVertexColorAlwaysGammaSpace;
|
||||
half _ReverseY;
|
||||
|
||||
v2f vert(appdata_t v)
|
||||
{
|
||||
v2f OUT;
|
||||
UNITY_SETUP_INSTANCE_ID(v);
|
||||
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(OUT);
|
||||
float4 vPosition = UnityObjectToClipPos(v.vertex);
|
||||
OUT.worldPosition = v.vertex;
|
||||
OUT.vertex = vPosition;
|
||||
|
||||
float2 pixelSize = vPosition.w;
|
||||
pixelSize /= float2(1, 1) * abs(mul((float2x2)UNITY_MATRIX_P, _ScreenParams.xy));
|
||||
|
||||
float4 clampedRect = clamp(_ClipRect, -2e10, 2e10);
|
||||
float2 maskUV = (v.vertex.xy - clampedRect.xy) / (clampedRect.zw - clampedRect.xy);
|
||||
OUT.texcoord = TRANSFORM_TEX(v.texcoord.xy, _MainTex);
|
||||
if (_ReverseY>0)
|
||||
{
|
||||
OUT.texcoord.y=1- OUT.texcoord.y;
|
||||
}
|
||||
OUT.mask = float4(v.vertex.xy * 2 - clampedRect.xy - clampedRect.zw, 0.25 / (0.25 * half2(_UIMaskSoftnessX, _UIMaskSoftnessY) + abs(pixelSize.xy)));
|
||||
|
||||
|
||||
if (_UIVertexColorAlwaysGammaSpace)
|
||||
{
|
||||
if(!IsGammaSpace())
|
||||
{
|
||||
v.color.rgb = UIGammaToLinear(v.color.rgb);
|
||||
}
|
||||
}
|
||||
|
||||
OUT.color = v.color * _Color;
|
||||
return OUT;
|
||||
}
|
||||
|
||||
fixed4 frag(v2f IN) : SV_Target
|
||||
{
|
||||
//Round up the alpha color coming from the interpolator (to 1.0/256.0 steps)
|
||||
//The incoming alpha could have numerical instability, which makes it very sensible to
|
||||
//HDR color transparency blend, when it blends with the world's texture.
|
||||
const half alphaPrecision = half(0xff);
|
||||
const half invAlphaPrecision = half(1.0 / alphaPrecision);
|
||||
IN.color.a = round(IN.color.a * alphaPrecision) * invAlphaPrecision;
|
||||
half3 c = 0;
|
||||
c.r = (tex2D(_MainTex, IN.texcoord)).a-(16/255.0);
|
||||
c.g = (tex2D(_UTex, IN.texcoord) ).a-(128/255.0);
|
||||
c.b = (tex2D(_VTex, IN.texcoord) ).a-(128/255.0);
|
||||
c=mul(c,YUV_TO_RGB);
|
||||
|
||||
half4 color = half4(c,IN.color.a);
|
||||
color.rgb = saturate(color*_Color);
|
||||
#ifdef UNITY_COLORSPACE_GAMMA
|
||||
#else
|
||||
color.rgb = pow(color.rgb, 2.2);
|
||||
#endif
|
||||
|
||||
#ifdef UNITY_UI_CLIP_RECT
|
||||
half2 m = saturate((_ClipRect.zw - _ClipRect.xy - abs(IN.mask.xy)) * IN.mask.zw);
|
||||
color.a *= m.x * m.y;
|
||||
#endif
|
||||
|
||||
#ifdef UNITY_UI_ALPHACLIP
|
||||
clip (color.a - 0.001);
|
||||
#endif
|
||||
|
||||
return color * IN.color;
|
||||
}
|
||||
ENDCG
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: beb8b637c3a75a24aa0de903c96638e1
|
||||
ShaderImporter:
|
||||
externalObjects: {}
|
||||
defaultTextures: []
|
||||
nonModifiableTextures: []
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,106 @@
|
||||
Shader "LeviathanVideo/YUV420P_3D"
|
||||
{
|
||||
Properties
|
||||
{
|
||||
_MainTex ("YTexture", 2D) = "white" {}
|
||||
_UTex("UTexture",2D)="white"{}
|
||||
_VTex("VTexture",2D)="white"{}
|
||||
[Toggle]_ReverseY("Reverse Y",Float)=1
|
||||
|
||||
_Color ("Tint", Color) = (1,1,1,1)
|
||||
|
||||
[Enum(UnityEngine.Rendering.CullMode)] _Cull ("Cull Mode", Float) = 2
|
||||
[Enum(Off, 0, On, 1)] _ZWrite ("ZWrite", Float) = 1
|
||||
[Enum(UnityEngine.Rendering.CompareFunction)] _ZTest ("ZTest", Float) = 4
|
||||
}
|
||||
|
||||
SubShader
|
||||
{
|
||||
Tags
|
||||
{
|
||||
"Queue"="Transparent"
|
||||
"IgnoreProjector"="True"
|
||||
"RenderType"="Transparent"
|
||||
}
|
||||
|
||||
Cull [_Cull]
|
||||
Lighting Off
|
||||
ZWrite [_ZWrite]
|
||||
ZTest [_ZTest]
|
||||
Blend SrcAlpha OneMinusSrcAlpha
|
||||
|
||||
Pass
|
||||
{
|
||||
Name "Default"
|
||||
CGPROGRAM
|
||||
#pragma vertex vert
|
||||
#pragma fragment frag
|
||||
#pragma target 2.0
|
||||
|
||||
#include "UnityCG.cginc"
|
||||
|
||||
//Rec.709
|
||||
static const half3x3 YUV_TO_RGB = half3x3(
|
||||
1.16438, 1.16438, 1.16438,
|
||||
0.0, -0.21325, 2.11240,
|
||||
1.79274, -0.53291, 0.0
|
||||
);
|
||||
|
||||
struct appdata_t
|
||||
{
|
||||
float4 vertex : POSITION;
|
||||
float4 color : COLOR;
|
||||
float2 texcoord : TEXCOORD0;
|
||||
UNITY_VERTEX_INPUT_INSTANCE_ID
|
||||
};
|
||||
|
||||
struct v2f
|
||||
{
|
||||
float4 vertex : SV_POSITION;
|
||||
fixed4 color : COLOR;
|
||||
float2 texcoord : TEXCOORD0;
|
||||
UNITY_VERTEX_OUTPUT_STEREO
|
||||
};
|
||||
|
||||
sampler2D _MainTex,_UTex,_VTex;
|
||||
fixed4 _Color;
|
||||
float4 _MainTex_ST;
|
||||
half _ReverseY;
|
||||
|
||||
v2f vert(appdata_t v)
|
||||
{
|
||||
v2f OUT;
|
||||
UNITY_SETUP_INSTANCE_ID(v);
|
||||
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(OUT);
|
||||
OUT.vertex = UnityObjectToClipPos(v.vertex);
|
||||
OUT.texcoord = TRANSFORM_TEX(v.texcoord.xy, _MainTex);
|
||||
if (_ReverseY > 0)
|
||||
{
|
||||
OUT.texcoord.y = 1 - OUT.texcoord.y;
|
||||
}
|
||||
OUT.color = v.color * _Color;
|
||||
return OUT;
|
||||
}
|
||||
|
||||
fixed4 frag(v2f IN) : SV_Target
|
||||
{
|
||||
half3 c = 0;
|
||||
c.r = (tex2D(_MainTex, IN.texcoord)).a - (16/255.0);
|
||||
c.g = (tex2D(_UTex, IN.texcoord)).a - (128/255.0);
|
||||
c.b = (tex2D(_VTex, IN.texcoord)).a - (128/255.0);
|
||||
c = mul(c, YUV_TO_RGB);
|
||||
|
||||
half4 color = half4(c, IN.color.a);
|
||||
color.rgb = saturate(color * _Color);
|
||||
#ifdef UNITY_COLORSPACE_GAMMA
|
||||
#else
|
||||
color.rgb = pow(color.rgb, 2.2);
|
||||
#endif
|
||||
|
||||
return color * IN.color;
|
||||
}
|
||||
ENDCG
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a815bdc14ea584c4b82c5cf38992fc18
|
||||
ShaderImporter:
|
||||
externalObjects: {}
|
||||
defaultTextures: []
|
||||
nonModifiableTextures: []
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,121 @@
|
||||
Shader "LeviathanVideo/YUV420P_3D_Alpha_Bottom"
|
||||
{
|
||||
Properties
|
||||
{
|
||||
_MainTex ("YTexture", 2D) = "white" {}
|
||||
_UTex("UTexture",2D)="white"{}
|
||||
_VTex("VTexture",2D)="white"{}
|
||||
[Toggle]_ReverseY("Reverse Y",Float)=1
|
||||
|
||||
_Color ("Tint", Color) = (1,1,1,1)
|
||||
|
||||
[Enum(UnityEngine.Rendering.CullMode)] _Cull ("Cull Mode", Float) = 2
|
||||
[Enum(Off, 0, On, 1)] _ZWrite ("ZWrite", Float) = 1
|
||||
[Enum(UnityEngine.Rendering.CompareFunction)] _ZTest ("ZTest", Float) = 4
|
||||
}
|
||||
|
||||
SubShader
|
||||
{
|
||||
Tags
|
||||
{
|
||||
"Queue"="Transparent"
|
||||
"IgnoreProjector"="True"
|
||||
"RenderType"="Transparent"
|
||||
}
|
||||
|
||||
Cull [_Cull]
|
||||
Lighting Off
|
||||
ZWrite [_ZWrite]
|
||||
ZTest [_ZTest]
|
||||
Blend SrcAlpha OneMinusSrcAlpha
|
||||
|
||||
Pass
|
||||
{
|
||||
Name "Default"
|
||||
CGPROGRAM
|
||||
#pragma vertex vert
|
||||
#pragma fragment frag
|
||||
#pragma target 2.0
|
||||
|
||||
#include "UnityCG.cginc"
|
||||
|
||||
//Rec.709
|
||||
static const half3x3 YUV_TO_RGB = half3x3(
|
||||
1.16438, 1.16438, 1.16438,
|
||||
0.0, -0.21325, 2.11240,
|
||||
1.79274, -0.53291, 0.0
|
||||
);
|
||||
static const float FLOAT_16_255 = 16.0 / 255.0;
|
||||
static const float FLOAT_128_255 = 128.0 / 255.0;
|
||||
|
||||
struct appdata_t
|
||||
{
|
||||
float4 vertex : POSITION;
|
||||
float4 color : COLOR;
|
||||
float2 texcoord : TEXCOORD0;
|
||||
UNITY_VERTEX_INPUT_INSTANCE_ID
|
||||
};
|
||||
|
||||
struct v2f
|
||||
{
|
||||
float4 vertex : SV_POSITION;
|
||||
fixed4 color : COLOR;
|
||||
float2 texcoord : TEXCOORD0;
|
||||
UNITY_VERTEX_OUTPUT_STEREO
|
||||
};
|
||||
|
||||
sampler2D _MainTex,_UTex,_VTex;
|
||||
fixed4 _Color;
|
||||
float4 _MainTex_ST;
|
||||
half _ReverseY;
|
||||
|
||||
v2f vert(appdata_t v)
|
||||
{
|
||||
v2f OUT;
|
||||
UNITY_SETUP_INSTANCE_ID(v);
|
||||
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(OUT);
|
||||
OUT.vertex = UnityObjectToClipPos(v.vertex);
|
||||
OUT.texcoord = TRANSFORM_TEX(v.texcoord.xy, _MainTex);
|
||||
if (_ReverseY > 0)
|
||||
{
|
||||
OUT.texcoord.y = 1 - OUT.texcoord.y;
|
||||
}
|
||||
OUT.color = v.color * _Color;
|
||||
return OUT;
|
||||
}
|
||||
|
||||
fixed4 frag(v2f IN) : SV_Target
|
||||
{
|
||||
half3 c = 0;
|
||||
half2 uv = IN.texcoord;
|
||||
// 上半部分是颜色,下半部分是Alpha
|
||||
half2 uv1 = half2(uv.x, uv.y * 0.5);
|
||||
half2 uv2 = half2(uv.x, uv.y * 0.5 + 0.5);
|
||||
|
||||
c.r = tex2D(_MainTex, uv1).a - FLOAT_16_255;
|
||||
c.g = tex2D(_UTex, uv1).a - FLOAT_128_255;
|
||||
c.b = tex2D(_VTex, uv1).a - FLOAT_128_255;
|
||||
c = mul(c, YUV_TO_RGB);
|
||||
|
||||
half3 ca = 0;
|
||||
ca.r = tex2D(_MainTex, uv2).a - FLOAT_16_255;
|
||||
ca.g = tex2D(_UTex, uv2).a - FLOAT_128_255;
|
||||
ca.b = 1;
|
||||
ca = mul(ca, YUV_TO_RGB);
|
||||
c /= clamp(ca.b, 0.01, 1);
|
||||
c = saturate(c);
|
||||
|
||||
half4 color = half4(c, ca.b);
|
||||
#ifdef UNITY_COLORSPACE_GAMMA
|
||||
#else
|
||||
color.rgb = pow(color.rgb, 2.2);
|
||||
#endif
|
||||
color.rgb = color.rgb * _Color.rgb;
|
||||
|
||||
return color * IN.color;
|
||||
}
|
||||
ENDCG
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9944e974099a00445a9a47d663f3223f
|
||||
ShaderImporter:
|
||||
externalObjects: {}
|
||||
defaultTextures: []
|
||||
nonModifiableTextures: []
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,123 @@
|
||||
Shader "LeviathanVideo/YUV420P_3D_Alpha_Right"
|
||||
{
|
||||
Properties
|
||||
{
|
||||
_MainTex ("YTexture", 2D) = "white" {}
|
||||
_UTex("UTexture",2D)="white"{}
|
||||
_VTex("VTexture",2D)="white"{}
|
||||
[Toggle]_ReverseY("Reverse Y",Float)=1
|
||||
|
||||
_Color ("Tint", Color) = (1,1,1,1)
|
||||
_ValidWidthRatio ("ValidWidthRatio", Float) = 1
|
||||
|
||||
[Enum(UnityEngine.Rendering.CullMode)] _Cull ("Cull Mode", Float) = 2
|
||||
[Enum(Off, 0, On, 1)] _ZWrite ("ZWrite", Float) = 1
|
||||
[Enum(UnityEngine.Rendering.CompareFunction)] _ZTest ("ZTest", Float) = 4
|
||||
}
|
||||
|
||||
SubShader
|
||||
{
|
||||
Tags
|
||||
{
|
||||
"Queue"="Transparent"
|
||||
"IgnoreProjector"="True"
|
||||
"RenderType"="Transparent"
|
||||
}
|
||||
|
||||
Cull [_Cull]
|
||||
Lighting Off
|
||||
ZWrite [_ZWrite]
|
||||
ZTest [_ZTest]
|
||||
Blend SrcAlpha OneMinusSrcAlpha
|
||||
|
||||
Pass
|
||||
{
|
||||
Name "Default"
|
||||
CGPROGRAM
|
||||
#pragma vertex vert
|
||||
#pragma fragment frag
|
||||
#pragma target 2.0
|
||||
|
||||
#include "UnityCG.cginc"
|
||||
|
||||
//Rec.709
|
||||
static const half3x3 YUV_TO_RGB = half3x3(
|
||||
1.16438, 1.16438, 1.16438,
|
||||
0.0, -0.21325, 2.11240,
|
||||
1.79274, -0.53291, 0.0
|
||||
);
|
||||
static const float FLOAT_16_255 = 16.0 / 255.0;
|
||||
static const float FLOAT_128_255 = 128.0 / 255.0;
|
||||
|
||||
struct appdata_t
|
||||
{
|
||||
float4 vertex : POSITION;
|
||||
float4 color : COLOR;
|
||||
float2 texcoord : TEXCOORD0;
|
||||
UNITY_VERTEX_INPUT_INSTANCE_ID
|
||||
};
|
||||
|
||||
struct v2f
|
||||
{
|
||||
float4 vertex : SV_POSITION;
|
||||
fixed4 color : COLOR;
|
||||
float2 texcoord : TEXCOORD0;
|
||||
UNITY_VERTEX_OUTPUT_STEREO
|
||||
};
|
||||
|
||||
sampler2D _MainTex,_UTex,_VTex;
|
||||
fixed4 _Color;
|
||||
float4 _MainTex_ST;
|
||||
float _ValidWidthRatio;
|
||||
half _ReverseY;
|
||||
|
||||
v2f vert(appdata_t v)
|
||||
{
|
||||
v2f OUT;
|
||||
UNITY_SETUP_INSTANCE_ID(v);
|
||||
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(OUT);
|
||||
OUT.vertex = UnityObjectToClipPos(v.vertex);
|
||||
OUT.texcoord = TRANSFORM_TEX(v.texcoord.xy, _MainTex);
|
||||
if (_ReverseY > 0)
|
||||
{
|
||||
OUT.texcoord.y = 1 - OUT.texcoord.y;
|
||||
}
|
||||
OUT.color = v.color * _Color;
|
||||
return OUT;
|
||||
}
|
||||
|
||||
fixed4 frag(v2f IN) : SV_Target
|
||||
{
|
||||
half3 c = 0;
|
||||
half2 uv = IN.texcoord;
|
||||
half ratio = 0.5 * _ValidWidthRatio;
|
||||
half2 uv1 = half2(uv.x * ratio, uv.y);
|
||||
half2 uv2 = half2(uv.x * ratio + ratio, uv.y);
|
||||
|
||||
c.r = tex2D(_MainTex, uv1).a - FLOAT_16_255;
|
||||
c.g = tex2D(_UTex, uv1).a - FLOAT_128_255;
|
||||
c.b = tex2D(_VTex, uv1).a - FLOAT_128_255;
|
||||
c = mul(c, YUV_TO_RGB);
|
||||
|
||||
half3 ca = 0;
|
||||
ca.r = tex2D(_MainTex, uv2).a - FLOAT_16_255;
|
||||
ca.g = tex2D(_UTex, uv2).a - FLOAT_128_255;
|
||||
ca.b = 1;
|
||||
ca = mul(ca, YUV_TO_RGB);
|
||||
c /= clamp(ca.b, 0.01, 1);
|
||||
c = saturate(c);
|
||||
|
||||
half4 color = half4(c, ca.b);
|
||||
#ifdef UNITY_COLORSPACE_GAMMA
|
||||
#else
|
||||
color.rgb = pow(color.rgb, 2.2);
|
||||
#endif
|
||||
color.rgb = color.rgb * _Color.rgb;
|
||||
|
||||
return color * IN.color;
|
||||
}
|
||||
ENDCG
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0b0f65a064460c54b93e5f72956880b9
|
||||
ShaderImporter:
|
||||
externalObjects: {}
|
||||
defaultTextures: []
|
||||
nonModifiableTextures: []
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,184 @@
|
||||
Shader "LeviathanVideo/YUV420P_3D_ChromaKey"
|
||||
{
|
||||
Properties
|
||||
{
|
||||
_MainTex ("YTexture", 2D) = "white" {}
|
||||
_UTex("UTexture",2D)="white"{}
|
||||
_VTex("VTexture",2D)="white"{}
|
||||
[Toggle]_ReverseY("Reverse Y",Float)=1
|
||||
|
||||
_Color ("Tint", Color) = (1,1,1,1)
|
||||
|
||||
_KeyColor("KeyColor", Color) = (0,1,0,0)
|
||||
_ColorCutoff("Cutoff", Range(0, 1)) = 0.2
|
||||
_ColorFeathering("ColorFeathering", Range(0, 1)) = 0.33
|
||||
_MaskFeathering("MaskFeathering", Range(0, 1)) = 1
|
||||
_Sharpening("Sharpening", Range(0, 1)) = 0.5
|
||||
|
||||
[Enum(UnityEngine.Rendering.CullMode)] _Cull ("Cull Mode", Float) = 2
|
||||
[Enum(Off, 0, On, 1)] _ZWrite ("ZWrite", Float) = 1
|
||||
[Enum(UnityEngine.Rendering.CompareFunction)] _ZTest ("ZTest", Float) = 4
|
||||
}
|
||||
|
||||
SubShader
|
||||
{
|
||||
Tags
|
||||
{
|
||||
"Queue"="Transparent"
|
||||
"IgnoreProjector"="True"
|
||||
"RenderType"="Transparent"
|
||||
}
|
||||
|
||||
Cull [_Cull]
|
||||
Lighting Off
|
||||
ZWrite [_ZWrite]
|
||||
ZTest [_ZTest]
|
||||
Blend SrcAlpha OneMinusSrcAlpha
|
||||
|
||||
Pass
|
||||
{
|
||||
Name "Default"
|
||||
CGPROGRAM
|
||||
#pragma vertex vert
|
||||
#pragma fragment frag
|
||||
#pragma target 2.0
|
||||
|
||||
#include "UnityCG.cginc"
|
||||
|
||||
//Rec.709
|
||||
static const half3x3 YUV_TO_RGB = half3x3(
|
||||
1.16438, 1.16438, 1.16438,
|
||||
0.0, -0.21325, 2.11240,
|
||||
1.79274, -0.53291, 0.0
|
||||
);
|
||||
static const float FLOAT_16_255 = 16.0 / 255.0;
|
||||
static const float FLOAT_128_255 = 128.0 / 255.0;
|
||||
|
||||
struct appdata_t
|
||||
{
|
||||
float4 vertex : POSITION;
|
||||
float4 color : COLOR;
|
||||
float2 texcoord : TEXCOORD0;
|
||||
UNITY_VERTEX_INPUT_INSTANCE_ID
|
||||
};
|
||||
|
||||
struct v2f
|
||||
{
|
||||
float4 vertex : SV_POSITION;
|
||||
fixed4 color : COLOR;
|
||||
float2 texcoord : TEXCOORD0;
|
||||
UNITY_VERTEX_OUTPUT_STEREO
|
||||
};
|
||||
|
||||
sampler2D _MainTex,_UTex,_VTex;
|
||||
fixed4 _Color;
|
||||
float4 _MainTex_ST;
|
||||
half _ReverseY;
|
||||
|
||||
float4 _MainTex_TexelSize;
|
||||
float4 _KeyColor;
|
||||
float _ColorCutoff;
|
||||
float _ColorFeathering;
|
||||
float _MaskFeathering;
|
||||
float _Sharpening;
|
||||
|
||||
float rgb2y(float3 c)
|
||||
{
|
||||
return (0.299*c.r + 0.587*c.g + 0.114*c.b);
|
||||
}
|
||||
|
||||
float rgb2cb(float3 c)
|
||||
{
|
||||
return (0.5 + -0.168736*c.r - 0.331264*c.g + 0.5*c.b);
|
||||
}
|
||||
|
||||
float rgb2cr(float3 c)
|
||||
{
|
||||
return (0.5 + 0.5*c.r - 0.418688*c.g - 0.081312*c.b);
|
||||
}
|
||||
|
||||
float colorclose(float Cb_p, float Cr_p, float Cb_key, float Cr_key, float tola, float tolb)
|
||||
{
|
||||
float temp = (Cb_key-Cb_p)*(Cb_key-Cb_p)+(Cr_key-Cr_p)*(Cr_key-Cr_p);
|
||||
float tola2 = tola*tola;
|
||||
float tolb2 = tolb*tolb;
|
||||
if (temp < tola2) return (0);
|
||||
if (temp < tolb2) return (temp-tola2)/(tolb2-tola2);
|
||||
return (1);
|
||||
}
|
||||
|
||||
half3 getRGB(sampler2D tex1, sampler2D tex2, sampler2D tex3, float2 uv)
|
||||
{
|
||||
half3 c = 0;
|
||||
c.r = tex2D(tex1, uv).a - FLOAT_16_255;
|
||||
c.g = tex2D(tex2, uv).a - FLOAT_128_255;
|
||||
c.b = tex2D(tex3, uv).a - FLOAT_128_255;
|
||||
c = mul(c, YUV_TO_RGB);
|
||||
return c;
|
||||
}
|
||||
|
||||
float maskedTex2D(sampler2D tex1, sampler2D tex2, sampler2D tex3, float2 uv)
|
||||
{
|
||||
float4 color = float4(getRGB(tex1, tex2, tex3, uv), 1.0);
|
||||
|
||||
// Chroma key to CYK conversion
|
||||
float key_cb = rgb2cb(_KeyColor.rgb);
|
||||
float key_cr = rgb2cr(_KeyColor.rgb);
|
||||
float pix_cb = rgb2cb(color.rgb);
|
||||
float pix_cr = rgb2cr(color.rgb);
|
||||
|
||||
return colorclose(pix_cb, pix_cr, key_cb, key_cr, _ColorCutoff, _ColorFeathering);
|
||||
}
|
||||
|
||||
v2f vert(appdata_t v)
|
||||
{
|
||||
v2f OUT;
|
||||
UNITY_SETUP_INSTANCE_ID(v);
|
||||
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(OUT);
|
||||
OUT.vertex = UnityObjectToClipPos(v.vertex);
|
||||
OUT.texcoord = TRANSFORM_TEX(v.texcoord.xy, _MainTex);
|
||||
if (_ReverseY > 0)
|
||||
{
|
||||
OUT.texcoord.y = 1 - OUT.texcoord.y;
|
||||
}
|
||||
OUT.color = v.color * _Color;
|
||||
return OUT;
|
||||
}
|
||||
|
||||
fixed4 frag(v2f IN) : SV_Target
|
||||
{
|
||||
half4 color = half4(getRGB(_MainTex, _UTex, _VTex, IN.texcoord), IN.color.a);
|
||||
color.rgb = saturate(color * _Color);
|
||||
#ifdef UNITY_COLORSPACE_GAMMA
|
||||
#else
|
||||
color.rgb = pow(color.rgb, 2.2);
|
||||
#endif
|
||||
|
||||
// Get pixel width
|
||||
float2 pixelWidth = float2(1.0 / _MainTex_TexelSize.z, 0);
|
||||
float2 pixelHeight = float2(0, 1.0 / _MainTex_TexelSize.w);
|
||||
|
||||
// Unfeathered mask
|
||||
float mask = maskedTex2D(_MainTex, _UTex, _VTex, IN.texcoord);
|
||||
|
||||
// Feathering & smoothing
|
||||
float c = mask;
|
||||
float r = maskedTex2D(_MainTex, _UTex, _VTex, IN.texcoord + pixelWidth);
|
||||
float l = maskedTex2D(_MainTex, _UTex, _VTex, IN.texcoord - pixelWidth);
|
||||
float d = maskedTex2D(_MainTex, _UTex, _VTex, IN.texcoord + pixelHeight);
|
||||
float u = maskedTex2D(_MainTex, _UTex, _VTex, IN.texcoord - pixelHeight);
|
||||
float rd = maskedTex2D(_MainTex, _UTex, _VTex, IN.texcoord + pixelWidth + pixelHeight) * .707;
|
||||
float dl = maskedTex2D(_MainTex, _UTex, _VTex, IN.texcoord - pixelWidth + pixelHeight) * .707;
|
||||
float lu = maskedTex2D(_MainTex, _UTex, _VTex, IN.texcoord - pixelHeight - pixelWidth) * .707;
|
||||
float ur = maskedTex2D(_MainTex, _UTex, _VTex, IN.texcoord + pixelWidth - pixelHeight) * .707;
|
||||
float blurContribution = (r + l + d + u + rd + dl + lu + ur + c) * 0.12774655;
|
||||
float smoothedMask = smoothstep(_Sharpening, 1, lerp(c, blurContribution, _MaskFeathering));
|
||||
float4 result = color * smoothedMask;
|
||||
|
||||
return float4(result.xyz, smoothedMask) * IN.color;
|
||||
}
|
||||
ENDCG
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4f3d30338e3677045a6f721dedfee089
|
||||
ShaderImporter:
|
||||
externalObjects: {}
|
||||
defaultTextures: []
|
||||
nonModifiableTextures: []
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,188 @@
|
||||
Shader "LeviathanVideo/YUV420P_Alpha_Bottom"
|
||||
{
|
||||
Properties
|
||||
{
|
||||
[PerRendererData] _MainTex ("YTexture", 2D) = "white" {}
|
||||
_UTex("UTexture",2D)="white"{}
|
||||
_VTex("VTexture",2D)="white"{}
|
||||
[Toggle]_ReverseY("Reverse Y",Float)=1
|
||||
|
||||
_Color ("Tint", Color) = (1,1,1,1)
|
||||
|
||||
_StencilComp ("Stencil Comparison", Float) = 8
|
||||
_Stencil ("Stencil ID", Float) = 0
|
||||
_StencilOp ("Stencil Operation", Float) = 0
|
||||
_StencilWriteMask ("Stencil Write Mask", Float) = 255
|
||||
_StencilReadMask ("Stencil Read Mask", Float) = 255
|
||||
|
||||
_ColorMask ("Color Mask", Float) = 15
|
||||
|
||||
[Toggle(UNITY_UI_ALPHACLIP)] _UseUIAlphaClip ("Use Alpha Clip", Float) = 0
|
||||
}
|
||||
|
||||
SubShader
|
||||
{
|
||||
Tags
|
||||
{
|
||||
"Queue"="Transparent"
|
||||
"IgnoreProjector"="True"
|
||||
"RenderType"="Transparent"
|
||||
"PreviewType"="Plane"
|
||||
"CanUseSpriteAtlas"="True"
|
||||
}
|
||||
|
||||
Stencil
|
||||
{
|
||||
Ref [_Stencil]
|
||||
Comp [_StencilComp]
|
||||
Pass [_StencilOp]
|
||||
ReadMask [_StencilReadMask]
|
||||
WriteMask [_StencilWriteMask]
|
||||
}
|
||||
|
||||
Cull Off
|
||||
Lighting Off
|
||||
ZWrite Off
|
||||
ZTest [unity_GUIZTestMode]
|
||||
Blend SrcAlpha OneMinusSrcAlpha
|
||||
ColorMask [_ColorMask]
|
||||
|
||||
Pass
|
||||
{
|
||||
Name "Default"
|
||||
CGPROGRAM
|
||||
#pragma vertex vert
|
||||
#pragma fragment frag
|
||||
#pragma target 2.0
|
||||
|
||||
#include "UnityCG.cginc"
|
||||
#include "UnityUI.cginc"
|
||||
|
||||
#pragma multi_compile_local _ UNITY_UI_CLIP_RECT
|
||||
#pragma multi_compile_local _ UNITY_UI_ALPHACLIP
|
||||
|
||||
#if UNITY_VERSION < 202100
|
||||
inline half3 UIGammaToLinear(half3 value)
|
||||
{
|
||||
return value * (value * (value * 0.305306011h + 0.682171111h) + 0.012522878h);
|
||||
}
|
||||
#endif
|
||||
|
||||
//Rec.709
|
||||
static const half3x3 YUV_TO_RGB = half3x3(
|
||||
1.16438, 1.16438, 1.16438,
|
||||
0.0, -0.21325, 2.11240,
|
||||
1.79274, -0.53291, 0.0
|
||||
);
|
||||
static const float FLOAT_16_255 = 16.0 / 255.0;
|
||||
static const float FLOAT_128_255 = 128.0 / 255.0;
|
||||
struct appdata_t
|
||||
{
|
||||
float4 vertex : POSITION;
|
||||
float4 color : COLOR;
|
||||
float2 texcoord : TEXCOORD0;
|
||||
UNITY_VERTEX_INPUT_INSTANCE_ID
|
||||
};
|
||||
|
||||
struct v2f
|
||||
{
|
||||
float4 vertex : SV_POSITION;
|
||||
fixed4 color : COLOR;
|
||||
float2 texcoord : TEXCOORD0;
|
||||
float4 worldPosition : TEXCOORD1;
|
||||
float4 mask : TEXCOORD2;
|
||||
UNITY_VERTEX_OUTPUT_STEREO
|
||||
};
|
||||
|
||||
sampler2D _MainTex,_UTex,_VTex;
|
||||
fixed4 _Color;
|
||||
fixed4 _TextureSampleAdd;
|
||||
float4 _ClipRect;
|
||||
float4 _MainTex_ST;
|
||||
float _UIMaskSoftnessX;
|
||||
float _UIMaskSoftnessY;
|
||||
int _UIVertexColorAlwaysGammaSpace;
|
||||
half _ReverseY;
|
||||
|
||||
v2f vert(appdata_t v)
|
||||
{
|
||||
v2f OUT;
|
||||
UNITY_SETUP_INSTANCE_ID(v);
|
||||
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(OUT);
|
||||
float4 vPosition = UnityObjectToClipPos(v.vertex);
|
||||
OUT.worldPosition = v.vertex;
|
||||
OUT.vertex = vPosition;
|
||||
|
||||
float2 pixelSize = vPosition.w;
|
||||
pixelSize /= float2(1, 1) * abs(mul((float2x2)UNITY_MATRIX_P, _ScreenParams.xy));
|
||||
|
||||
float4 clampedRect = clamp(_ClipRect, -2e10, 2e10);
|
||||
float2 maskUV = (v.vertex.xy - clampedRect.xy) / (clampedRect.zw - clampedRect.xy);
|
||||
OUT.texcoord = TRANSFORM_TEX(v.texcoord.xy, _MainTex);
|
||||
if (_ReverseY>0)
|
||||
{
|
||||
OUT.texcoord.y=1- OUT.texcoord.y;
|
||||
}
|
||||
OUT.mask = float4(v.vertex.xy * 2 - clampedRect.xy - clampedRect.zw, 0.25 / (0.25 * half2(_UIMaskSoftnessX, _UIMaskSoftnessY) + abs(pixelSize.xy)));
|
||||
|
||||
|
||||
if (_UIVertexColorAlwaysGammaSpace)
|
||||
{
|
||||
if(!IsGammaSpace())
|
||||
{
|
||||
v.color.rgb = UIGammaToLinear(v.color.rgb);
|
||||
}
|
||||
}
|
||||
|
||||
OUT.color = v.color * _Color;
|
||||
return OUT;
|
||||
}
|
||||
|
||||
fixed4 frag(v2f IN) : SV_Target
|
||||
{
|
||||
//Round up the alpha color coming from the interpolator (to 1.0/256.0 steps)
|
||||
//The incoming alpha could have numerical instability, which makes it very sensible to
|
||||
//HDR color transparency blend, when it blends with the world's texture.
|
||||
|
||||
const half alphaPrecision = half(0xff);
|
||||
const half invAlphaPrecision = half(1.0 / alphaPrecision);
|
||||
IN.color.a = round(IN.color.a * alphaPrecision) * invAlphaPrecision;
|
||||
half3 c = 0;
|
||||
half2 uv=IN.texcoord;
|
||||
half2 uv1=half2(uv.x,uv.y*0.5);
|
||||
half2 uv2=half2(uv.x,uv.y*0.5+0.5);
|
||||
|
||||
c.r = tex2D(_MainTex, uv1).a - FLOAT_16_255;
|
||||
c.g = tex2D(_UTex,uv1).a - FLOAT_128_255;
|
||||
c.b = tex2D(_VTex, uv1).a - FLOAT_128_255;
|
||||
c=mul(c,YUV_TO_RGB);
|
||||
|
||||
half3 ca=0;
|
||||
ca.r = tex2D(_MainTex, uv2).a - FLOAT_16_255;
|
||||
ca.g = tex2D(_UTex, uv2).a - FLOAT_128_255;
|
||||
ca.b = 1;
|
||||
ca=mul(ca,YUV_TO_RGB);
|
||||
c/=clamp(ca.b,0.01,1);
|
||||
c=saturate(c);
|
||||
half4 color = half4(c,ca.b);
|
||||
#ifdef UNITY_COLORSPACE_GAMMA
|
||||
#else
|
||||
color.rgb = pow(color.rgb, 2.2);
|
||||
#endif
|
||||
color.rgb = color.rgb*_Color.rgb;
|
||||
|
||||
#ifdef UNITY_UI_CLIP_RECT
|
||||
half2 m = saturate((_ClipRect.zw - _ClipRect.xy - abs(IN.mask.xy)) * IN.mask.zw);
|
||||
color.a *= m.x * m.y;
|
||||
#endif
|
||||
|
||||
#ifdef UNITY_UI_ALPHACLIP
|
||||
clip (color.a - 0.001);
|
||||
#endif
|
||||
|
||||
return color * IN.color;
|
||||
}
|
||||
ENDCG
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7cb92b36c7d519d4a8aea09d292561be
|
||||
ShaderImporter:
|
||||
externalObjects: {}
|
||||
defaultTextures: []
|
||||
nonModifiableTextures: []
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,191 @@
|
||||
Shader "LeviathanVideo/YUV420P_Alpha_Right"
|
||||
{
|
||||
Properties
|
||||
{
|
||||
[PerRendererData] _MainTex ("YTexture", 2D) = "white" {}
|
||||
_UTex("UTexture",2D)="white"{}
|
||||
_VTex("VTexture",2D)="white"{}
|
||||
[Toggle]_ReverseY("Reverse Y",Float)=1
|
||||
|
||||
_Color ("Tint", Color) = (1,1,1,1)
|
||||
|
||||
_StencilComp ("Stencil Comparison", Float) = 8
|
||||
_Stencil ("Stencil ID", Float) = 0
|
||||
_StencilOp ("Stencil Operation", Float) = 0
|
||||
_StencilWriteMask ("Stencil Write Mask", Float) = 255
|
||||
_StencilReadMask ("Stencil Read Mask", Float) = 255
|
||||
|
||||
_ColorMask ("Color Mask", Float) = 15
|
||||
_ValidWidthRatio ("ValidWidthRatio", Float) = 1
|
||||
|
||||
[Toggle(UNITY_UI_ALPHACLIP)] _UseUIAlphaClip ("Use Alpha Clip", Float) = 0
|
||||
}
|
||||
|
||||
SubShader
|
||||
{
|
||||
Tags
|
||||
{
|
||||
"Queue"="Transparent"
|
||||
"IgnoreProjector"="True"
|
||||
"RenderType"="Transparent"
|
||||
"PreviewType"="Plane"
|
||||
"CanUseSpriteAtlas"="True"
|
||||
}
|
||||
|
||||
Stencil
|
||||
{
|
||||
Ref [_Stencil]
|
||||
Comp [_StencilComp]
|
||||
Pass [_StencilOp]
|
||||
ReadMask [_StencilReadMask]
|
||||
WriteMask [_StencilWriteMask]
|
||||
}
|
||||
|
||||
Cull Off
|
||||
Lighting Off
|
||||
ZWrite Off
|
||||
ZTest [unity_GUIZTestMode]
|
||||
Blend SrcAlpha OneMinusSrcAlpha
|
||||
ColorMask [_ColorMask]
|
||||
|
||||
Pass
|
||||
{
|
||||
Name "Default"
|
||||
CGPROGRAM
|
||||
#pragma vertex vert
|
||||
#pragma fragment frag
|
||||
#pragma target 2.0
|
||||
|
||||
#include "UnityCG.cginc"
|
||||
#include "UnityUI.cginc"
|
||||
|
||||
#pragma multi_compile_local _ UNITY_UI_CLIP_RECT
|
||||
#pragma multi_compile_local _ UNITY_UI_ALPHACLIP
|
||||
|
||||
#if UNITY_VERSION < 202100
|
||||
inline half3 UIGammaToLinear(half3 value)
|
||||
{
|
||||
return value * (value * (value * 0.305306011h + 0.682171111h) + 0.012522878h);
|
||||
}
|
||||
#endif
|
||||
|
||||
//Rec.709
|
||||
static const half3x3 YUV_TO_RGB = half3x3(
|
||||
1.16438, 1.16438, 1.16438,
|
||||
0.0, -0.21325, 2.11240,
|
||||
1.79274, -0.53291, 0.0
|
||||
);
|
||||
static const float FLOAT_16_255 = 16.0 / 255.0;
|
||||
static const float FLOAT_128_255 = 128.0 / 255.0;
|
||||
struct appdata_t
|
||||
{
|
||||
float4 vertex : POSITION;
|
||||
float4 color : COLOR;
|
||||
float2 texcoord : TEXCOORD0;
|
||||
UNITY_VERTEX_INPUT_INSTANCE_ID
|
||||
};
|
||||
|
||||
struct v2f
|
||||
{
|
||||
float4 vertex : SV_POSITION;
|
||||
fixed4 color : COLOR;
|
||||
float2 texcoord : TEXCOORD0;
|
||||
float4 worldPosition : TEXCOORD1;
|
||||
float4 mask : TEXCOORD2;
|
||||
UNITY_VERTEX_OUTPUT_STEREO
|
||||
};
|
||||
|
||||
sampler2D _MainTex,_UTex,_VTex;
|
||||
fixed4 _Color;
|
||||
fixed4 _TextureSampleAdd;
|
||||
float4 _ClipRect;
|
||||
float4 _MainTex_ST;
|
||||
float _UIMaskSoftnessX;
|
||||
float _UIMaskSoftnessY;
|
||||
float _ValidWidthRatio;
|
||||
int _UIVertexColorAlwaysGammaSpace;
|
||||
half _ReverseY;
|
||||
|
||||
v2f vert(appdata_t v)
|
||||
{
|
||||
v2f OUT;
|
||||
UNITY_SETUP_INSTANCE_ID(v);
|
||||
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(OUT);
|
||||
float4 vPosition = UnityObjectToClipPos(v.vertex);
|
||||
OUT.worldPosition = v.vertex;
|
||||
OUT.vertex = vPosition;
|
||||
|
||||
float2 pixelSize = vPosition.w;
|
||||
pixelSize /= float2(1, 1) * abs(mul((float2x2)UNITY_MATRIX_P, _ScreenParams.xy));
|
||||
|
||||
float4 clampedRect = clamp(_ClipRect, -2e10, 2e10);
|
||||
float2 maskUV = (v.vertex.xy - clampedRect.xy) / (clampedRect.zw - clampedRect.xy);
|
||||
OUT.texcoord = TRANSFORM_TEX(v.texcoord.xy, _MainTex);
|
||||
if (_ReverseY>0)
|
||||
{
|
||||
OUT.texcoord.y=1- OUT.texcoord.y;
|
||||
}
|
||||
OUT.mask = float4(v.vertex.xy * 2 - clampedRect.xy - clampedRect.zw, 0.25 / (0.25 * half2(_UIMaskSoftnessX, _UIMaskSoftnessY) + abs(pixelSize.xy)));
|
||||
|
||||
|
||||
if (_UIVertexColorAlwaysGammaSpace)
|
||||
{
|
||||
if(!IsGammaSpace())
|
||||
{
|
||||
v.color.rgb = UIGammaToLinear(v.color.rgb);
|
||||
}
|
||||
}
|
||||
|
||||
OUT.color = v.color * _Color;
|
||||
return OUT;
|
||||
}
|
||||
|
||||
fixed4 frag(v2f IN) : SV_Target
|
||||
{
|
||||
//Round up the alpha color coming from the interpolator (to 1.0/256.0 steps)
|
||||
//The incoming alpha could have numerical instability, which makes it very sensible to
|
||||
//HDR color transparency blend, when it blends with the world's texture.
|
||||
|
||||
const half alphaPrecision = half(0xff);
|
||||
const half invAlphaPrecision = half(1.0 / alphaPrecision);
|
||||
IN.color.a = round(IN.color.a * alphaPrecision) * invAlphaPrecision;
|
||||
half3 c = 0;
|
||||
half2 uv=IN.texcoord;
|
||||
half ratio = 0.5 * _ValidWidthRatio;
|
||||
half2 uv1=half2(uv.x*ratio,uv.y);
|
||||
half2 uv2=half2(uv.x*ratio+ratio,uv.y);
|
||||
|
||||
c.r = tex2D(_MainTex, uv1).a - FLOAT_16_255;
|
||||
c.g = tex2D(_UTex,uv1).a - FLOAT_128_255;
|
||||
c.b = tex2D(_VTex, uv1).a - FLOAT_128_255;
|
||||
c=mul(c,YUV_TO_RGB);
|
||||
|
||||
half3 ca=0;
|
||||
ca.r = tex2D(_MainTex, uv2).a - FLOAT_16_255;
|
||||
ca.g = tex2D(_UTex, uv2).a - FLOAT_128_255;
|
||||
ca.b = 1;
|
||||
ca=mul(ca,YUV_TO_RGB);
|
||||
c/=clamp(ca.b,0.01,1);
|
||||
c=saturate(c);
|
||||
half4 color = half4(c,ca.b);
|
||||
#ifdef UNITY_COLORSPACE_GAMMA
|
||||
#else
|
||||
color.rgb = pow(color.rgb, 2.2);
|
||||
#endif
|
||||
color.rgb = color.rgb*_Color.rgb;
|
||||
|
||||
#ifdef UNITY_UI_CLIP_RECT
|
||||
half2 m = saturate((_ClipRect.zw - _ClipRect.xy - abs(IN.mask.xy)) * IN.mask.zw);
|
||||
color.a *= m.x * m.y;
|
||||
#endif
|
||||
|
||||
#ifdef UNITY_UI_ALPHACLIP
|
||||
clip (color.a - 0.001);
|
||||
#endif
|
||||
|
||||
return color * IN.color;
|
||||
}
|
||||
ENDCG
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 527483c5bc98511428b82fedf30210a6
|
||||
ShaderImporter:
|
||||
externalObjects: {}
|
||||
defaultTextures: []
|
||||
nonModifiableTextures: []
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,265 @@
|
||||
Shader "LeviathanVideo/YUV420P_ChromaKey"
|
||||
{
|
||||
Properties
|
||||
{
|
||||
[PerRendererData] _MainTex ("YTexture", 2D) = "white" {}
|
||||
_UTex("UTexture",2D)="white"{}
|
||||
_VTex("VTexture",2D)="white"{}
|
||||
[Toggle]_ReverseY("Reverse Y",Float)=1
|
||||
|
||||
_Color ("Tint", Color) = (1,1,1,1)
|
||||
|
||||
_StencilComp ("Stencil Comparison", Float) = 8
|
||||
_Stencil ("Stencil ID", Float) = 0
|
||||
_StencilOp ("Stencil Operation", Float) = 0
|
||||
_StencilWriteMask ("Stencil Write Mask", Float) = 255
|
||||
_StencilReadMask ("Stencil Read Mask", Float) = 255
|
||||
|
||||
_ColorMask ("Color Mask", Float) = 15
|
||||
|
||||
[Toggle(UNITY_UI_ALPHACLIP)] _UseUIAlphaClip ("Use Alpha Clip", Float) = 0
|
||||
|
||||
_KeyColor("KeyColor", Color) = (0,1,0,0)
|
||||
_ColorCutoff("Cutoff", Range(0, 1)) = 0.2
|
||||
_ColorFeathering("ColorFeathering", Range(0, 1)) = 0.33
|
||||
_MaskFeathering("MaskFeathering", Range(0, 1)) = 1
|
||||
_Sharpening("Sharpening", Range(0, 1)) = 0.5
|
||||
|
||||
// despill 滤镜主要是处理前景由于蓝色或者绿色背景映射的光,比如我们在摄影棚拍摄绿色背景的图片的时候,有的时候会发现人物身上会反射有绿光,此时可以这个滤镜进行处理。
|
||||
// _Despill("DespillStrength", Range(0, 1)) = 1
|
||||
// _DespillLuminanceAdd("DespillLuminanceAdd", Range(0, 1)) = 0.2
|
||||
}
|
||||
|
||||
SubShader
|
||||
{
|
||||
Tags
|
||||
{
|
||||
"Queue"="Transparent"
|
||||
"IgnoreProjector"="True"
|
||||
"RenderType"="Transparent"
|
||||
"PreviewType"="Plane"
|
||||
"CanUseSpriteAtlas"="True"
|
||||
}
|
||||
|
||||
Stencil
|
||||
{
|
||||
Ref [_Stencil]
|
||||
Comp [_StencilComp]
|
||||
Pass [_StencilOp]
|
||||
ReadMask [_StencilReadMask]
|
||||
WriteMask [_StencilWriteMask]
|
||||
}
|
||||
|
||||
Cull Off
|
||||
Lighting Off
|
||||
ZWrite Off
|
||||
ZTest [unity_GUIZTestMode]
|
||||
Blend SrcAlpha OneMinusSrcAlpha
|
||||
ColorMask [_ColorMask]
|
||||
|
||||
Pass
|
||||
{
|
||||
Name "Default"
|
||||
CGPROGRAM
|
||||
#pragma vertex vert
|
||||
#pragma fragment frag
|
||||
#pragma target 2.0
|
||||
|
||||
#include "UnityCG.cginc"
|
||||
#include "UnityUI.cginc"
|
||||
|
||||
#pragma multi_compile_local _ UNITY_UI_CLIP_RECT
|
||||
#pragma multi_compile_local _ UNITY_UI_ALPHACLIP
|
||||
|
||||
#if UNITY_VERSION < 202100
|
||||
inline half3 UIGammaToLinear(half3 value)
|
||||
{
|
||||
return value * (value * (value * 0.305306011h + 0.682171111h) + 0.012522878h);
|
||||
}
|
||||
#endif
|
||||
|
||||
//Rec.709
|
||||
static const half3x3 YUV_TO_RGB = half3x3(
|
||||
1.16438, 1.16438, 1.16438,
|
||||
0.0, -0.21325, 2.11240,
|
||||
1.79274, -0.53291, 0.0
|
||||
);
|
||||
static const float FLOAT_16_255 = 16.0 / 255.0;
|
||||
static const float FLOAT_128_255 = 128.0 / 255.0;
|
||||
struct appdata_t
|
||||
{
|
||||
float4 vertex : POSITION;
|
||||
float4 color : COLOR;
|
||||
float2 texcoord : TEXCOORD0;
|
||||
UNITY_VERTEX_INPUT_INSTANCE_ID
|
||||
};
|
||||
|
||||
struct v2f
|
||||
{
|
||||
float4 vertex : SV_POSITION;
|
||||
fixed4 color : COLOR;
|
||||
float2 texcoord : TEXCOORD0;
|
||||
float4 worldPosition : TEXCOORD1;
|
||||
float4 mask : TEXCOORD2;
|
||||
UNITY_VERTEX_OUTPUT_STEREO
|
||||
};
|
||||
|
||||
sampler2D _MainTex,_UTex,_VTex;
|
||||
fixed4 _Color;
|
||||
fixed4 _TextureSampleAdd;
|
||||
float4 _ClipRect;
|
||||
float4 _MainTex_ST;
|
||||
float _UIMaskSoftnessX;
|
||||
float _UIMaskSoftnessY;
|
||||
int _UIVertexColorAlwaysGammaSpace;
|
||||
half _ReverseY;
|
||||
|
||||
float4 _MainTex_TexelSize;
|
||||
float4 _KeyColor;
|
||||
float _ColorCutoff;
|
||||
float _ColorFeathering;
|
||||
float _MaskFeathering;
|
||||
float _Sharpening;
|
||||
// float _Despill;
|
||||
// float _DespillLuminanceAdd;
|
||||
|
||||
float rgb2y(float3 c)
|
||||
{
|
||||
return (0.299*c.r + 0.587*c.g + 0.114*c.b);
|
||||
}
|
||||
|
||||
float rgb2cb(float3 c)
|
||||
{
|
||||
return (0.5 + -0.168736*c.r - 0.331264*c.g + 0.5*c.b);
|
||||
}
|
||||
|
||||
float rgb2cr(float3 c)
|
||||
{
|
||||
return (0.5 + 0.5*c.r - 0.418688*c.g - 0.081312*c.b);
|
||||
}
|
||||
|
||||
float colorclose(float Cb_p, float Cr_p, float Cb_key, float Cr_key, float tola, float tolb)
|
||||
{
|
||||
float temp = (Cb_key-Cb_p)*(Cb_key-Cb_p)+(Cr_key-Cr_p)*(Cr_key-Cr_p);
|
||||
float tola2 = tola*tola;
|
||||
float tolb2 = tolb*tolb;
|
||||
if (temp < tola2) return (0);
|
||||
if (temp < tolb2) return (temp-tola2)/(tolb2-tola2);
|
||||
return (1);
|
||||
}
|
||||
|
||||
|
||||
half3 getRGB(sampler2D tex1, sampler2D tex2, sampler2D tex3, float2 uv)
|
||||
{
|
||||
half3 c = 0;
|
||||
c.r = tex2D(tex1, uv).a - FLOAT_16_255;
|
||||
c.g = tex2D(tex2, uv).a - FLOAT_128_255;
|
||||
c.b = tex2D(tex3, uv).a - FLOAT_128_255;
|
||||
c=mul(c, YUV_TO_RGB);
|
||||
return c;
|
||||
}
|
||||
|
||||
float maskedTex2D(sampler2D tex1, sampler2D tex2, sampler2D tex3, float2 uv)
|
||||
{
|
||||
float4 color = float4(getRGB(tex1, tex2, tex3, uv), 1.0);
|
||||
|
||||
// Chroma key to CYK conversion
|
||||
float key_cb = rgb2cb(_KeyColor.rgb);
|
||||
float key_cr = rgb2cr(_KeyColor.rgb);
|
||||
float pix_cb = rgb2cb(color.rgb);
|
||||
float pix_cr = rgb2cr(color.rgb);
|
||||
|
||||
return colorclose(pix_cb, pix_cr, key_cb, key_cr, _ColorCutoff, _ColorFeathering);
|
||||
}
|
||||
|
||||
|
||||
v2f vert(appdata_t v)
|
||||
{
|
||||
v2f OUT;
|
||||
UNITY_SETUP_INSTANCE_ID(v);
|
||||
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(OUT);
|
||||
float4 vPosition = UnityObjectToClipPos(v.vertex);
|
||||
OUT.worldPosition = v.vertex;
|
||||
OUT.vertex = vPosition;
|
||||
|
||||
float2 pixelSize = vPosition.w;
|
||||
pixelSize /= float2(1, 1) * abs(mul((float2x2)UNITY_MATRIX_P, _ScreenParams.xy));
|
||||
|
||||
float4 clampedRect = clamp(_ClipRect, -2e10, 2e10);
|
||||
float2 maskUV = (v.vertex.xy - clampedRect.xy) / (clampedRect.zw - clampedRect.xy);
|
||||
OUT.texcoord = TRANSFORM_TEX(v.texcoord.xy, _MainTex);
|
||||
if (_ReverseY>0)
|
||||
{
|
||||
OUT.texcoord.y=1- OUT.texcoord.y;
|
||||
}
|
||||
OUT.mask = float4(v.vertex.xy * 2 - clampedRect.xy - clampedRect.zw, 0.25 / (0.25 * half2(_UIMaskSoftnessX, _UIMaskSoftnessY) + abs(pixelSize.xy)));
|
||||
|
||||
|
||||
if (_UIVertexColorAlwaysGammaSpace)
|
||||
{
|
||||
if(!IsGammaSpace())
|
||||
{
|
||||
v.color.rgb = UIGammaToLinear(v.color.rgb);
|
||||
}
|
||||
}
|
||||
|
||||
OUT.color = v.color * _Color;
|
||||
return OUT;
|
||||
}
|
||||
|
||||
fixed4 frag(v2f IN) : SV_Target
|
||||
{
|
||||
const half alphaPrecision = half(0xff);
|
||||
const half invAlphaPrecision = half(1.0 / alphaPrecision);
|
||||
IN.color.a = round(IN.color.a * alphaPrecision) * invAlphaPrecision;
|
||||
|
||||
half4 color = half4(getRGB(_MainTex, _UTex, _VTex, IN.texcoord), IN.color.a);
|
||||
color.rgb = saturate(color*_Color);
|
||||
#ifdef UNITY_COLORSPACE_GAMMA
|
||||
#else
|
||||
color.rgb = pow(color.rgb, 2.2);
|
||||
#endif
|
||||
|
||||
// Get pixel width
|
||||
float2 pixelWidth = float2(1.0 / _MainTex_TexelSize.z, 0);
|
||||
float2 pixelHeight = float2(0, 1.0 / _MainTex_TexelSize.w);
|
||||
|
||||
// Unfeathered mask
|
||||
float mask = maskedTex2D(_MainTex, _UTex, _VTex, IN.texcoord);
|
||||
|
||||
// Feathering & smoothing
|
||||
float c = mask;
|
||||
float r = maskedTex2D(_MainTex, _UTex, _VTex, IN.texcoord + pixelWidth);
|
||||
float l = maskedTex2D(_MainTex, _UTex, _VTex, IN.texcoord - pixelWidth);
|
||||
float d = maskedTex2D(_MainTex, _UTex, _VTex, IN.texcoord + pixelHeight);
|
||||
float u = maskedTex2D(_MainTex, _UTex, _VTex, IN.texcoord - pixelHeight);
|
||||
float rd = maskedTex2D(_MainTex, _UTex, _VTex, IN.texcoord + pixelWidth + pixelHeight) * .707;
|
||||
float dl = maskedTex2D(_MainTex, _UTex, _VTex, IN.texcoord - pixelWidth + pixelHeight) * .707;
|
||||
float lu = maskedTex2D(_MainTex, _UTex, _VTex, IN.texcoord - pixelHeight - pixelWidth) * .707;
|
||||
float ur = maskedTex2D(_MainTex, _UTex, _VTex, IN.texcoord + pixelWidth - pixelHeight) * .707;
|
||||
float blurContribution = (r + l + d + u + rd + dl + lu + ur + c) * 0.12774655;
|
||||
float smoothedMask = smoothstep(_Sharpening, 1, lerp(c, blurContribution, _MaskFeathering));
|
||||
float4 result = color * smoothedMask;
|
||||
|
||||
// Despill
|
||||
// float v = (2*result.b+result.r)/4;
|
||||
// if(result.g > v) result.g = lerp(result.g, v, _Despill);
|
||||
// float4 dif = (color - result);
|
||||
// float desaturatedDif = rgb2y(dif.xyz);
|
||||
// result += lerp(0, desaturatedDif, _DespillLuminanceAdd);
|
||||
|
||||
#ifdef UNITY_UI_CLIP_RECT
|
||||
half2 m = saturate((_ClipRect.zw - _ClipRect.xy - abs(IN.mask.xy)) * IN.mask.zw);
|
||||
smoothedMask *= m.x * m.y;
|
||||
#endif
|
||||
|
||||
#ifdef UNITY_UI_ALPHACLIP
|
||||
clip (smoothedMask - 0.001);
|
||||
#endif
|
||||
|
||||
return float4(result.xyz, smoothedMask) * IN.color;
|
||||
}
|
||||
ENDCG
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c036bf8d3c966a94ca7dec96182b86b5
|
||||
ShaderImporter:
|
||||
externalObjects: {}
|
||||
defaultTextures: []
|
||||
nonModifiableTextures: []
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user