视频同步不同于图片帧同步,因为图片帧同步只要传递帧数,然后加载相应的图片就行。
一、使用VideoPlayer做视频时间同步
最开始想到的方法就是这样,因为时间是视频位置的控制。使用UDP测试之后发现,在给VideoPlayer设置播放时间的时候,会有卡顿的现象。估计是要解析这个时间的视频数据,所以就觉得可能是VideoPlayer解析能力不能,其实本来也不行。于是就用了EasyMovieTexture插件。
修改程序之后,发现还是有一样的问题,于是就还是想换到视频的帧同步。但是EasyMovieTexture这个插件是以时间获取的视频图像,解析的帧和时间有关系的,所以时间的误差会导致帧图片不一样。因此又换回VideoPlayer做视频帧同步。
二、使用VideoPlayer做视频帧同步
VideoPlayer可以监听帧准备完毕的事件的
首先说一下整体思路,同步如果要不卡顿,首先肯定要帧缓存的。作为服务器的一方发出帧数的信息,接收的一方从保存的帧缓存中找到图像。
这里有几个问题:1、保存的图像是要占内存的,可能几百兆的视频,图片帧能到几个G,内存会炸,Unity会蹦。因此肯定缓存服务器当前帧之后的几个帧数。
2.图片保存在一个List中,图片和帧数应该组成一个数据包。在这帧数据使用之后,需要Destory掉图片,不然资源不会自己释放的。
3.VideoPlayer在播放的时候,帧准备好会frameReady。但是会有概率掉帧的,就是你没有缓存到某一帧,然而服务器请求到了那一帧,最好的办法就是跳过,直接丢掉这一帧。因为如果再去缓存这一帧,需要时间,当你找到,下一帧也到了。掉帧的原因可他获取帧的方法有关应该,播放速度越快,掉帧越明显。
4.VideoPlayer播放速度,肯定比服务器发送指令的速度要快一下,不然就会卡顿的,就相当于你解析速度没有播放速度快。因为当VideoPlayer帧缓存到一定数量的时候,停止播放。在你有数据空间的时候再开启。还要检测List最前的数据,是不是过期了,过期需要删除,腾出空间。
video.frameReady += FrameReady;
video.sendFrameReadyEvents = true;
private void FrameReady(VideoPlayer source, long frameIdx)
{
RenderTexture buffertexture = source.texture as RenderTexture;
RenderTexture.active = buffertexture;
Texture2D texture = new Texture2D(buffertexture.width, buffertexture.height, TextureFormat.RGB24, false);
texture.ReadPixels(new Rect(0, 0, buffertexture.width, buffertexture.height), 0, 0);
texture.Apply();
if (frameIdx == nowIndex)
{
bgImage.texture = texture;
return;
}
if (FrameContain((int)frameIdx))
return;
if ((frameIdx - nowIndex < 99 && frameIdx > nowIndex) || (int)video.frameCount + frameIdx - nowIndex < 99)
{
if(allmovieTexture.Count > 1 && IsOldData())
{
Texture unuseTexture = allmovieTexture[0]._texture;
GameObject.DestroyImmediate(unuseTexture);
allmovieTexture.RemoveAt(0);
}
allmovieTexture.Add(new FrameDate() { index = (int)frameIdx, _texture = texture });
}
else if(allmovieTexture.Count < 50)
{
video.frame = nowIndex + 5;
}
else
{
source.Stop();
video.frame = frameIdx - 15;
}
}
if (FrameContain(nowIndex))
{
int bufferindex = GetFrame(nowIndex);
bgImage.texture = allmovieTexture[bufferindex]._texture;
}
if(!video.isPlaying)
{
if ((allmovieTexture.Count > 1 && IsOldData()))
{
Texture unuseTexture = allmovieTexture[0]._texture;
GameObject.DestroyImmediate(unuseTexture);
allmovieTexture.RemoveAt(0);
video.Play();
}
if (allmovieTexture.Count < 50)
{
video.Play();
}
}
private bool FrameContain(int index)
{
for (int i = 0; i < allmovieTexture.Count; i++)
{
if (allmovieTexture[i] != null && allmovieTexture[i].index == index)
{
return true;
}
}
return false;
}
private int GetFrame(int index)
{
for (int i = 0; i < allmovieTexture.Count; i++)
{
if (allmovieTexture[i] != null && allmovieTexture[i].index == index)
{
return i;
}
}
return -1;
}
private bool IsOldData()
{
int frameIdx = allmovieTexture[0].index;
if (!((frameIdx - nowIndex < 99 && frameIdx > nowIndex) || (int)video.frameCount + frameIdx - nowIndex < 99))
{
return true;
}
return false;
}
代码比较乱,因为工程有其他部分,也不好截取。最好自己按照思路写吧。
我再局域网用UDP测试过,服务器用videoPlayer正常播放,客户端用帧同步。
不论是服务器先启动还是客户端先启动,都能在2秒以内同步上。客户端播放开始因为没帧缓存,会有一些图像跳动的现象,但几秒后就好了。
如果有其他好的方法也希望能留言告诉我一下,谢谢!