1 #include <windows.h>
2 #include "shared-memory-queue.h"
3 #include "tiny-nv12-scale.h"
4 
5 #define VIDEO_NAME L"OBSVirtualCamVideo"
6 
7 enum queue_type {
8 	SHARED_QUEUE_TYPE_VIDEO,
9 };
10 
11 struct queue_header {
12 	volatile uint32_t write_idx;
13 	volatile uint32_t read_idx;
14 	volatile uint32_t state;
15 
16 	uint32_t offsets[3];
17 
18 	uint32_t type;
19 
20 	uint32_t cx;
21 	uint32_t cy;
22 	uint64_t interval;
23 
24 	uint32_t reserved[8];
25 };
26 
27 struct video_queue {
28 	HANDLE handle;
29 	bool ready_to_read;
30 	struct queue_header *header;
31 	uint64_t *ts[3];
32 	uint8_t *frame[3];
33 	long last_inc;
34 	int dup_counter;
35 	bool is_writer;
36 };
37 
38 #define ALIGN_SIZE(size, align) size = (((size) + (align - 1)) & (~(align - 1)))
39 #define FRAME_HEADER_SIZE 32
40 
video_queue_create(uint32_t cx,uint32_t cy,uint64_t interval)41 video_queue_t *video_queue_create(uint32_t cx, uint32_t cy, uint64_t interval)
42 {
43 	struct video_queue vq = {0};
44 	struct video_queue *pvq;
45 	DWORD frame_size = cx * cy * 3 / 2;
46 	uint32_t offset_frame[3];
47 	DWORD size;
48 
49 	size = sizeof(struct queue_header);
50 
51 	ALIGN_SIZE(size, 32);
52 
53 	offset_frame[0] = size;
54 	size += frame_size + FRAME_HEADER_SIZE;
55 	ALIGN_SIZE(size, 32);
56 
57 	offset_frame[1] = size;
58 	size += frame_size + FRAME_HEADER_SIZE;
59 	ALIGN_SIZE(size, 32);
60 
61 	offset_frame[2] = size;
62 	size += frame_size + FRAME_HEADER_SIZE;
63 	ALIGN_SIZE(size, 32);
64 
65 	struct queue_header header = {0};
66 
67 	header.state = SHARED_QUEUE_STATE_STARTING;
68 	header.cx = cx;
69 	header.cy = cy;
70 	header.interval = interval;
71 	vq.is_writer = true;
72 
73 	for (size_t i = 0; i < 3; i++) {
74 		uint32_t off = offset_frame[i];
75 		header.offsets[i] = off;
76 	}
77 
78 	/* fail if already in use */
79 	vq.handle = OpenFileMappingW(FILE_MAP_READ, false, VIDEO_NAME);
80 	if (vq.handle) {
81 		CloseHandle(vq.handle);
82 		return NULL;
83 	}
84 
85 	vq.handle = CreateFileMappingW(INVALID_HANDLE_VALUE, NULL,
86 				       PAGE_READWRITE, 0, size, VIDEO_NAME);
87 	if (!vq.handle) {
88 		return NULL;
89 	}
90 
91 	vq.header = (struct queue_header *)MapViewOfFile(
92 		vq.handle, FILE_MAP_ALL_ACCESS, 0, 0, 0);
93 	if (!vq.header) {
94 		CloseHandle(vq.handle);
95 		return NULL;
96 	}
97 	memcpy(vq.header, &header, sizeof(header));
98 
99 	for (size_t i = 0; i < 3; i++) {
100 		uint32_t off = offset_frame[i];
101 		vq.ts[i] = (uint64_t *)(((uint8_t *)vq.header) + off);
102 		vq.frame[i] = ((uint8_t *)vq.header) + off + FRAME_HEADER_SIZE;
103 	}
104 	pvq = malloc(sizeof(vq));
105 	if (!pvq) {
106 		CloseHandle(vq.handle);
107 		return NULL;
108 	}
109 	memcpy(pvq, &vq, sizeof(vq));
110 	return pvq;
111 }
112 
video_queue_open()113 video_queue_t *video_queue_open()
114 {
115 	struct video_queue vq = {0};
116 
117 	vq.handle = OpenFileMappingW(FILE_MAP_READ, false, VIDEO_NAME);
118 	if (!vq.handle) {
119 		return NULL;
120 	}
121 
122 	vq.header = (struct queue_header *)MapViewOfFile(
123 		vq.handle, FILE_MAP_READ, 0, 0, 0);
124 	if (!vq.header) {
125 		CloseHandle(vq.handle);
126 		return NULL;
127 	}
128 
129 	struct video_queue *pvq = malloc(sizeof(vq));
130 	if (!pvq) {
131 		CloseHandle(vq.handle);
132 		return NULL;
133 	}
134 	memcpy(pvq, &vq, sizeof(vq));
135 	return pvq;
136 }
137 
video_queue_close(video_queue_t * vq)138 void video_queue_close(video_queue_t *vq)
139 {
140 	if (!vq) {
141 		return;
142 	}
143 	if (vq->is_writer) {
144 		vq->header->state = SHARED_QUEUE_STATE_STOPPING;
145 	}
146 
147 	UnmapViewOfFile(vq->header);
148 	CloseHandle(vq->handle);
149 	free(vq);
150 }
151 
video_queue_get_info(video_queue_t * vq,uint32_t * cx,uint32_t * cy,uint64_t * interval)152 void video_queue_get_info(video_queue_t *vq, uint32_t *cx, uint32_t *cy,
153 			  uint64_t *interval)
154 {
155 	struct queue_header *qh = vq->header;
156 	*cx = qh->cx;
157 	*cy = qh->cy;
158 	*interval = qh->interval;
159 }
160 
161 #define get_idx(inc) ((unsigned long)inc % 3)
162 
video_queue_write(video_queue_t * vq,uint8_t ** data,uint32_t * linesize,uint64_t timestamp)163 void video_queue_write(video_queue_t *vq, uint8_t **data, uint32_t *linesize,
164 		       uint64_t timestamp)
165 {
166 	struct queue_header *qh = vq->header;
167 	long inc = ++qh->write_idx;
168 
169 	unsigned long idx = get_idx(inc);
170 	size_t size = linesize[0] * qh->cy;
171 
172 	*vq->ts[idx] = timestamp;
173 	memcpy(vq->frame[idx], data[0], size);
174 	memcpy(vq->frame[idx] + size, data[1], size / 2);
175 
176 	qh->read_idx = inc;
177 	qh->state = SHARED_QUEUE_STATE_READY;
178 }
179 
video_queue_state(video_queue_t * vq)180 enum queue_state video_queue_state(video_queue_t *vq)
181 {
182 	if (!vq) {
183 		return SHARED_QUEUE_STATE_INVALID;
184 	}
185 
186 	enum queue_state state = (enum queue_state)vq->header->state;
187 	if (!vq->ready_to_read && state == SHARED_QUEUE_STATE_READY) {
188 		for (size_t i = 0; i < 3; i++) {
189 			size_t off = vq->header->offsets[i];
190 			vq->ts[i] = (uint64_t *)(((uint8_t *)vq->header) + off);
191 			vq->frame[i] = ((uint8_t *)vq->header) + off +
192 				       FRAME_HEADER_SIZE;
193 		}
194 		vq->ready_to_read = true;
195 	}
196 
197 	return state;
198 }
199 
video_queue_read(video_queue_t * vq,nv12_scale_t * scale,void * dst,uint64_t * ts)200 bool video_queue_read(video_queue_t *vq, nv12_scale_t *scale, void *dst,
201 		      uint64_t *ts)
202 {
203 	struct queue_header *qh = vq->header;
204 	long inc = qh->read_idx;
205 
206 	if (qh->state == SHARED_QUEUE_STATE_STOPPING) {
207 		return false;
208 	}
209 
210 	if (inc == vq->last_inc) {
211 		if (++vq->dup_counter == 10) {
212 			return false;
213 		}
214 	} else {
215 		vq->dup_counter = 0;
216 		vq->last_inc = inc;
217 	}
218 
219 	unsigned long idx = get_idx(inc);
220 
221 	*ts = *vq->ts[idx];
222 
223 	nv12_do_scale(scale, dst, vq->frame[idx]);
224 	return true;
225 }
226