⛏️ index : haiku.git

/*
 * Copyright 2002, 2003 Marcus Overhagen, Jérôme Duval. All rights reserved.
 * Distributed under the terms of the MIT License.
 */


#include "DefaultManager.h"

#include <Application.h>
#include <Directory.h>
#include <File.h>
#include <FindDirectory.h>
#include <MediaNode.h>
#include <OS.h>
#include <Path.h>
#include <TimeSource.h>
#include <string.h>

#include "MediaDebug.h"
#include "DormantNodeManager.h"
#include "media_server.h"
#include "NodeManager.h"


/* no locking used in this file, we assume that the caller (NodeManager) does it.
 */


#define MAX_NODE_INFOS 10
#define MAX_INPUT_INFOS 10

const uint32 kMsgHeader = 'sepx';
const uint32 kMsgTypeVideoIn = 0xffffffef;
const uint32 kMsgTypeVideoOut = 0xffffffee;
const uint32 kMsgTypeAudioIn = 0xfffffffe;
const uint32 kMsgTypeAudioOut = 0xffffffff;

const char *kDefaultManagerType 			= "be:_default";
const char *kDefaultManagerAddon 			= "be:_addon_id";
const char *kDefaultManagerFlavorId 		= "be:_internal_id";
const char *kDefaultManagerFlavorName 		= "be:_flavor_name";
const char *kDefaultManagerPath 			= "be:_path";
const char *kDefaultManagerInput 			= "be:_input_id";

const char *kDefaultManagerSettingsDirectory			= "Media";
const char *kDefaultManagerSettingsFile				= "MDefaultManager";


DefaultManager::DefaultManager()
	:
	fMixerConnected(false),
 	fPhysicalVideoOut(-1),
	fPhysicalVideoIn(-1),
	fPhysicalAudioOut(-1),
	fPhysicalAudioIn(-1),
	fSystemTimeSource(-1),
	fTimeSource(-1),
	fAudioMixer(-1),
	fPhysicalAudioOutInputID(0),
	fRescanThread(-1),
	fRescanRequested(0),
	fRescanLock("rescan default manager"),
	fRoster(NULL)
{
	strcpy(fPhysicalAudioOutInputName, "default");
	fBeginHeader[0] = 0xab00150b;
	fBeginHeader[1] = 0x18723462;
	fBeginHeader[2] = 0x00000002;
	fEndHeader[0] = 0x7465726d;
	fEndHeader[1] = 0x6d666c67;
	fEndHeader[2] = 0x00000002;

	fRoster = BMediaRoster::Roster();
	if (fRoster == NULL)
		TRACE("DefaultManager: The roster is NULL\n");
}


DefaultManager::~DefaultManager()
{
}


// this is called by the media_server *before* any add-ons have been loaded
status_t
DefaultManager::LoadState()
{
	CALLED();
	status_t err = B_OK;
	BPath path;
	if ((err = find_directory(B_USER_SETTINGS_DIRECTORY, &path)) != B_OK)
		return err;

	path.Append(kDefaultManagerSettingsDirectory);
	path.Append(kDefaultManagerSettingsFile);

	BFile file(path.Path(), B_READ_ONLY);

	uint32 categoryCount;
	ssize_t size = sizeof(uint32) * 3;
	if (file.Read(fBeginHeader, size) < size)
		return B_ERROR;
	TRACE("0x%08lx %ld\n", fBeginHeader[0], fBeginHeader[0]);
	TRACE("0x%08lx %ld\n", fBeginHeader[1], fBeginHeader[1]);
	TRACE("0x%08lx %ld\n", fBeginHeader[2], fBeginHeader[2]);
	size = sizeof(uint32);
	if (file.Read(&categoryCount, size) < size) {
		fprintf(stderr,
			"DefaultManager::LoadState() failed to read categoryCount\n");
		return B_ERROR;
	}
	TRACE("DefaultManager::LoadState() categoryCount %ld\n", categoryCount);
	while (categoryCount--) {
		BMessage settings;
		uint32 msg_header;
		uint32 default_type;
		if (file.Read(&msg_header, size) < size) {
			fprintf(stderr,
				"DefaultManager::LoadState() failed to read msg_header\n");
			return B_ERROR;
		}
		if (file.Read(&default_type, size) < size) {
			fprintf(stderr,
				"DefaultManager::LoadState() failed to read default_type\n");
			return B_ERROR;
		}
		if (settings.Unflatten(&file) == B_OK)
			fMsgList.AddItem(new BMessage(settings));
		else
			fprintf(stderr, "DefaultManager::LoadState() failed to unflatten\n");
	}
	size = sizeof(uint32) * 3;
	if (file.Read(fEndHeader,size) < size) {
		fprintf(stderr,
			"DefaultManager::LoadState() failed to read fEndHeader\n");
		return B_ERROR;
	}

	TRACE("LoadState returns B_OK\n");
	return B_OK;
}


status_t
DefaultManager::SaveState(NodeManager *node_manager)
{
	CALLED();
	status_t err = B_OK;
	BPath path;
	BList list;
	if ((err = find_directory(B_USER_SETTINGS_DIRECTORY, &path, true)) != B_OK)
		return err;
	path.Append(kDefaultManagerSettingsDirectory);
	if ((err = create_directory(path.Path(), 0755)) != B_OK)
		return err;
	path.Append(kDefaultManagerSettingsFile);

	uint32 default_types[] = {kMsgTypeVideoIn, kMsgTypeVideoOut,
		kMsgTypeAudioIn, kMsgTypeAudioOut};
	media_node_id media_node_ids[] = {fPhysicalVideoIn, fPhysicalVideoOut,
		fPhysicalAudioIn, fPhysicalAudioOut};
	for (uint32 i = 0; i < sizeof(default_types) / sizeof(default_types[0]);
		i++) {

		// we call the node manager to have more infos about nodes
		dormant_node_info info;
		media_node node;
		entry_ref ref;
		if (node_manager->GetCloneForID(media_node_ids[i], be_app->Team(),
				&node) != B_OK
			|| node_manager->GetDormantNodeInfo(node, &info) != B_OK
			|| node_manager->ReleaseNodeReference(media_node_ids[i],
				be_app->Team()) != B_OK
			|| node_manager->GetAddOnRef(info.addon, &ref) != B_OK) {
			if (media_node_ids[i] != -1) {
				// failed to get node info thus just return
				return B_ERROR;
			}
			continue;
		}

		BMessage *settings = new BMessage();
		settings->AddInt32(kDefaultManagerType, default_types[i]);
		BPath path(&ref);
		settings->AddInt32(kDefaultManagerAddon, info.addon);
		settings->AddInt32(kDefaultManagerFlavorId, info.flavor_id);
		settings->AddInt32(kDefaultManagerInput,
			default_types[i] == kMsgTypeAudioOut ? fPhysicalAudioOutInputID : 0);
		settings->AddString(kDefaultManagerFlavorName, info.name);
		settings->AddString(kDefaultManagerPath, path.Path());

		list.AddItem(settings);
		TRACE("message %s added\n", info.name);
	}

	BFile file(path.Path(), B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE);

	if (file.Write(fBeginHeader, sizeof(uint32)*3) < (int32)sizeof(uint32)*3)
		return B_ERROR;
	int32 categoryCount = list.CountItems();
	if (file.Write(&categoryCount, sizeof(uint32)) < (int32)sizeof(uint32))
		return B_ERROR;

	for (int32 i = 0; i < categoryCount; i++) {
		BMessage *settings = (BMessage *)list.ItemAt(i);
		uint32 default_type;
		if (settings->FindInt32(kDefaultManagerType,
			(int32*)&default_type) < B_OK)
			return B_ERROR;
		if (file.Write(&kMsgHeader, sizeof(uint32)) < (int32)sizeof(uint32))
			return B_ERROR;
		if (file.Write(&default_type, sizeof(uint32)) < (int32)sizeof(uint32))
			return B_ERROR;
		if (settings->Flatten(&file) < B_OK)
			return B_ERROR;
		delete settings;
	}
	if (file.Write(fEndHeader, sizeof(uint32)*3) < (int32)sizeof(uint32)*3)
		return B_ERROR;

	return B_OK;
}


status_t
DefaultManager::Set(media_node_id node_id, const char *input_name,
	int32 input_id, node_type type)
{
	CALLED();
	TRACE("DefaultManager::Set type : %i, node : %li, input : %li\n", type,
		node_id, input_id);
	switch (type) {
		case VIDEO_INPUT:
			fPhysicalVideoIn = node_id;
			return B_OK;
		case AUDIO_INPUT:
			fPhysicalAudioIn = node_id;
			return B_OK;
		case VIDEO_OUTPUT:
			fPhysicalVideoOut = node_id;
			return B_OK;
		case AUDIO_MIXER:
			return B_ERROR;
		case AUDIO_OUTPUT:
			fPhysicalAudioOut = node_id;
			fPhysicalAudioOutInputID = input_id;
			strcpy(fPhysicalAudioOutInputName,
				input_name ? input_name : "<null>");
			return B_OK;
		case TIME_SOURCE:
			return B_ERROR;

		// called by the media_server's ServerApp::StartSystemTimeSource()
		case SYSTEM_TIME_SOURCE:
		{
			ASSERT(fSystemTimeSource == -1);
			fSystemTimeSource = node_id;
			return B_OK;
		}

		default:
		{
			ERROR("DefaultManager::Set Error: called with unknown type %d\n",
				type);
			return B_ERROR;
		}
	}
}


status_t
DefaultManager::Get(media_node_id *nodeid, char *input_name, int32 *inputid,
	node_type type)
{
	CALLED();
	switch (type) {
		case VIDEO_INPUT: 		// output: nodeid
			if (fPhysicalVideoIn == -1)
				return B_NAME_NOT_FOUND;
			*nodeid = fPhysicalVideoIn;
			return B_OK;

		case AUDIO_INPUT: 		// output: nodeid
			if (fPhysicalAudioIn == -1)
				return B_NAME_NOT_FOUND;
			*nodeid = fPhysicalAudioIn;
			return B_OK;

		case VIDEO_OUTPUT: 		// output: nodeid
			if (fPhysicalVideoOut == -1)
				return B_NAME_NOT_FOUND;
			*nodeid = fPhysicalVideoOut;
			return B_OK;

		case AUDIO_OUTPUT:		// output: nodeid
			if (fPhysicalAudioOut == -1)
				return B_NAME_NOT_FOUND;
			*nodeid = fPhysicalAudioOut;
			return B_OK;

		case AUDIO_OUTPUT_EX:	// output: nodeid, input_name, input_id
			if (fPhysicalAudioOut == -1)
				return B_NAME_NOT_FOUND;
			*nodeid = fPhysicalAudioOut;
			*inputid = fPhysicalAudioOutInputID;
			strcpy(input_name, fPhysicalAudioOutInputName);
			return B_OK;

		case AUDIO_MIXER:		// output: nodeid
			if (fAudioMixer == -1)
				return B_NAME_NOT_FOUND;
			*nodeid = fAudioMixer;
			return B_OK;

		case TIME_SOURCE:
			if (fTimeSource != -1)
				*nodeid = fTimeSource;
			else
				*nodeid = fSystemTimeSource;
			return B_OK;

		case SYSTEM_TIME_SOURCE:
			*nodeid = fSystemTimeSource;
			return B_OK;

		default:
		{
			ERROR("DefaultManager::Get Error: called with unknown type %d\n",
				type);
			return B_ERROR;
		}
	}
}


// this is called by the media_server *after* the initial add-on loading
// has been done
status_t
DefaultManager::Rescan()
{
	BAutolock locker(fRescanLock);
	atomic_add(&fRescanRequested, 1);
	if (fRescanThread < 0) {
		fRescanThread = spawn_thread(rescan_thread, "rescan defaults",
			B_NORMAL_PRIORITY - 2, this);
		resume_thread(fRescanThread);
	}

	return B_OK;
}


int32
DefaultManager::rescan_thread(void *arg)
{
	reinterpret_cast<DefaultManager *>(arg)->_RescanThread();
	return 0;
}


void
DefaultManager::_RescanThread()
{
	TRACE("DefaultManager::_RescanThread() enter\n");

	BAutolock locker(fRescanLock);

	while (atomic_and(&fRescanRequested, 0) != 0) {
		locker.Unlock();

		// We do not search for the system time source,
		// it should already exist
		ASSERT(fSystemTimeSource != -1);

		if (fPhysicalVideoOut == -1) {
			_FindPhysical(&fPhysicalVideoOut, kMsgTypeVideoOut, false,
				B_MEDIA_RAW_VIDEO);
			_FindPhysical(&fPhysicalVideoOut, kMsgTypeVideoOut, false,
				B_MEDIA_ENCODED_VIDEO);
		}
		if (fPhysicalVideoIn == -1) {
			_FindPhysical(&fPhysicalVideoIn, kMsgTypeVideoIn, true,
				B_MEDIA_RAW_VIDEO);
			_FindPhysical(&fPhysicalVideoIn, kMsgTypeVideoIn, true,
				B_MEDIA_ENCODED_VIDEO);
		}
		if (fPhysicalAudioOut == -1)
			_FindPhysical(&fPhysicalAudioOut, kMsgTypeAudioOut, false,
				B_MEDIA_RAW_AUDIO);
		if (fPhysicalAudioIn == -1)
			_FindPhysical(&fPhysicalAudioIn, kMsgTypeAudioIn, true,
				B_MEDIA_RAW_AUDIO);
		if (fAudioMixer == -1)
			_FindAudioMixer();

		// The normal time source is searched for after the
		// Physical Audio Out has been created.
		if (fTimeSource == -1)
			_FindTimeSource();

		// Connect the mixer and physical audio out (soundcard)
		if (!fMixerConnected && fAudioMixer != -1 && fPhysicalAudioOut != -1) {
			fMixerConnected = _ConnectMixerToOutput() == B_OK;
			if (!fMixerConnected)
				TRACE("DefaultManager: failed to connect mixer and "
					"soundcard\n");
		} else {
			TRACE("DefaultManager: Did not try to connect mixer and "
				"soundcard\n");
		}

		if (fMixerConnected) {
			add_on_server_rescan_finished_notify_command cmd;
			SendToAddOnServer(ADD_ON_SERVER_RESCAN_FINISHED_NOTIFY, &cmd,
				sizeof(cmd));
		}

		locker.Lock();
	}

	fRescanThread = -1;

	BMessage msg(MEDIA_SERVER_RESCAN_COMPLETED);
	be_app->PostMessage(&msg);

	TRACE("DefaultManager::_RescanThread() leave\n");
}


void
DefaultManager::_FindPhysical(volatile media_node_id *id, uint32 default_type,
	bool isInput, media_type type)
{
	live_node_info info[MAX_NODE_INFOS];
	media_format format;
	int32 count;
	status_t rv;
	BMessage *msg = NULL;
	BPath msgPath;
	dormant_node_info msgDninfo;
	int32 input_id;
	bool isAudio = (type == B_MEDIA_RAW_AUDIO)
		|| (type == B_MEDIA_ENCODED_AUDIO);

	for (int32 i = 0; i < fMsgList.CountItems(); i++) {
		msg = (BMessage *)fMsgList.ItemAt(i);
		int32 msgType;
		if (msg->FindInt32(kDefaultManagerType, &msgType) == B_OK
			&& ((uint32)msgType == default_type)) {
			const char *name = NULL;
			const char *path = NULL;
			msg->FindInt32(kDefaultManagerAddon, &msgDninfo.addon);
			msg->FindInt32(kDefaultManagerFlavorId, &msgDninfo.flavor_id);
			msg->FindInt32(kDefaultManagerInput, &input_id);
			msg->FindString(kDefaultManagerFlavorName, &name);
			msg->FindString(kDefaultManagerPath, &path);
			if (name)
				strcpy(msgDninfo.name, name);
			if (path)
				msgPath = BPath(path);
			break;
		}
	}

	format.type = type;
	count = MAX_NODE_INFOS;
	rv = fRoster->GetLiveNodes(&info[0], &count,
		isInput ? NULL : &format, isInput ? &format : NULL, NULL,
		isInput ? B_BUFFER_PRODUCER | B_PHYSICAL_INPUT
			: B_BUFFER_CONSUMER | B_PHYSICAL_OUTPUT);
	if (rv != B_OK || count < 1) {
		TRACE("Couldn't find physical %s %s node\n",
			isAudio ? "audio" : "video", isInput ? "input" : "output");
		return;
	}
	for (int i = 0; i < count; i++)
		TRACE("info[%d].name %s\n", i, info[i].name);

	for (int i = 0; i < count; i++) {
		if (isAudio) {
			if (isInput) {
				if (0 == strcmp(info[i].name, "None In")) {
					// we keep the Null audio driver if none else matchs
					*id = info[i].node.node;
					continue;
				}
				// skip the Firewire audio driver
				if (0 == strcmp(info[i].name, "DV Input"))
					continue;
			} else {
				if (0 == strcmp(info[i].name, "None Out")) {
					// we keep the Null audio driver if none else matchs
					*id = info[i].node.node;
					if (msg)
						fPhysicalAudioOutInputID = input_id;
					continue;
				}
				// skip the Firewire audio driver
				if (0 == strcmp(info[i].name, "DV Output"))
					continue;
			}
		}
		if (msg) {	// we have a default info msg
			dormant_node_info dninfo;
			if (fRoster->GetDormantNodeFor(info[i].node,
					&dninfo) != B_OK) {
				ERROR("Couldn't GetDormantNodeFor\n");
				continue;
			}
			if (dninfo.flavor_id != msgDninfo.flavor_id
				|| strcmp(dninfo.name, msgDninfo.name) != 0) {
				ERROR("Doesn't match flavor or name\n");
				continue;
			}
			BPath path;
			if (gDormantNodeManager->FindAddOnPath(&path, dninfo.addon) != B_OK
				|| path != msgPath) {
				ERROR("Doesn't match : path\n");
				continue;
			}
		}
		TRACE("Default physical %s %s \"%s\" created!\n",
			isAudio ? "audio" : "video", isInput ? "input" : "output",
			info[i].name);
		*id = info[i].node.node;
		if (msg && isAudio && !isInput)
			fPhysicalAudioOutInputID = input_id;
		return;
	}
}


void
DefaultManager::_FindTimeSource()
{
	live_node_info info[MAX_NODE_INFOS];
	media_format input; /* a physical audio output has a logical data input (DAC)*/
	int32 count;
	status_t rv;

	/* First try to use the current default physical audio out
	 */
	if (fPhysicalAudioOut != -1) {
		media_node clone;
		if (fRoster->GetNodeFor(fPhysicalAudioOut,
				&clone) == B_OK) {
			if (clone.kind & B_TIME_SOURCE) {
				fTimeSource = clone.node;
				fRoster->StartTimeSource(clone,
					system_time() + 1000);
				fRoster->ReleaseNode(clone);
				TRACE("Default DAC timesource created!\n");
				return;
			}
			fRoster->ReleaseNode(clone);
		} else {
			TRACE("Default DAC is not a timesource!\n");
		}
	} else {
		TRACE("Default DAC node does not exist!\n");
	}

	/* Now try to find another physical audio out node
	 */
	input.type = B_MEDIA_RAW_AUDIO;
	count = MAX_NODE_INFOS;
	rv = fRoster->GetLiveNodes(&info[0], &count, &input, NULL, NULL,
		B_TIME_SOURCE | B_PHYSICAL_OUTPUT);
	if (rv == B_OK && count >= 1) {
		for (int i = 0; i < count; i++)
			printf("info[%d].name %s\n", i, info[i].name);

		for (int i = 0; i < count; i++) {
			// The BeOS R5 None Out node pretend to be a physical time source,
			// that is pretty dumb
			// skip the Null audio driver
			if (0 == strcmp(info[i].name, "None Out"))
				continue;
			// skip the Firewire audio driver
			if (0 != strstr(info[i].name, "DV Output"))
				continue;
			TRACE("Default DAC timesource \"%s\" created!\n", info[i].name);
			fTimeSource = info[i].node.node;
			fRoster->StartTimeSource(info[i].node,
				system_time() + 1000);
			return;
		}
	} else {
		TRACE("Couldn't find DAC timesource node\n");
	}

	/* XXX we might use other audio or video clock timesources
	 */
}


void
DefaultManager::_FindAudioMixer()
{
	live_node_info info;
	int32 count;
	status_t rv;

	if (fRoster == NULL)
		fRoster = BMediaRoster::Roster();

	count = 1;
	rv = fRoster->GetLiveNodes(&info, &count, NULL, NULL, NULL,
		B_BUFFER_PRODUCER | B_BUFFER_CONSUMER | B_SYSTEM_MIXER);
	if (rv != B_OK || count != 1) {
		TRACE("Couldn't find audio mixer node\n");
		return;
	}
	fAudioMixer = info.node.node;
	TRACE("Default audio mixer node created\n");
}


status_t
DefaultManager::_ConnectMixerToOutput()
{
	media_node 			timesource;
	media_node 			mixer;
	media_node 			soundcard;
	media_input			inputs[MAX_INPUT_INFOS];
	media_input 		input;
	media_output 		output;
	media_input 		newinput;
	media_output 		newoutput;
	media_format 		format;
	BTimeSource * 		ts;
	bigtime_t 			start_at;
	int32 				count;
	status_t 			rv;

	if (fRoster == NULL)
		fRoster = BMediaRoster::Roster();

	rv = fRoster->GetNodeFor(fPhysicalAudioOut, &soundcard);
	if (rv != B_OK) {
		TRACE("DefaultManager: failed to find soundcard (physical audio "
			"output)\n");
		return B_ERROR;
	}

	rv = fRoster->GetNodeFor(fAudioMixer, &mixer);
	if (rv != B_OK) {
		fRoster->ReleaseNode(soundcard);
		TRACE("DefaultManager: failed to find mixer\n");
		return B_ERROR;
	}

	// we now have the mixer and soundcard nodes,
	// find a free input/output and connect them

	rv = fRoster->GetFreeOutputsFor(mixer, &output, 1, &count,
		B_MEDIA_RAW_AUDIO);
	if (rv != B_OK || count != 1) {
		TRACE("DefaultManager: can't find free mixer output\n");
		rv = B_ERROR;
		goto finish;
	}

	rv = fRoster->GetFreeInputsFor(soundcard, inputs, MAX_INPUT_INFOS, &count,
		B_MEDIA_RAW_AUDIO);
	if (rv != B_OK || count < 1) {
		TRACE("DefaultManager: can't find free soundcard inputs\n");
		rv = B_ERROR;
		goto finish;
	}

	for (int32 i = 0; i < count; i++) {
		input = inputs[i];
		if (input.destination.id == fPhysicalAudioOutInputID)
			break;
	}

	for (int i = 0; i < 6; i++) {
		switch (i) {
			case 0:
				TRACE("DefaultManager: Trying connect in native format (1)\n");
				if (fRoster->GetFormatFor(input, &format) != B_OK) {
					ERROR("DefaultManager: GetFormatFor failed\n");
					continue;
				}
				// XXX BeOS R5 multiaudio node bug workaround
				if (format.u.raw_audio.channel_count == 1) {
					TRACE("##### WARNING! DefaultManager: ignored mono format\n");
					continue;
				}
				break;

			case 1:
				TRACE("DefaultManager: Trying connect in format 1\n");
				format.Clear();
				format.type = B_MEDIA_RAW_AUDIO;
				format.u.raw_audio.frame_rate = 44100;
				format.u.raw_audio.channel_count = 2;
				format.u.raw_audio.format = 0x2;
				break;

			case 2:
				TRACE("DefaultManager: Trying connect in format 2\n");
				format.Clear();
				format.type = B_MEDIA_RAW_AUDIO;
				format.u.raw_audio.frame_rate = 48000;
				format.u.raw_audio.channel_count = 2;
				format.u.raw_audio.format = 0x2;
				break;

			case 3:
				TRACE("DefaultManager: Trying connect in format 3\n");
				format.Clear();
				format.type = B_MEDIA_RAW_AUDIO;
				break;

			case 4:
				// BeOS R5 multiaudio node bug workaround
				TRACE("DefaultManager: Trying connect in native format (2)\n");
				if (fRoster->GetFormatFor(input, &format) != B_OK) {
					ERROR("DefaultManager: GetFormatFor failed\n");
					continue;
				}
				break;

			case 5:
				TRACE("DefaultManager: Trying connect in format 4\n");
				format.Clear();
				break;

		}
		rv = fRoster->Connect(output.source, input.destination, &format,
			&newoutput, &newinput);
		if (rv == B_OK)
			break;
	}
	if (rv != B_OK) {
		ERROR("DefaultManager: connect failed\n");
		goto finish;
	}

	fRoster->SetRunModeNode(mixer, BMediaNode::B_INCREASE_LATENCY);
	fRoster->SetRunModeNode(soundcard, BMediaNode::B_RECORDING);

	fRoster->GetTimeSource(&timesource);
	fRoster->SetTimeSourceFor(mixer.node, timesource.node);
	fRoster->SetTimeSourceFor(soundcard.node, timesource.node);
	fRoster->PrerollNode(mixer);
	fRoster->PrerollNode(soundcard);

	ts = fRoster->MakeTimeSourceFor(mixer);
	start_at = ts->Now() + 50000;
	fRoster->StartNode(mixer, start_at);
	fRoster->StartNode(soundcard, start_at);
	ts->Release();

finish:
	fRoster->ReleaseNode(mixer);
	fRoster->ReleaseNode(soundcard);
	fRoster->ReleaseNode(timesource);
	return rv;
}


void
DefaultManager::Dump()
{
}


void
DefaultManager::CleanupTeam(team_id team)
{
}