SHA256
forked from ADOFAIEditorForum/ADOFAIRenderer
332 lines
12 KiB
C#
332 lines
12 KiB
C#
using System;
|
|
using System.IO;
|
|
using ADOFAIRenderer.Util.FFmpeg;
|
|
using MelonLoader;
|
|
using UnityEngine;
|
|
using FFmpeg.AutoGen;
|
|
|
|
namespace ADOFAIRenderer.Util
|
|
{
|
|
// ReSharper disable once InconsistentNaming
|
|
public class FFmpegVideoBGPlayer : MonoBehaviour
|
|
{
|
|
private enum GPUMode
|
|
{
|
|
None,
|
|
YUV420P,
|
|
NV12
|
|
}
|
|
|
|
private FFmpegNativeDecoder? _decoder;
|
|
|
|
// CPU fallback rendering
|
|
private Texture2D? _textureRGBA;
|
|
|
|
// GPU conversion textures
|
|
private Texture2D? _textureY;
|
|
private Texture2D? _textureU;
|
|
private Texture2D? _textureV;
|
|
private Texture2D? _textureUV;
|
|
|
|
// Compute Shader variables
|
|
private ComputeShader? _computeShader;
|
|
private RenderTexture? _renderTexture;
|
|
private int _kernelIndex = -1;
|
|
|
|
private double _currentFrameTimestamp;
|
|
private double _nextFrameTimestamp;
|
|
private GPUMode _gpuMode = GPUMode.None;
|
|
private RenderTexture? _targetTexture;
|
|
private bool _isLooping;
|
|
private bool _isInitialized;
|
|
|
|
public void Init(string videoPath, RenderTexture targetTexture, bool loop)
|
|
{
|
|
CleanUp();
|
|
|
|
_targetTexture = targetTexture;
|
|
_isLooping = loop;
|
|
|
|
if (!File.Exists(videoPath))
|
|
{
|
|
Melon<RenderMod>.Logger.Warning($"[FFmpegVideoBGPlayer] 비디오 파일이 존재하지 않습니다: {videoPath}");
|
|
return;
|
|
}
|
|
|
|
try
|
|
{
|
|
_decoder = new FFmpegNativeDecoder(videoPath);
|
|
_computeShader = RenderMod.ColorConversionShader;
|
|
|
|
int w = _decoder.Width;
|
|
int h = _decoder.Height;
|
|
AVPixelFormat pixFmt = _decoder.PixelFormat;
|
|
|
|
bool gpuInitSuccess = false;
|
|
|
|
if (_computeShader != null)
|
|
{
|
|
if (pixFmt == AVPixelFormat.AV_PIX_FMT_NV12)
|
|
{
|
|
try
|
|
{
|
|
_kernelIndex = _computeShader.FindKernel("CSMain_NV12ToRGBA");
|
|
|
|
_textureY = new Texture2D(w, h, TextureFormat.R8, false);
|
|
_textureUV = new Texture2D(w / 2, h / 2, TextureFormat.RG16, false);
|
|
ConfigureTexture(_textureY);
|
|
ConfigureTexture(_textureUV);
|
|
|
|
_gpuMode = GPUMode.NV12;
|
|
gpuInitSuccess = true;
|
|
Melon<RenderMod>.Logger.Msg($"[FFmpegVideoBGPlayer] Compute Shader NV12 모드 초기화 완료");
|
|
}
|
|
catch { }
|
|
}
|
|
else if (pixFmt == AVPixelFormat.AV_PIX_FMT_YUV420P || pixFmt == AVPixelFormat.AV_PIX_FMT_YUVJ420P)
|
|
{
|
|
try
|
|
{
|
|
_kernelIndex = _computeShader.FindKernel("CSMain_YUV420PToRGBA");
|
|
|
|
_textureY = new Texture2D(w, h, TextureFormat.R8, false);
|
|
_textureU = new Texture2D(w / 2, h / 2, TextureFormat.R8, false);
|
|
_textureV = new Texture2D(w / 2, h / 2, TextureFormat.R8, false);
|
|
ConfigureTexture(_textureY);
|
|
ConfigureTexture(_textureU);
|
|
ConfigureTexture(_textureV);
|
|
|
|
_gpuMode = GPUMode.YUV420P;
|
|
gpuInitSuccess = true;
|
|
Melon<RenderMod>.Logger.Msg($"[FFmpegVideoBGPlayer] Compute Shader YUV420P 모드 초기화 완료");
|
|
}
|
|
catch { }
|
|
}
|
|
}
|
|
|
|
if (gpuInitSuccess && _computeShader != null)
|
|
{
|
|
_renderTexture = new RenderTexture(w, h, 0, RenderTextureFormat.ARGB32);
|
|
_renderTexture.enableRandomWrite = true;
|
|
_renderTexture.Create();
|
|
}
|
|
else
|
|
{
|
|
_textureRGBA = new Texture2D(w, h, TextureFormat.RGBA32, false);
|
|
ConfigureTexture(_textureRGBA);
|
|
_gpuMode = GPUMode.None;
|
|
Melon<RenderMod>.Logger.Warning($"[FFmpegVideoBGPlayer] GPU 가속 미지원 또는 로드 실패로 CPU 폴백(RGBA)으로 진행합니다.");
|
|
}
|
|
|
|
_currentFrameTimestamp = 0.0;
|
|
_nextFrameTimestamp = 0.0;
|
|
_isInitialized = true;
|
|
|
|
Melon<RenderMod>.Logger.Msg($"[FFmpegVideoBGPlayer] 초기화 성공 - 해상도: {w}x{h}, FPS: {_decoder.FPS:F2}");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Melon<RenderMod>.Logger.Error($"[FFmpegVideoBGPlayer] 초기화 중 에러 발생: {ex}");
|
|
CleanUp();
|
|
}
|
|
}
|
|
|
|
private void ConfigureTexture(Texture2D tex)
|
|
{
|
|
tex.wrapMode = TextureWrapMode.Clamp;
|
|
tex.filterMode = FilterMode.Bilinear;
|
|
}
|
|
|
|
private void Update()
|
|
{
|
|
if (!_isInitialized || _decoder == null) return;
|
|
|
|
scrConductor cond = scrConductor.instance;
|
|
if (cond == null || !cond.hasSongStarted) return;
|
|
|
|
double num4 = (cond.separateCountdownTime ? (cond.crotchetAtStart * (double)ADOBase.conductor.adjustedCountdownTicks) : 0.0);
|
|
double vidOffset = scrVfxPlus.instance ? scrVfxPlus.instance.vidOffset : 0.0;
|
|
double targetVideoTime = cond.songposition_minusi - num4 + vidOffset;
|
|
|
|
if (targetVideoTime < 0)
|
|
{
|
|
if (_targetTexture != null)
|
|
{
|
|
RenderTexture active = RenderTexture.active;
|
|
RenderTexture.active = _targetTexture;
|
|
GL.Clear(true, true, Color.black);
|
|
RenderTexture.active = active;
|
|
}
|
|
return;
|
|
}
|
|
|
|
try
|
|
{
|
|
if (targetVideoTime < _currentFrameTimestamp || targetVideoTime - _currentFrameTimestamp > 1.0)
|
|
{
|
|
_decoder.SeekToTime(targetVideoTime);
|
|
_currentFrameTimestamp = targetVideoTime;
|
|
_nextFrameTimestamp = targetVideoTime;
|
|
}
|
|
|
|
bool frameUpdated = false;
|
|
while (targetVideoTime >= _nextFrameTimestamp)
|
|
{
|
|
double timestamp;
|
|
bool success = false;
|
|
|
|
if (_gpuMode == GPUMode.NV12 && _textureY != null && _textureUV != null)
|
|
{
|
|
success = _decoder.TryReadNV12Frame(
|
|
_textureY.GetRawTextureData<byte>(),
|
|
_textureUV.GetRawTextureData<byte>(),
|
|
out timestamp
|
|
);
|
|
}
|
|
else if (_gpuMode == GPUMode.YUV420P && _textureY != null && _textureU != null && _textureV != null)
|
|
{
|
|
success = _decoder.TryReadYUV420PFrame(
|
|
_textureY.GetRawTextureData<byte>(),
|
|
_textureU.GetRawTextureData<byte>(),
|
|
_textureV.GetRawTextureData<byte>(),
|
|
out timestamp
|
|
);
|
|
}
|
|
else
|
|
{
|
|
if (_textureRGBA != null)
|
|
{
|
|
success = _decoder.TryReadFrame(
|
|
_textureRGBA.GetRawTextureData<byte>(),
|
|
vflip: true,
|
|
out timestamp
|
|
);
|
|
}
|
|
}
|
|
|
|
if (success)
|
|
{
|
|
_currentFrameTimestamp = timestamp;
|
|
_nextFrameTimestamp = timestamp + (1.0 / _decoder.FPS);
|
|
frameUpdated = true;
|
|
}
|
|
else
|
|
{
|
|
if (_isLooping)
|
|
{
|
|
_decoder.SeekToTime(0);
|
|
_currentFrameTimestamp = 0;
|
|
_nextFrameTimestamp = 1.0 / _decoder.FPS;
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (frameUpdated)
|
|
{
|
|
RenderToTarget();
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Melon<RenderMod>.Logger.Error($"[FFmpegVideoBGPlayer] Update 재생 중 에러 발생: {ex}");
|
|
_isInitialized = false;
|
|
}
|
|
}
|
|
|
|
private void RenderToTarget()
|
|
{
|
|
if (_decoder == null || _targetTexture == null) return;
|
|
|
|
int w = _decoder.Width;
|
|
int h = _decoder.Height;
|
|
|
|
if (_gpuMode == GPUMode.NV12 && _textureY != null && _textureUV != null && _computeShader != null && _renderTexture != null)
|
|
{
|
|
_textureY.Apply();
|
|
_textureUV.Apply();
|
|
|
|
_computeShader.SetTexture(_kernelIndex, "_YTex", _textureY);
|
|
_computeShader.SetTexture(_kernelIndex, "_UVTex", _textureUV);
|
|
_computeShader.SetTexture(_kernelIndex, "_Result", _renderTexture);
|
|
_computeShader.SetInt("Width", w);
|
|
_computeShader.SetInt("Height", h);
|
|
|
|
int threadGroupsX = (w + 7) / 8;
|
|
int threadGroupsY = (h + 7) / 8;
|
|
_computeShader.Dispatch(_kernelIndex, threadGroupsX, threadGroupsY, 1);
|
|
|
|
Graphics.Blit(_renderTexture, _targetTexture);
|
|
}
|
|
else if (_gpuMode == GPUMode.YUV420P && _textureY != null && _textureU != null && _textureV != null && _computeShader != null && _renderTexture != null)
|
|
{
|
|
_textureY.Apply();
|
|
_textureU.Apply();
|
|
_textureV.Apply();
|
|
|
|
_computeShader.SetTexture(_kernelIndex, "_YTex", _textureY);
|
|
_computeShader.SetTexture(_kernelIndex, "_UTex", _textureU);
|
|
_computeShader.SetTexture(_kernelIndex, "_VTex", _textureV);
|
|
_computeShader.SetTexture(_kernelIndex, "_Result", _renderTexture);
|
|
_computeShader.SetInt("Width", w);
|
|
_computeShader.SetInt("Height", h);
|
|
|
|
int threadGroupsX = (w + 7) / 8;
|
|
int threadGroupsY = (h + 7) / 8;
|
|
_computeShader.Dispatch(_kernelIndex, threadGroupsX, threadGroupsY, 1);
|
|
|
|
Graphics.Blit(_renderTexture, _targetTexture);
|
|
}
|
|
else if (_textureRGBA != null)
|
|
{
|
|
_textureRGBA.Apply();
|
|
Graphics.Blit(_textureRGBA, _targetTexture);
|
|
}
|
|
}
|
|
|
|
private void OnDestroy()
|
|
{
|
|
CleanUp();
|
|
}
|
|
|
|
public void CleanUp()
|
|
{
|
|
_isInitialized = false;
|
|
|
|
if (_decoder != null)
|
|
{
|
|
_decoder.Dispose();
|
|
_decoder = null;
|
|
Melon<RenderMod>.Logger.Msg("[FFmpegVideoBGPlayer] 디코더 해제 완료.");
|
|
}
|
|
|
|
if (_textureRGBA != null)
|
|
{
|
|
Destroy(_textureRGBA);
|
|
_textureRGBA = null;
|
|
}
|
|
|
|
if (_textureY != null) Destroy(_textureY);
|
|
if (_textureU != null) Destroy(_textureU);
|
|
if (_textureV != null) Destroy(_textureV);
|
|
if (_textureUV != null) Destroy(_textureUV);
|
|
|
|
_textureY = null;
|
|
_textureU = null;
|
|
_textureV = null;
|
|
_textureUV = null;
|
|
|
|
if (_renderTexture != null)
|
|
{
|
|
_renderTexture.Release();
|
|
Destroy(_renderTexture);
|
|
_renderTexture = null;
|
|
}
|
|
}
|
|
}
|
|
}
|