问题
Unity中,使用Recorder录制视频时,因为选择的帧率不同,导致录制的视频中的VideoPlayer播放视频不正常。
不正常体现在:Recorder的TargetFPS和VideoPlayer播放的视频的帧率不一致,导致VideoPlayer播放的视频被减慢和加快(为了去和TargetFPS匹配),而且原视频对应的音频的播放速度也和原视频不匹配。
需求
不同的帧率下VideoPlayer的视频的画面和声音播放速度均为原速。
允许从中途指定帧数开始播放。(使用本功能时,最好将视频先播放一次,至少播放至“指定帧数”,确保设置videoPlayer的frame能起效)
实现
做法:使用VideoPlayer的StepForward去播放视频,因为这种播放方式下视频不播声音,因此需将视频转为一个mp3文件,使用AudioSource去专门播放声音。
image.png
MyVideoPlayer.cs
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Video;
public class MyVideoPlayer : MonoBehaviour
{
[SerializeField]
private VideoPlayer _videoPlayer;
[SerializeField]
private AudioSource _audioSource;
private float _beginTime;
private float CurTime{ get { return Time.time - _beginTime; } }
private long _beginFrameIndex;
private IEnumerator _playVideoCoroutine;
[SerializeField]
private bool _shouldAutoStart = true;
private void Start()
{
Init();
}
private void OnDestroy()
{
Destroy();
}
public void Init()
{
_videoPlayer.playOnAwake = false;
_videoPlayer.isLooping = false;
_videoPlayer.Stop();
_audioSource.playOnAwake = false;
_audioSource.loop = false;
_audioSource.Stop();
if (_shouldAutoStart)
{
Play();
}
}
public void Destroy()
{ }
// 从触发后,等待多久后开始播放,从第几帧开始播放,是否要清除视频缓存
public void Play(float delayTime = 0, long frameIndex = 0, bool shouldClearTexture = true)
{
Stop();
// 为了确保跳到指定帧直接开始播放时,能跳到指定帧(若指定帧未准备好,_videoPlayer的frame设置不会成功,会是-1)
// 实际用时,为确保设置frame有效,较好的做法:让videoPlayer的视频先完整播放一次(至少播到接下来要直接跳到的位置处),再使用从指定帧开始播放的功能
_videoPlayer.Prepare();
_beginFrameIndex = frameIndex;
if (shouldClearTexture)
{
ReleaseTexture();
}
DelayCall(delayTime, () =>
{
_beginTime = Time.time;
PlayVideo();
PlayAudio();
});
void ReleaseTexture()
{
_videoPlayer.targetTexture.Release();
_videoPlayer.targetTexture.MarkRestoreExpected();
}
void PlayVideo()
{
_videoPlayer.frame = _beginFrameIndex;
_videoPlayer.Play();
_playVideoCoroutine = PlayVideoCoroutine();
StartCoroutine(_playVideoCoroutine);
}
void PlayAudio()
{
_audioSource.time = _beginFrameIndex / _videoPlayer.frameRate;
_audioSource.Play();
}
IEnumerator PlayVideoCoroutine()
{
while (true)
{
_videoPlayer.Pause();
if ((_videoPlayer.frame - _beginFrameIndex) / _videoPlayer.frameRate < CurTime)
{
_videoPlayer.StepForward();
}
yield return null;
// 循环播放
if (_videoPlayer.frame ==(long)_videoPlayer.frameCount - 1)
{
Play(0, 0, false);
}
}
}
}
public void Stop()
{
Stop(0f);
}
public void Stop(float delayTime)
{
DelayCall(delayTime, () =>
{
Stop(out long frame, out float time);
});
}
/// <summary>
/// 停止时,视频已播放了多少帧,多少秒(从视频开头计)
/// </summary>
/// <returns></returns>
public void Stop(out long frame, out float time)
{
StopVideo();
StopAudio();
frame = _videoPlayer.frame;
time = frame / _videoPlayer.frameRate;
#if UNITY_EDITOR
Debug.Log("停止播放,此时的frame:" + frame + " time:" + time);
#endif
void StopVideo()
{
if (_playVideoCoroutine != null)
{
StopCoroutine(_playVideoCoroutine);
_videoPlayer.Pause();
_playVideoCoroutine = null;
}
}
void StopAudio()
{
_audioSource.Pause();
}
}
private void DelayCall(float delayTime, Action action)
{
StartCoroutine(DelayCallCoroutine());
IEnumerator DelayCallCoroutine()
{
yield return new WaitForSeconds(delayTime);
action?.Invoke();
}
}
}
测试
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TestMyVideoPlayer : MonoBehaviour
{
[SerializeField]
private MyVideoPlayer _myVideoPlayer;
void Start()
{
}
// Update is called once per frame
void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
Test1();
}
if (Input.GetKeyDown(KeyCode.A))
{
Test2();
}
}
private void Test1()
{
_myVideoPlayer.Play(3);
_myVideoPlayer.Stop(15);
}
private void Test2()
{
var frameIndex = 360;
_myVideoPlayer.Play(1, frameIndex, false);
}
}
网友评论