1/*
2 * OpenSound media addon for BeOS and Haiku
3 *
4 * Copyright (c) 2007, François Revol (revol@free.fr)
5 * Distributed under the terms of the MIT License.
6 *
7 * Based on MultiAudio media addon
8 * Copyright (c) 2002, 2003 Jerome Duval (jerome.duval@free.fr)
9 */
10#include "OpenSoundAddOn.h"
11
12#include <MediaDefs.h>
13#include <MediaAddOn.h>
14#include <Errors.h>
15#include <Node.h>
16#include <Mime.h>
17#include <StorageDefs.h>
18#include <Path.h>
19#include <Directory.h>
20#include <Entry.h>
21#include <FindDirectory.h>
22#include <Debug.h>
23#include <errno.h>
24
25#include "OpenSoundNode.h"
26#include "OpenSoundDevice.h"
27#include "OpenSoundDeviceEngine.h"
28
29#include <limits.h>
30#include <stdio.h>
31#include <string.h>
32
33#include "debug.h"
34
35#define MULTI_SAVE
36
37
38// instantiation function
39extern "C" _EXPORT BMediaAddOn * make_media_addon(image_id image) {
40	CALLED();
41	return new OpenSoundAddOn(image);
42}
43
44// -------------------------------------------------------- //
45// ctor/dtor
46// -------------------------------------------------------- //
47
48OpenSoundAddOn::~OpenSoundAddOn()
49{
50	CALLED();
51
52	void *device = NULL;
53	for (int32 i = 0; (device = fDevices.ItemAt(i)); i++)
54		delete (OpenSoundDevice *)device;
55
56	SaveSettings();
57}
58
59OpenSoundAddOn::OpenSoundAddOn(image_id image) :
60	BMediaAddOn(image),
61	fDevices()
62{
63	CALLED();
64	fInitCheckStatus = B_NO_INIT;
65
66	/* unix paths */
67	if (RecursiveScan("/dev/oss/") != B_OK)
68		return;
69	/*
70	if (RecursiveScan("/dev/audio/oss/") != B_OK)
71		return;
72	*/
73
74	LoadSettings();
75
76	fInitCheckStatus = B_OK;
77}
78
79// -------------------------------------------------------- //
80// BMediaAddOn impl
81// -------------------------------------------------------- //
82
83status_t OpenSoundAddOn::InitCheck(
84	const char ** out_failure_text)
85{
86	CALLED();
87	return B_OK;
88}
89
90int32 OpenSoundAddOn::CountFlavors()
91{
92	CALLED();
93	PRINT(("%" B_PRId32 " flavours\n", fDevices.CountItems()));
94	return fDevices.CountItems();
95}
96
97status_t OpenSoundAddOn::GetFlavorAt(
98	int32 n,
99	const flavor_info ** out_info)
100{
101	CALLED();
102	if (n < 0 || n > fDevices.CountItems() - 1) {
103		fprintf(stderr, "<- B_BAD_INDEX\n");
104		return B_BAD_INDEX;
105	}
106
107	OpenSoundDevice *device = (OpenSoundDevice *) fDevices.ItemAt(n);
108
109	flavor_info * infos = new flavor_info[1];
110	OpenSoundNode::GetFlavor(&infos[0], n);
111	infos[0].name = device->fCardInfo.longname;
112	(*out_info) = infos;
113	return B_OK;
114}
115
116BMediaNode * OpenSoundAddOn::InstantiateNodeFor(
117	const flavor_info * info,
118	BMessage * config,
119	status_t * out_error)
120{
121	CALLED();
122
123	OpenSoundDevice *device = (OpenSoundDevice*)fDevices.ItemAt(
124		info->internal_id);
125	if (device == NULL) {
126		*out_error = B_ERROR;
127		return NULL;
128	}
129
130#ifdef MULTI_SAVE
131	if (fSettings.FindMessage(device->fCardInfo.longname, config) == B_OK) {
132		fSettings.RemoveData(device->fCardInfo.longname);
133	}
134#endif
135
136	OpenSoundNode * node =
137		new OpenSoundNode(this,
138			device->fCardInfo.longname,
139			device,
140			info->internal_id,
141			config);
142	if (node == 0) {
143		*out_error = B_NO_MEMORY;
144		fprintf(stderr, "<- B_NO_MEMORY\n");
145	} else {
146		*out_error = node->InitCheck();
147	}
148	return node;
149}
150
151status_t
152OpenSoundAddOn::GetConfigurationFor(BMediaNode * your_node, BMessage * into_message)
153{
154	CALLED();
155#ifdef MULTI_SAVE
156	{
157		into_message = new BMessage();
158		OpenSoundNode * node = dynamic_cast<OpenSoundNode*>(your_node);
159		if (node == 0) {
160			fprintf(stderr, "<- B_BAD_TYPE\n");
161			return B_BAD_TYPE;
162		}
163		if (node->GetConfigurationFor(into_message) == B_OK) {
164			fSettings.AddMessage(your_node->Name(), into_message);
165		}
166		return B_OK;
167	}
168#endif
169	// currently never called by the media kit. Seems it is not implemented.
170	OpenSoundNode * node = dynamic_cast<OpenSoundNode*>(your_node);
171	if (node == 0) {
172		fprintf(stderr, "<- B_BAD_TYPE\n");
173		return B_BAD_TYPE;
174	}
175	return node->GetConfigurationFor(into_message);
176}
177
178
179bool OpenSoundAddOn::WantsAutoStart()
180{
181	CALLED();
182	return false;
183}
184
185status_t OpenSoundAddOn::AutoStart(
186	int in_count,
187	BMediaNode ** out_node,
188	int32 * out_internal_id,
189	bool * out_has_more)
190{
191	CALLED();
192	return B_OK;
193}
194
195status_t
196OpenSoundAddOn::RecursiveScan(const char* rootPath, BEntry *rootEntry)
197{
198	status_t err;
199	int mixer;
200	oss_sysinfo sysinfo;
201	oss_card_info cardinfo;
202	int card, i, j;
203	BList devs;
204
205	CALLED();
206
207	// make sure directories are scanned in this order
208	BDirectory("/dev/audio/hmulti");
209	BDirectory("/dev/audio/old");
210	// OSS last, to give precedence to native drivers.
211	// If other addons are loaded first it's ok as well.
212	// Also, we must open it to make sure oss_loader is here,
213	// else we don't get /dev/sndstat since we don't have a symlink in dev/.
214	BDirectory("/dev/oss");
215
216	mixer = open(OSS_MIXER_DEV, O_RDWR);
217	if (mixer < 0) {
218		// try to rescan
219		// only works in BeOS
220		BFile fDevFS("/dev/.", B_WRITE_ONLY);
221		const char *drv = "oss_loader";
222		fDevFS.Write(drv, strlen(drv));
223		mixer = open(OSS_MIXER_DEV, O_RDWR);
224		if (mixer < 0) {
225			err = errno;
226			goto err0;
227		}
228	}
229
230	if (ioctl(mixer, SNDCTL_SYSINFO, &sysinfo) < 0) {
231		err = errno;
232		goto err1;
233	}
234
235	PRINT(("OSS: %s %s (0x%08X)\n", sysinfo.product, sysinfo.version, sysinfo.versionnum));
236	PRINT(("OSS: %d audio cards, %d audio devs, %d audio engines, %d midi, %d mixers\n", sysinfo.numcards, sysinfo.numaudios, sysinfo.numaudioengines, sysinfo.nummidis, sysinfo.nummixers));
237
238	/* construct an empty SoundDevice per card */
239
240	for (card = 0; card < sysinfo.numcards; card++) {
241		cardinfo.card = card;
242		if (ioctl(mixer, SNDCTL_CARDINFO, &cardinfo) < 0) {
243			err = errno;
244			goto err1;
245		}
246		OpenSoundDevice *device = new OpenSoundDevice(&cardinfo);
247		if (device)
248			devs.AddItem(device);
249		else {
250			err = ENOMEM;
251			goto err1;
252		}
253	}
254
255	/* Add its audio engines to it */
256
257	for (i = 0; i < sysinfo.numaudioengines; i++) {
258		oss_audioinfo audioinfo;
259		audioinfo.dev = i;
260		if (ioctl(mixer, SNDCTL_ENGINEINFO, &audioinfo, sizeof(oss_audioinfo)) < 0) {
261			err = errno;
262			goto err1;
263		}
264		PRINT(("OSS: engine[%d]: card=%d, port=%d, legacy=%d, next_play=%d, next_rec=%d\n", i, audioinfo.card_number, audioinfo.port_number, audioinfo.legacy_device, audioinfo.next_play_engine, audioinfo.next_rec_engine));
265		OpenSoundDevice *device = (OpenSoundDevice *)(devs.ItemAt(audioinfo.card_number));
266		if (device)
267			device->AddEngine(&audioinfo);
268	}
269
270	/* Add its mixers to it */
271
272	for (i = 0; i < sysinfo.nummixers; i++) {
273		oss_mixerinfo mixerinfo;
274		mixerinfo.dev = i;
275		if (ioctl(mixer, SNDCTL_MIXERINFO, &mixerinfo) < 0) {
276			err = errno;
277			goto err1;
278		}
279		PRINT(("OSS: mixer[%d]: card=%d\n", i, mixerinfo.card_number));
280		OpenSoundDevice *device = (OpenSoundDevice *)(devs.ItemAt(mixerinfo.card_number));
281		if (device)
282			device->AddMixer(&mixerinfo);
283	}
284
285	/* resolve engine chains of shadow engines */
286
287	for (card = 0; card < sysinfo.numcards; card++) {
288		OpenSoundDevice *device = (OpenSoundDevice *)(devs.ItemAt(card));
289		if (!device)
290			continue;
291		for (i = 0; i < device->CountEngines(); i++) {
292			OpenSoundDeviceEngine *engine = device->EngineAt(i);
293			if (engine) {
294				if (engine->Info()->next_play_engine) {
295					for (j = 0; j < device->CountEngines(); j++) {
296						OpenSoundDeviceEngine *next = device->EngineAt(j);
297						if (!next || (engine == next))
298							continue;
299						if (next->Info()->dev == engine->Info()->next_play_engine) {
300							PRINT(("OSS: engine[%d].next_play = engine[%d]\n", i, j));
301							engine->fNextPlay = next;
302							break;
303						}
304					}
305				}
306				if (engine->Info()->next_rec_engine) {
307					for (j = 0; j < device->CountEngines(); j++) {
308						OpenSoundDeviceEngine *next = device->EngineAt(j);
309						if (!next || (engine == next))
310							continue;
311						if (next->Info()->dev == engine->Info()->next_rec_engine) {
312							PRINT(("OSS: engine[%d].next_rec = engine[%d]\n", i, j));
313							engine->fNextRec = next;
314							break;
315						}
316					}
317				}
318			}
319		}
320	}
321
322	/* copy correctly initialized devs to fDevices */
323
324	for (card = 0; card < sysinfo.numcards; card++) {
325		OpenSoundDevice *device = (OpenSoundDevice *)(devs.ItemAt(card));
326		if (device) {
327			if (card == 0) { /* skip the "oss0" pseudo card device */
328				delete device;
329				continue;
330			}
331			if ((device->InitDriver() == B_OK) && (device->InitCheck() == B_OK))
332				fDevices.AddItem(device);
333			else
334				delete device;
335		}
336	}
337	if (fDevices.CountItems())
338		err = B_OK;
339	else
340		err = ENOENT;
341err1:
342	close(mixer);
343err0:
344	return err;
345
346#if MA
347	BDirectory root;
348	if (rootEntry != NULL)
349		root.SetTo(rootEntry);
350	else if (rootPath != NULL) {
351		root.SetTo(rootPath);
352	} else {
353		PRINT(("Error in OpenSoundAddOn::RecursiveScan null params\n"));
354		return B_ERROR;
355	}
356
357	BEntry entry;
358
359	while (root.GetNextEntry(&entry) > B_ERROR) {
360
361		if (entry.IsDirectory()) {
362			BPath path;
363			entry.GetPath(&path);
364			OpenSoundDevice *device = new OpenSoundDevice(path.Path() + strlen(rootPath), path.Path());
365			if (device) {
366				if (device->InitCheck() == B_OK)
367					fDevices.AddItem(device);
368				else
369					delete device;
370			}
371		}
372	}
373
374	return B_OK;
375#endif
376}
377
378
379void
380OpenSoundAddOn::SaveSettings(void)
381{
382	CALLED();
383	BPath path;
384	if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) == B_OK) {
385		path.Append(SETTINGS_FILE);
386		BFile file(path.Path(), B_READ_WRITE | B_CREATE_FILE | B_ERASE_FILE);
387		if (file.InitCheck() == B_OK)
388			fSettings.Flatten(&file);
389	}
390}
391
392
393void
394OpenSoundAddOn::LoadSettings(void)
395{
396	CALLED();
397	fSettings.MakeEmpty();
398
399	BPath path;
400	if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) == B_OK) {
401		path.Append(SETTINGS_FILE);
402		BFile file(path.Path(), B_READ_ONLY);
403		if ((file.InitCheck() == B_OK) && (fSettings.Unflatten(&file) == B_OK))
404		{
405			PRINT_OBJECT(fSettings);
406		} else {
407			PRINT(("Error unflattening settings file %s\n", path.Path()));
408		}
409	}
410}
411
412
413void
414OpenSoundAddOn::RegisterMediaFormats(void)
415{
416	CALLED();
417	// register non-raw audio formats to the Media Kit
418#ifdef ENABLE_NON_RAW_SUPPORT
419	OpenSoundDevice::register_media_formats();
420#endif
421
422
423}
424