Unity 多机器的视频不卡帧同步

视频同步不同于图片帧同步,因为图片帧同步只要传递帧数,然后加载相应的图片就行。

一、使用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秒以内同步上。客户端播放开始因为没帧缓存,会有一些图像跳动的现象,但几秒后就好了。

如果有其他好的方法也希望能留言告诉我一下,谢谢!

你可能感兴趣的:(Unity效果实现思路)