1 /* GStreamer
2 * Copyright (C) 2021 Seungha Yang <seungha@centricular.com>
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Library General Public License for more details.
13 *
14 * You should have received a copy of the GNU Library General Public
15 * License along with this library; if not, write to the
16 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
18 */
19
20 #ifdef HAVE_CONFIG_H
21 #include "config.h"
22 #endif
23
24 #include "gstasioutils.h"
25 #include <windows.h>
26 #include <string.h>
27 #include <atlconv.h>
28
29 static gboolean
gst_asio_enum_check_class_root(GstAsioDeviceInfo * info,LPCWSTR clsid)30 gst_asio_enum_check_class_root (GstAsioDeviceInfo * info, LPCWSTR clsid)
31 {
32 LSTATUS status;
33 HKEY root_key = nullptr;
34 HKEY device_key = nullptr;
35 HKEY proc_server_key = nullptr;
36 DWORD type = REG_SZ;
37 CHAR data[256];
38 DWORD size = sizeof (data);
39 gboolean ret = FALSE;
40
41 status = RegOpenKeyExW (HKEY_CLASSES_ROOT, L"clsid", 0, KEY_READ, &root_key);
42 if (status != ERROR_SUCCESS)
43 return FALSE;
44
45 /* Read registry HKEY_CLASS_ROOT/CLSID/{device-clsid} */
46 status = RegOpenKeyExW (root_key, clsid, 0, KEY_READ, &device_key);
47 if (status != ERROR_SUCCESS)
48 goto done;
49
50 /* ThreadingModel describes COM apartment */
51 status = RegOpenKeyExW (device_key,
52 L"InprocServer32", 0, KEY_READ, &proc_server_key);
53 if (status != ERROR_SUCCESS)
54 goto done;
55
56 status = RegQueryValueExA (proc_server_key,
57 "ThreadingModel", nullptr, &type, (LPBYTE) data, &size);
58 if (status != ERROR_SUCCESS)
59 goto done;
60
61 if (g_ascii_strcasecmp (data, "Both") == 0 ||
62 g_ascii_strcasecmp (data, "Free") == 0) {
63 info->sta_model = FALSE;
64 } else {
65 info->sta_model = TRUE;
66 }
67
68 ret = TRUE;
69
70 done:
71 if (proc_server_key)
72 RegCloseKey (proc_server_key);
73
74 if (device_key)
75 RegCloseKey (device_key);
76
77 if (root_key)
78 RegCloseKey (root_key);
79
80 return ret;
81 }
82
83 static GstAsioDeviceInfo *
gst_asio_enum_new_device_info_from_reg(HKEY reg_key,LPWSTR key_name)84 gst_asio_enum_new_device_info_from_reg (HKEY reg_key, LPWSTR key_name)
85 {
86 LSTATUS status;
87 HKEY sub_key = nullptr;
88 WCHAR clsid_data[256];
89 WCHAR desc_data[256];
90 DWORD type = REG_SZ;
91 DWORD size = sizeof (clsid_data);
92 GstAsioDeviceInfo *ret = nullptr;
93 CLSID id;
94 HRESULT hr;
95
96 USES_CONVERSION;
97
98 status = RegOpenKeyExW (reg_key, key_name, 0, KEY_READ, &sub_key);
99 if (status != ERROR_SUCCESS)
100 return nullptr;
101
102 /* find CLSID value, used for CoCreateInstance */
103 status = RegQueryValueExW (sub_key,
104 L"clsid", 0, &type, (LPBYTE) clsid_data, &size);
105 if (status != ERROR_SUCCESS)
106 goto done;
107
108 hr = CLSIDFromString (W2COLE (clsid_data), &id);
109 if (FAILED (hr))
110 goto done;
111
112 ret = g_new0 (GstAsioDeviceInfo, 1);
113 ret->clsid = id;
114 ret->driver_name = g_utf16_to_utf8 ((gunichar2 *) key_name, -1,
115 nullptr, nullptr, nullptr);
116
117 /* human readable device description */
118 status = RegQueryValueExW (sub_key,
119 L"description", 0, &type, (LPBYTE) desc_data, &size);
120 if (status != ERROR_SUCCESS) {
121 GST_WARNING ("no description");
122 ret->driver_desc = g_strdup (ret->driver_name);
123 } else {
124 ret->driver_desc = g_utf16_to_utf8 ((gunichar2 *) desc_data, -1,
125 nullptr, nullptr, nullptr);
126 }
127
128 /* Check COM threading model */
129 if (!gst_asio_enum_check_class_root (ret, clsid_data)) {
130 gst_asio_device_info_free (ret);
131 ret = nullptr;
132 }
133
134 done:
135 if (sub_key)
136 RegCloseKey (sub_key);
137
138 return ret;
139 }
140
141 guint
gst_asio_enum(GList ** infos)142 gst_asio_enum (GList ** infos)
143 {
144 GList *info_list = nullptr;
145 DWORD index = 0;
146 guint num_device = 0;
147 LSTATUS status;
148 HKEY reg_key = nullptr;
149 WCHAR key_name[512];
150
151 g_return_val_if_fail (infos != nullptr, 0);
152
153 status = RegOpenKeyExW (HKEY_LOCAL_MACHINE, L"software\\asio", 0,
154 KEY_READ, ®_key);
155 while (status == ERROR_SUCCESS) {
156 GstAsioDeviceInfo *info;
157
158 status = RegEnumKeyW (reg_key, index, key_name, 512);
159 if (status != ERROR_SUCCESS)
160 break;
161
162 index++;
163 info = gst_asio_enum_new_device_info_from_reg (reg_key, key_name);
164 if (!info)
165 continue;
166
167 info_list = g_list_append (info_list, info);
168 num_device++;
169 }
170
171 if (reg_key)
172 RegCloseKey (reg_key);
173
174 *infos = info_list;
175
176 return num_device;
177 }
178
179 GstAsioDeviceInfo *
gst_asio_device_info_copy(const GstAsioDeviceInfo * info)180 gst_asio_device_info_copy (const GstAsioDeviceInfo * info)
181 {
182 GstAsioDeviceInfo *new_info;
183
184 if (!info)
185 return nullptr;
186
187 new_info = g_new0 (GstAsioDeviceInfo, 1);
188
189 new_info->clsid = info->clsid;
190 new_info->sta_model = info->sta_model;
191 new_info->driver_name = g_strdup (info->driver_name);
192 new_info->driver_desc = g_strdup (info->driver_desc);
193
194 return new_info;
195 }
196
197 void
gst_asio_device_info_free(GstAsioDeviceInfo * info)198 gst_asio_device_info_free (GstAsioDeviceInfo * info)
199 {
200 if (!info)
201 return;
202
203 g_free (info->driver_name);
204 g_free (info->driver_desc);
205
206 g_free (info);
207 }
208
209 GstAudioFormat
gst_asio_sample_type_to_gst(ASIOSampleType type)210 gst_asio_sample_type_to_gst (ASIOSampleType type)
211 {
212 GstAudioFormat fmt;
213
214 switch (type) {
215 /*~~ MSB means big endian ~~ */
216 case ASIOSTInt16MSB:
217 fmt = GST_AUDIO_FORMAT_S16BE;
218 break;
219 /* FIXME: also used for 20 bits packed in 24 bits, how do we detect that? */
220 case ASIOSTInt24MSB:
221 fmt = GST_AUDIO_FORMAT_S24BE;
222 break;
223 case ASIOSTInt32MSB:
224 fmt = GST_AUDIO_FORMAT_S32BE;
225 break;
226 case ASIOSTFloat32MSB:
227 fmt = GST_AUDIO_FORMAT_F32BE;
228 break;
229 case ASIOSTFloat64MSB:
230 fmt = GST_AUDIO_FORMAT_F64BE;
231 break;
232 /* All these are aligned to a different boundary than the packing, not sure
233 * how to handle it, let's try the normal S32BE format */
234 case ASIOSTInt32MSB16:
235 case ASIOSTInt32MSB18:
236 case ASIOSTInt32MSB20:
237 case ASIOSTInt32MSB24:
238 fmt = GST_AUDIO_FORMAT_S32BE;
239 break;
240
241 /*~~ LSB means little endian ~~ */
242 case ASIOSTInt16LSB:
243 fmt = GST_AUDIO_FORMAT_S16LE;
244 break;
245 /* FIXME: also used for 20 bits packed in 24 bits, how do we detect that? */
246 case ASIOSTInt24LSB:
247 fmt = GST_AUDIO_FORMAT_S24LE;
248 break;
249 case ASIOSTInt32LSB:
250 fmt = GST_AUDIO_FORMAT_S32LE;
251 break;
252 case ASIOSTFloat32LSB:
253 fmt = GST_AUDIO_FORMAT_F32LE;
254 break;
255 case ASIOSTFloat64LSB:
256 fmt = GST_AUDIO_FORMAT_F64LE;
257 break;
258 /* All these are aligned to a different boundary than the packing, not sure
259 * how to handle it, let's try the normal S32LE format */
260 case ASIOSTInt32LSB16:
261 case ASIOSTInt32LSB18:
262 case ASIOSTInt32LSB20:
263 case ASIOSTInt32LSB24:
264 GST_WARNING ("weird alignment %ld, trying S32LE", type);
265 fmt = GST_AUDIO_FORMAT_S32LE;
266 break;
267
268 /*~~ ASIO DSD formats are don't have gstreamer mappings ~~ */
269 case ASIOSTDSDInt8LSB1:
270 case ASIOSTDSDInt8MSB1:
271 case ASIOSTDSDInt8NER8:
272 GST_ERROR ("ASIO DSD formats are not supported");
273 fmt = GST_AUDIO_FORMAT_UNKNOWN;
274 break;
275 default:
276 GST_ERROR ("Unknown asio sample type %ld", type);
277 fmt = GST_AUDIO_FORMAT_UNKNOWN;
278 break;
279 }
280
281 return fmt;
282 }
283