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