1 /* 2 * Copyright 2002, 2003 Marcus Overhagen, Jérôme Duval. All rights reserved. 3 * Distributed under the terms of the MIT License. 4 */ 5 #include <Application.h> 6 #include <OS.h> 7 #include <MediaNode.h> 8 #include <MediaRoster.h> 9 #include <TimeSource.h> 10 #include <string.h> 11 #include <storage/File.h> 12 #include <storage/FindDirectory.h> 13 #include <storage/Path.h> 14 #include "DefaultManager.h" 15 #include "DormantNodeManager.h" 16 #include "NodeManager.h" 17 #include "debug.h" 18 19 /* no locking used in this file, we assume that the caller (NodeManager) does it. 20 */ 21 22 23 #define MAX_NODE_INFOS 10 24 #define MAX_INPUT_INFOS 10 25 26 const uint32 kMsgHeader = 'sepx'; 27 const uint32 kMsgTypeVideoIn = 0xffffffef; 28 const uint32 kMsgTypeVideoOut = 0xffffffee; 29 const uint32 kMsgTypeAudioIn = 0xfffffffe; 30 const uint32 kMsgTypeAudioOut = 0xffffffff; 31 32 const char *kDefaultManagerType = "be:_default"; 33 const char *kDefaultManagerAddon = "be:_addon_id"; 34 const char *kDefaultManagerFlavorId = "be:_internal_id"; 35 const char *kDefaultManagerFlavorName = "be:_flavor_name"; 36 const char *kDefaultManagerPath = "be:_path"; 37 const char *kDefaultManagerInput = "be:_input_id"; 38 39 const char *kDefaultManagerSettings = "Media/MDefaultManager"; 40 41 42 DefaultManager::DefaultManager() 43 : fMixerConnected(false), 44 fPhysicalVideoOut(-1), 45 fPhysicalVideoIn(-1), 46 fPhysicalAudioOut(-1), 47 fPhysicalAudioIn(-1), 48 fSystemTimeSource(-1), 49 fTimeSource(-1), 50 fAudioMixer(-1), 51 fPhysicalAudioOutInputID(0) 52 { 53 strcpy(fPhysicalAudioOutInputName, "default"); 54 fBeginHeader[0] = 0xab00150b; 55 fBeginHeader[1] = 0x18723462; 56 fBeginHeader[2] = 0x00000002; 57 fEndHeader[0] = 0x7465726d; 58 fEndHeader[1] = 0x6d666c67; 59 fEndHeader[2] = 0x00000002; 60 } 61 62 DefaultManager::~DefaultManager() 63 { 64 } 65 66 // this is called by the media_server *before* any add-ons have been loaded 67 status_t 68 DefaultManager::LoadState() 69 { 70 CALLED(); 71 status_t err = B_OK; 72 BPath path; 73 if((err = find_directory(B_USER_SETTINGS_DIRECTORY, &path))!=B_OK) 74 return err; 75 76 path.Append(kDefaultManagerSettings); 77 78 BFile file(path.Path(), B_READ_ONLY); 79 80 uint32 category_count; 81 if (file.Read(fBeginHeader, sizeof(uint32)*3) < (int32)sizeof(uint32)*3) 82 return B_ERROR; 83 TRACE("0x%08lx %ld\n", fBeginHeader[0], fBeginHeader[0]); 84 TRACE("0x%08lx %ld\n", fBeginHeader[1], fBeginHeader[1]); 85 TRACE("0x%08lx %ld\n", fBeginHeader[2], fBeginHeader[2]); 86 if (file.Read(&category_count, sizeof(uint32)) < (int32)sizeof(uint32)) 87 return B_ERROR; 88 while (category_count--) { 89 BMessage settings; 90 uint32 msg_header; 91 uint32 default_type; 92 if (file.Read(&msg_header, sizeof(uint32)) < (int32)sizeof(uint32)) 93 return B_ERROR; 94 if (file.Read(&default_type, sizeof(uint32)) < (int32)sizeof(uint32)) 95 return B_ERROR; 96 if(settings.Unflatten(&file)==B_OK) { 97 settings.PrintToStream(); 98 fMsgList.AddItem(new BMessage(settings)); 99 } 100 } 101 if (file.Read(fEndHeader, sizeof(uint32)*3) < (int32)sizeof(uint32)*3) 102 return B_ERROR; 103 return B_OK; 104 } 105 106 status_t 107 DefaultManager::SaveState(NodeManager *node_manager) 108 { 109 CALLED(); 110 status_t err = B_OK; 111 BPath path; 112 BList list; 113 if((err = find_directory(B_USER_SETTINGS_DIRECTORY, &path))!=B_OK) 114 return err; 115 116 path.Append(kDefaultManagerSettings); 117 118 BFile file(path.Path(), B_WRITE_ONLY | B_CREATE_FILE); 119 120 uint32 default_types[] = {kMsgTypeVideoIn, kMsgTypeVideoOut, kMsgTypeAudioIn, kMsgTypeAudioOut}; 121 uint32 media_node_ids[] = {fPhysicalVideoIn, fPhysicalVideoOut, fPhysicalAudioIn, fPhysicalAudioOut}; 122 for(uint32 i=0; i<sizeof(default_types)/sizeof(default_types[0]); i++) { 123 BMessage *settings = new BMessage(); 124 settings->AddInt32(kDefaultManagerType, default_types[i]); 125 126 // we call the node manager to have more infos about nodes 127 dormant_node_info info; 128 media_node node; 129 entry_ref ref; 130 if (node_manager->GetCloneForId(&node, media_node_ids[i], be_app->Team()) != B_OK) 131 continue; 132 if (node_manager->GetDormantNodeInfo(&info, node) != B_OK) 133 continue; 134 if (node_manager->DecrementGlobalRefCount(media_node_ids[i], be_app->Team()) != B_OK) 135 continue; 136 if (node_manager->GetAddonRef(&ref, info.addon)!=B_OK) 137 continue; 138 139 BPath path(&ref); 140 settings->AddInt32(kDefaultManagerAddon, info.addon); 141 settings->AddInt32(kDefaultManagerFlavorId, info.flavor_id); 142 settings->AddInt32(kDefaultManagerInput, default_types[i] == kMsgTypeAudioOut ? fPhysicalAudioOutInputID : 0); 143 settings->AddString(kDefaultManagerFlavorName, info.name); 144 settings->AddString(kDefaultManagerPath, path.Path()); 145 146 list.AddItem(settings); 147 TRACE("message %s added\n", info.name); 148 } 149 150 if (file.Write(fBeginHeader, sizeof(uint32)*3) < (int32)sizeof(uint32)*3) 151 return B_ERROR; 152 int32 category_count = list.CountItems(); 153 if (file.Write(&category_count, sizeof(uint32)) < (int32)sizeof(uint32)) 154 return B_ERROR; 155 156 for (int32 i = 0; i < category_count; i++) { 157 BMessage *settings = (BMessage *)list.ItemAt(i); 158 uint32 default_type; 159 if (settings->FindInt32(kDefaultManagerType, (int32*)&default_type) < B_OK) 160 return B_ERROR; 161 if (file.Write(&kMsgHeader, sizeof(uint32)) < (int32)sizeof(uint32)) 162 return B_ERROR; 163 if (file.Write(&default_type, sizeof(uint32)) < (int32)sizeof(uint32)) 164 return B_ERROR; 165 if(settings->Flatten(&file) < B_OK) 166 return B_ERROR; 167 delete settings; 168 } 169 if (file.Write(fEndHeader, sizeof(uint32)*3) < (int32)sizeof(uint32)*3) 170 return B_ERROR; 171 172 return B_OK; 173 } 174 175 status_t 176 DefaultManager::Set(media_node_id node_id, const char *input_name, int32 input_id, node_type type) 177 { 178 CALLED(); 179 TRACE("DefaultManager::Set type : %i, node : %li, input : %li\n", type, node_id, input_id); 180 switch (type) { 181 case VIDEO_INPUT: 182 fPhysicalVideoIn = node_id; 183 return B_OK; 184 case AUDIO_INPUT: 185 fPhysicalAudioIn = node_id; 186 return B_OK; 187 case VIDEO_OUTPUT: 188 fPhysicalVideoOut = node_id; 189 return B_OK; 190 case AUDIO_MIXER: 191 return B_ERROR; 192 case AUDIO_OUTPUT: 193 fPhysicalAudioOut = node_id; 194 fPhysicalAudioOutInputID = input_id; 195 strcpy(fPhysicalAudioOutInputName, input_name ? input_name : "<null>"); 196 return B_OK; 197 case TIME_SOURCE: 198 return B_ERROR; 199 200 case SYSTEM_TIME_SOURCE: //called by the media_server's ServerApp::StartSystemTimeSource() 201 { 202 ASSERT(fSystemTimeSource == -1); 203 fSystemTimeSource = node_id; 204 return B_OK; 205 } 206 207 default: 208 { 209 ERROR("DefaultManager::Set Error: called with unknown type %d\n", type); 210 return B_ERROR; 211 } 212 } 213 } 214 215 status_t 216 DefaultManager::Get(media_node_id *nodeid, char *input_name, int32 *inputid, node_type type) 217 { 218 CALLED(); 219 switch (type) { 220 case VIDEO_INPUT: // output: nodeid 221 if (fPhysicalVideoIn == -1) 222 return B_ERROR; 223 *nodeid = fPhysicalVideoIn; 224 return B_OK; 225 226 case AUDIO_INPUT: // output: nodeid 227 if (fPhysicalAudioIn == -1) 228 return B_ERROR; 229 *nodeid = fPhysicalAudioIn; 230 return B_OK; 231 232 case VIDEO_OUTPUT: // output: nodeid 233 if (fPhysicalVideoOut == -1) 234 return B_ERROR; 235 *nodeid = fPhysicalVideoOut; 236 return B_OK; 237 238 case AUDIO_OUTPUT: // output: nodeid 239 if (fPhysicalAudioOut == -1) 240 return B_ERROR; 241 *nodeid = fPhysicalAudioOut; 242 return B_OK; 243 244 case AUDIO_OUTPUT_EX: // output: nodeid, input_name, input_id 245 if (fPhysicalAudioOut == -1) 246 return B_ERROR; 247 *nodeid = fPhysicalAudioOut; 248 *inputid = fPhysicalAudioOutInputID; 249 strcpy(input_name, fPhysicalAudioOutInputName); 250 return B_OK; 251 252 case AUDIO_MIXER: // output: nodeid 253 if (fAudioMixer == -1) 254 return B_ERROR; 255 *nodeid = fAudioMixer; 256 return B_OK; 257 258 case TIME_SOURCE: 259 if (fTimeSource != -1) 260 *nodeid = fTimeSource; 261 else 262 *nodeid = fSystemTimeSource; 263 return B_OK; 264 265 case SYSTEM_TIME_SOURCE: 266 *nodeid = fSystemTimeSource; 267 return B_OK; 268 269 default: 270 { 271 ERROR("DefaultManager::Get Error: called with unknown type %d\n", type); 272 return B_ERROR; 273 } 274 } 275 } 276 277 // this is called by the media_server *after* the initial add-on loading has been done 278 status_t 279 DefaultManager::Rescan() 280 { 281 thread_id fThreadId = spawn_thread(rescan_thread, "rescan defaults", 8, this); 282 resume_thread(fThreadId); 283 return B_OK; 284 } 285 286 int32 287 DefaultManager::rescan_thread(void *arg) 288 { 289 reinterpret_cast<DefaultManager *>(arg)->RescanThread(); 290 return 0; 291 } 292 293 void 294 DefaultManager::RescanThread() 295 { 296 printf("DefaultManager::RescanThread() enter\n"); 297 298 // We do not search for the system time source, 299 // it should already exist 300 ASSERT(fSystemTimeSource != -1); 301 302 if (fPhysicalVideoOut == -1) { 303 FindPhysical(&fPhysicalVideoOut, kMsgTypeVideoOut, false, B_MEDIA_RAW_VIDEO); 304 FindPhysical(&fPhysicalVideoOut, kMsgTypeVideoOut, false, B_MEDIA_ENCODED_VIDEO); 305 } 306 if (fPhysicalVideoIn == -1) { 307 FindPhysical(&fPhysicalVideoIn, kMsgTypeVideoIn, true, B_MEDIA_RAW_VIDEO); 308 FindPhysical(&fPhysicalVideoIn, kMsgTypeVideoIn, true, B_MEDIA_ENCODED_VIDEO); 309 } 310 if (fPhysicalAudioOut == -1) 311 FindPhysical(&fPhysicalAudioOut, kMsgTypeAudioOut, false, B_MEDIA_RAW_AUDIO); 312 if (fPhysicalAudioIn == -1) 313 FindPhysical(&fPhysicalAudioIn, kMsgTypeAudioIn, true, B_MEDIA_RAW_AUDIO); 314 if (fAudioMixer == -1) 315 FindAudioMixer(); 316 317 // The normal time source is searched for after the 318 // Physical Audio Out has been created. 319 if (fTimeSource == -1) 320 FindTimeSource(); 321 322 // Connect the mixer and physical audio out (soundcard) 323 if (!fMixerConnected && fAudioMixer != -1 && fPhysicalAudioOut != -1) { 324 fMixerConnected = B_OK == ConnectMixerToOutput(); 325 if (!fMixerConnected) 326 ERROR("DefaultManager: failed to connect mixer and soundcard\n"); 327 } else { 328 ERROR("DefaultManager: Did not try to connect mixer and soundcard\n"); 329 } 330 331 printf("DefaultManager::RescanThread() leave\n"); 332 } 333 334 335 void 336 DefaultManager::FindPhysical(volatile media_node_id *id, uint32 default_type, bool isInput, media_type type) 337 { 338 live_node_info info[MAX_NODE_INFOS]; 339 media_format format; 340 int32 count; 341 status_t rv; 342 BMessage *msg = NULL; 343 BPath msgPath; 344 dormant_node_info msgDninfo; 345 int32 input_id; 346 bool isAudio = type & B_MEDIA_RAW_AUDIO; 347 348 for(int32 i=0; i<fMsgList.CountItems(); i++) { 349 msg = (BMessage *)fMsgList.ItemAt(i); 350 int32 msgType; 351 if(msg->FindInt32(kDefaultManagerType, &msgType)==B_OK && ((uint32)msgType == default_type)) { 352 const char *name = NULL; 353 const char *path = NULL; 354 msg->FindInt32(kDefaultManagerAddon, &msgDninfo.addon); 355 msg->FindInt32(kDefaultManagerFlavorId, &msgDninfo.flavor_id); 356 msg->FindInt32(kDefaultManagerInput, &input_id); 357 msg->FindString(kDefaultManagerFlavorName, &name); 358 msg->FindString(kDefaultManagerPath, &path); 359 if(name) 360 strcpy(msgDninfo.name, name); 361 if(path) 362 msgPath = BPath(path); 363 break; 364 } 365 } 366 367 memset(&format, 0, sizeof(format)); 368 format.type = type; 369 count = MAX_NODE_INFOS; 370 rv = BMediaRoster::Roster()->GetLiveNodes(&info[0], &count, 371 isInput ? NULL : &format, isInput ? &format : NULL, NULL, 372 isInput ? B_BUFFER_PRODUCER | B_PHYSICAL_INPUT : B_BUFFER_CONSUMER | B_PHYSICAL_OUTPUT); 373 if (rv != B_OK || count < 1) { 374 ERROR("Couldn't find physical %s %s node\n", isAudio ? "audio" : "video", isInput ? "input" : "output"); 375 return; 376 } 377 for (int i = 0; i < count; i++) 378 TRACE("info[%d].name %s\n", i, info[i].name); 379 380 for (int i = 0; i < count; i++) { 381 if (isAudio) { 382 if (isInput) { 383 if (0 == strcmp(info[i].name, "None In")) { 384 // we keep the Null audio driver if none else matchs 385 *id = info[i].node.node; 386 continue; 387 } 388 if (0 == strcmp(info[i].name, "DV Input")) // skip the Firewire audio driver 389 continue; 390 } else { 391 if (0 == strcmp(info[i].name, "None Out")) { 392 // we keep the Null audio driver if none else matchs 393 *id = info[i].node.node; 394 if(msg) 395 fPhysicalAudioOutInputID = input_id; 396 continue; 397 } 398 if (0 == strcmp(info[i].name, "DV Output")) // skip the Firewire audio driver 399 continue; 400 } 401 } 402 if(msg) { // we have a default info msg 403 dormant_node_info dninfo; 404 if(BMediaRoster::Roster()->GetDormantNodeFor(info[i].node, &dninfo) != B_OK) { 405 ERROR("Couldn't GetDormantNodeFor\n"); 406 continue; 407 } 408 if(dninfo.flavor_id!=msgDninfo.flavor_id 409 || strcmp(dninfo.name, msgDninfo.name)!=0) { 410 ERROR("Doesn't match flavor or name\n"); 411 continue; 412 } 413 BPath path; 414 if((_DormantNodeManager->FindAddonPath(&path, dninfo.addon)!=B_OK) 415 || (path != msgPath)) { 416 ERROR("Doesn't match : path\n"); 417 continue; 418 } 419 } 420 TRACE("Default physical %s %s \"%s\" created!\n", 421 isAudio ? "audio" : "video", isInput ? "input" : "output", info[i].name); 422 *id = info[i].node.node; 423 if(msg && isAudio && !isInput) 424 fPhysicalAudioOutInputID = input_id; 425 return; 426 } 427 } 428 429 430 void 431 DefaultManager::FindTimeSource() 432 { 433 live_node_info info[MAX_NODE_INFOS]; 434 media_format input; /* a physical audio output has a logical data input (DAC)*/ 435 int32 count; 436 status_t rv; 437 438 /* First try to use the current default physical audio out 439 */ 440 if (fPhysicalAudioOut != -1) { 441 media_node clone; 442 if (B_OK == BMediaRoster::Roster()->GetNodeFor(fPhysicalAudioOut, &clone)) { 443 if (clone.kind & B_TIME_SOURCE) { 444 fTimeSource = clone.node; 445 BMediaRoster::Roster()->StartTimeSource(clone, system_time() + 1000); 446 BMediaRoster::Roster()->ReleaseNode(clone); 447 printf("Default DAC timesource created!\n"); 448 return; 449 } 450 BMediaRoster::Roster()->ReleaseNode(clone); 451 } else { 452 printf("Default DAC is not a timesource!\n"); 453 } 454 } else { 455 printf("Default DAC node does not exist!\n"); 456 } 457 458 /* Now try to find another physical audio out node 459 */ 460 memset(&input, 0, sizeof(input)); 461 input.type = B_MEDIA_RAW_AUDIO; 462 count = MAX_NODE_INFOS; 463 rv = BMediaRoster::Roster()->GetLiveNodes(&info[0], &count, &input, NULL, NULL, B_TIME_SOURCE | B_PHYSICAL_OUTPUT); 464 if (rv == B_OK && count >= 1) { 465 for (int i = 0; i < count; i++) 466 printf("info[%d].name %s\n", i, info[i].name); 467 468 for (int i = 0; i < count; i++) { 469 // The BeOS R5 None Out node pretend to be a physical time source, that is pretty dumb 470 if (0 == strcmp(info[i].name, "None Out")) // skip the Null audio driver 471 continue; 472 if (0 != strstr(info[i].name, "DV Output")) // skip the Firewire audio driver 473 continue; 474 printf("Default DAC timesource \"%s\" created!\n", info[i].name); 475 fTimeSource = info[i].node.node; 476 BMediaRoster::Roster()->StartTimeSource(info[i].node, system_time() + 1000); 477 return; 478 } 479 } else { 480 printf("Couldn't find DAC timesource node\n"); 481 } 482 483 /* XXX we might use other audio or video clock timesources 484 */ 485 } 486 487 void 488 DefaultManager::FindAudioMixer() 489 { 490 live_node_info info; 491 int32 count; 492 status_t rv; 493 494 count = 1; 495 rv = BMediaRoster::Roster()->GetLiveNodes(&info, &count, NULL, NULL, NULL, B_BUFFER_PRODUCER | B_BUFFER_CONSUMER | B_SYSTEM_MIXER); 496 if (rv != B_OK || count != 1) { 497 printf("Couldn't find audio mixer node\n"); 498 return; 499 } 500 fAudioMixer = info.node.node; 501 printf("Default audio mixer node created\n"); 502 } 503 504 status_t 505 DefaultManager::ConnectMixerToOutput() 506 { 507 BMediaRoster *roster; 508 media_node timesource; 509 media_node mixer; 510 media_node soundcard; 511 media_input inputs[MAX_INPUT_INFOS]; 512 media_input input; 513 media_output output; 514 media_input newinput; 515 media_output newoutput; 516 media_format format; 517 BTimeSource * ts; 518 bigtime_t start_at; 519 int32 count; 520 status_t rv; 521 522 roster = BMediaRoster::Roster(); 523 524 rv = roster->GetNodeFor(fPhysicalAudioOut, &soundcard); 525 if (rv != B_OK) { 526 printf("DefaultManager: failed to find soundcard (physical audio output)\n"); 527 return B_ERROR; 528 } 529 530 rv = roster->GetNodeFor(fAudioMixer, &mixer); 531 if (rv != B_OK) { 532 roster->ReleaseNode(soundcard); 533 printf("DefaultManager: failed to find mixer\n"); 534 return B_ERROR; 535 } 536 537 // we now have the mixer and soundcard nodes, 538 // find a free input/output and connect them 539 540 rv = roster->GetFreeOutputsFor(mixer, &output, 1, &count, B_MEDIA_RAW_AUDIO); 541 if (rv != B_OK || count != 1) { 542 printf("DefaultManager: can't find free mixer output\n"); 543 rv = B_ERROR; 544 goto finish; 545 } 546 547 rv = roster->GetFreeInputsFor(soundcard, inputs, MAX_INPUT_INFOS, &count, B_MEDIA_RAW_AUDIO); 548 if (rv != B_OK || count < 1) { 549 printf("DefaultManager: can't find free soundcard inputs\n"); 550 rv = B_ERROR; 551 goto finish; 552 } 553 554 for (int32 i = 0; i < count; i++) { 555 input = inputs[i]; 556 if(input.destination.id == fPhysicalAudioOutInputID) 557 break; 558 } 559 560 for (int i = 0; i < 6; i++) { 561 switch (i) { 562 case 0: 563 printf("DefaultManager: Trying connect in native format (1)\n"); 564 if (B_OK != roster->GetFormatFor(input, &format)) { 565 ERROR("DefaultManager: GetFormatFor failed\n"); 566 continue; 567 } 568 // XXX BeOS R5 multiaudio node bug workaround 569 if (format.u.raw_audio.channel_count == 1) { 570 printf("##### WARNING! DefaultManager: ignored mono format\n"); 571 continue; 572 } 573 break; 574 575 case 1: 576 printf("DefaultManager: Trying connect in format 1\n"); 577 memset(&format, 0, sizeof(format)); 578 format.type = B_MEDIA_RAW_AUDIO; 579 format.u.raw_audio.frame_rate = 44100; 580 format.u.raw_audio.channel_count = 2; 581 format.u.raw_audio.format = 0x2; 582 break; 583 584 case 2: 585 printf("DefaultManager: Trying connect in format 2\n"); 586 memset(&format, 0, sizeof(format)); 587 format.type = B_MEDIA_RAW_AUDIO; 588 format.u.raw_audio.frame_rate = 48000; 589 format.u.raw_audio.channel_count = 2; 590 format.u.raw_audio.format = 0x2; 591 break; 592 593 case 3: 594 printf("DefaultManager: Trying connect in format 3\n"); 595 memset(&format, 0, sizeof(format)); 596 format.type = B_MEDIA_RAW_AUDIO; 597 break; 598 599 case 4: 600 // BeOS R5 multiaudio node bug workaround 601 printf("DefaultManager: Trying connect in native format (2)\n"); 602 if (B_OK != roster->GetFormatFor(input, &format)) { 603 ERROR("DefaultManager: GetFormatFor failed\n"); 604 continue; 605 } 606 break; 607 608 case 5: 609 printf("DefaultManager: Trying connect in format 4\n"); 610 memset(&format, 0, sizeof(format)); 611 break; 612 613 } 614 rv = roster->Connect(output.source, input.destination, &format, &newoutput, &newinput); 615 if (rv == B_OK) 616 break; 617 } 618 if (rv != B_OK) { 619 ERROR("DefaultManager: connect failed\n"); 620 goto finish; 621 } 622 623 roster->SetRunModeNode(mixer, BMediaNode::B_INCREASE_LATENCY); 624 roster->SetRunModeNode(soundcard, BMediaNode::B_RECORDING); 625 626 roster->GetTimeSource(×ource); 627 roster->SetTimeSourceFor(mixer.node, timesource.node); 628 roster->SetTimeSourceFor(soundcard.node, timesource.node); 629 roster->PrerollNode(mixer); 630 roster->PrerollNode(soundcard); 631 632 ts = roster->MakeTimeSourceFor(mixer); 633 start_at = ts->Now() + 50000; 634 roster->StartNode(mixer, start_at); 635 roster->StartNode(soundcard, start_at); 636 ts->Release(); 637 638 finish: 639 roster->ReleaseNode(mixer); 640 roster->ReleaseNode(soundcard); 641 roster->ReleaseNode(timesource); 642 return rv; 643 } 644 645 void 646 DefaultManager::Dump() 647 { 648 } 649 650 void 651 DefaultManager::CleanupTeam(team_id team) 652 { 653 } 654 655