Hanjie's Blog

一只有理想的羊驼

Unity3D中,我需要从C++的DLL中获取摄像头取得的图像数据(uchar *),并且用Rawimage显示出来。

程序包括C++的DLL接口部分和Unity3D C#的接口部分。

使用VS 2015建立一个空白的Visual C++ Win32控制台应用程序,使用.NET Framework 4.7.2,应用程序类型为DLL。

C++ CameraDll.cpp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include "Camera.h"

#define DLLExport __declspec(dllexport)

extern "C" {
DLLExport Camera* CreateCamera(int width, int height, int fps) {
Camera* obj = new Camera(width, height, fps);
return obj;
}

DLLExport void DestroyCamera(Camera* obj) {
delete obj;
obj = NULL;
}

DLLExport bool GetImage(Camera* obj, unsigned char *& data, int &size) {
return obj->GetImage(data, size);
}
}

C++ Camera.cpp:

1
2
3
4
5
6
7
8
...
bool Camera::GetImage(unsigned char*& rgb24_pixels, int& size) {
...
rgb24_pixels = img.data;
size = img.cols * img.rows * img.channels();
return true;
}
...

编译Release x64后,将dll文件放到Unity项目的Assets\Plugins文件夹中。Unity中建立Create Empty对象,添加一个脚本Camera.cs

C# Camera.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using UnityEngine.UI;
using System.Runtime.InteropServices;

public class Camera : MonoBehaviour
{
[DllImport("Camera")]
private static extern IntPtr CreateCamera(int width, int height, int fps);

[DllImport("Camera")]
private static extern void DestroyCamera(IntPtr obj);

[DllImport("Camera")]
private static extern bool GetImage(IntPtr obj, ref IntPtr data, ref int size);

private int width_ = 640;
private int height_ = 480;
private int fps_ = 30;

private IntPtr Camera_ = IntPtr.Zero;
private IntPtr data_ = IntPtr.Zero;
private int size_ = 0;
private byte[] buffer_ = null;

private RawImage background_ = null;
private Texture2D tex_ = null;

// Start is called before the first frame update
void Start()
{
background_ = GameObject.FindWithTag("BackgroundImage").GetComponent<RawImage>();

Camera_ = CreateCamera(width_, height_, fps_);
}

void OnDestroy()
{
if (Camera_ != IntPtr.Zero)
{
DestroyCamera(Camera_);
Camera_ = IntPtr.Zero;
}
}

// Update is called once per frame
void Update()
{
if (Camera_ == IntPtr.Zero) return;

bool flag = GetImage(Camera_, ref data_, ref size_);

if (!flag || size_ <= 0) return;

if (buffer_ == null) {
buffer_ = new byte[size_];
}

if (tex_ == null) {
tex_ = new Texture2D(width_, height_, TextureFormat.RGB24, false, false);
}

System.Runtime.InteropServices.Marshal.Copy(data_, buffer_, 0, size_);
tex_.LoadRawTextureData(buffer_);
tex_.Apply();

background_.texture = tex_;

// byte[] bytes = tex_.EncodeToPNG();
// File.WriteAllBytes("Image.png", bytes);
}
}

使用BestHttp的Websocket接收Binary图片数据时,发现接收一段时间后程序会卡掉,怀疑是buffer size太小。

修改/Assets/Best HTTP (Pro)/BestHTTP/Extensions/ReadOnlyBufferedStream.cs 中的READBUFFER值,改为:

1
public const int READBUFFER = 81920;

问题解决。

本文主要参考博客Unity3D 与 Python 的 Socket 通信简单指南

Unity3D Client端

Unity3D新建一个Project,Window - Asset Store,搜索Socket.IO for Unity进行安装。Import成功后,Project目录Assets中会出现SocketIO文档。

Project目录中选择Assets - SoketIO - Prefabs,将该目录下的SocketIO拖入到场景中,如下图所示:

检查场景中SocketIOUrl属性:

1
ws://127.0.0.1:4567/socket.io/?EIO=4&transport=websocket

SampleScene栏目中右键,Create Empty建立一个空白的Object,命名为TestSocket。选择TestSocket,在Inspector窗口中点击Add Component - New script,命名为TestSocketScript,双击脚本进行编辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
using System.Collections.Generic;
using UnityEngine;
using SocketIO;

public class TestSocketScript : MonoBehaviour
{
// 在 Editor 里把 SocketIO 拖过来
public SocketIOComponent sio;
void Start()
{
if (sio == null)
Debug.LogError("Drop a SocketIOComponent to Me!");
// 声明 connect 事件和 server_sent 事件的回调函数
sio.On("connect", OnConnect);
sio.On("server_sent", OnReceive);
}

/// <summary>
/// connect 事件的回调函数
/// </summary>
/// <param name="obj"></param>
void OnConnect(SocketIOEvent obj)
{
Debug.Log("Connection Open");
OnReceive(obj);
}

/// <summary>
/// 接收到 server_sent 事件的回调函数
/// </summary>
/// <param name="obj">SocketIOEvent</param>
void OnReceive(SocketIOEvent obj)
{
// 1. 接收并输出 Server 传递过来的数字
JSONObject jsonObject = obj.data;
string rcv_nbr = jsonObject.GetField("nbr").str;
Debug.Log("Recieved From Server : " + rcv_nbr);
// 2. 将数字 +1 并返回给 Server
try
{
int int_nbr = int.Parse(rcv_nbr);
SendToServer(int_nbr + 1);
}
catch
{
}
}

/// <summary>
/// 将数字发给 Server
/// </summary>
/// <param name="_nbr">发送的数字</param>
void SendToServer(int _nbr)
{
Dictionary<string, string> data = new Dictionary<string, string>();
data["nbr"] = _nbr.ToString();
sio.Emit("client_sent", new JSONObject(data));
}
}

此时Inspector窗口的Test Socket Script(Script)中会新增一个Sio属性:

sio1

SampleScene栏目的SocketIO拖动到Sio属性里:

sio2

Python版Server端

1
2
3
sudo pip2 install python-socketio
sudo pip2 install eventlet
sudo pip2 install Flask
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import socketio
import eventlet
import eventlet.wsgi
from flask import Flask

sio = socketio.Server()
app = Flask(__name__)

# "连接建立的回调函数"
@sio.on('connect')
def on_connect(sid, environ):
print("connect ", sid)
send_to_client(101)

# "接收 Client 事件 (client_sent) 的回调函数"
@sio.on('client_sent')
def on_revieve(sid, data):
if data:
print ('From Client : %s' % data['nbr'])
send_to_client(int(data['nbr']) + 1)
else:
print ('Recieved Empty Data!')

# "向 Client 发送数字"
def send_to_client(_nbr):
sio.emit(
'server_sent',
data = {'nbr':_nbr.__str__()},
skip_sid=True)

if __name__ == '__main__':
app = socketio.Middleware(sio, app)
eventlet.wsgi.server(eventlet.listen(('', 4567)), app)

先运行Python程序,再运行Unity3D场景:

0%