Files
tysdk-intergration/sdk-intergration/LocalPackages/LeviathanVideo/Runtime/LeviathanVideoDecoder.cs
2026-03-24 11:43:22 +08:00

367 lines
12 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*----------------------------------------------------------------
// 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