Mac上のPython -> Unityでのローカルの映像転送にメモリマップトファイルを利用する

Macで映像転送と言えばSyphonですが、PythonSyphonできないぽかったのでメモリマップトファイルを利用して実装しました。メモリマップトファイルはメモリを映像のような大量のデータだとソケット通信とかでは速度が間に合わないので、これ一択ぽいです。メモリマップトファイルにバイト列として画像を置いて通信する仕組みです。

注意点としては、映像のフォーマットを送受信出来ないので、決めうちにする必要があります。今回は1200x1200のRGB画像を送る前提のサンプルコードを載せておきます。なおコードは元のプロジェクトから抜き出して書いているもので、テストしておりません。ライセンスはパブリックドメインです。

Unityは、Monoではなく.NET Coreでないとメモリマップトファイルが利用出来なかった気がします。Unity側ではRawImageにメモリマップトファイルから取り出したTextureを貼ってます。Textureは明示的にDestoryしないとメモリーリークするので気をつけて。

利用ライブラリ

Pythonサンプルコード

import os
import mmap

DATA_LENGTH = 4320000

fd = os.open('/tmp/mmappython', os.O_CREAT | os.O_TRUNC | os.O_RDWR)
assert os.write(fd, b'\x00' * DATA_LENGTH) == DATA_LENGTH

buf = mmap.mmap(fd, DATA_LENGTH, mmap.MAP_SHARED, mmap.PROT_WRITE)

buf.seek(0)
byte_data = b'\x00' * DATA_LENGTH
buf.write(byte_data)

buf.close()

Unityサンプルコード

using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.IO.MemoryMappedFiles;

using UnityEngine;
using UnityEngine.UI;

public class MmfManager : MonoBehaviour {
    private string filePath = "/tmp/mmappython";
    private const int dataLength = 4320000;
    private RawImage rawImage;

	private MemoryMappedFile mmf;
    private MemoryMappedViewStream stream;

    public void InitMemoryMappedFile()
    {
        if (File.Exists(filePath) == false)
        {
            byte[] bs = new byte[dataLength];
            using(FileStream fs = File.Open(filePath, FileMode.Create))
            {
                fs.Write(bs, 0, bs.Length);
            }
        }
        mmf = MemoryMappedFile.CreateFromFile(filePath, FileMode.Open);

        Debug.Log(filePath);

        stream = mmf.CreateViewStream();
        Debug.Log(stream.Capacity);
        Debug.Log(stream.Length);
    }

	void Start () {
        rawImage = GetComponent<RawImage>();
	}
	
	void Update () {
        stream.Seek(0, SeekOrigin.Begin);
        byte[] buffer = new byte[dataLength];
        int retVal = stream.Read(buffer, 0, dataLength);
        if (retVal == dataLength)
        {
            Texture oldTex = rawImage.texture;
            Texture2D tex = new Texture2D(1200, 1200, TextureFormat.RGB24, false);
            tex.LoadRawTextureData(buffer);
            tex.Apply();
            rawImage.texture = tex;
            Destroy(oldTex);
        }
    }

    void OnDestroy()
    {
        stream.Close();
        stream.Dispose();
        mmf.Dispose();
        stream = null;
        mmf = null;
    }
}