I Will Survive

Body

It's not something that might happen in a rare circumstance, something that can be neglected in the design of your media application, but something that will happen as soon as the user hits that big inviting button on front of the Media preference panel - the media_server quitting while you rely on it and the connections you have established with your own and other media nodes. So, for your programs to survive this situation, is quite desirable. And, of course, Soundplay taught us first - it can be done!

The first step towards that goal is detecting a media_server restart at all. It cannot be done through any of the event listening methods that the BMediaRoster already provides. You have to look elsewhere - another roster perhaps? You register one of your loopers with the application roster to receive notices whenever an application is launched or quit. In the following example, I will use the BApplication object. From there, I will broadcast the event of a media_server restart to all parts of the application using media nodes. The best place to register with the application roster is probably BApplication::ReadyToRun() like this:

#define MEDIA_SERVER_SIG "application/x-vnd.Be.media-server"
#define ADDON_SERVER_SIG "application/x-vnd.Be.addon-host"


void
ExampleApp::ReadyToRun()
{
// Now tell the application roster, that we're interested
// in getting notifications of apps being launched or quit.
// In this way we are going to detect a media_server restart.
be_roster->StartWatching(BMessenger(this, this),
B_REQUEST_LAUNCHED | B_REQUEST_QUIT);
// we will keep track of the status of media_server
// and media_addon_server
fMediaServerRunning = be_roster->IsRunning(MEDIA_SERVER_SIG);
fMediaAddOnServerRunning = be_roster->IsRunning(ADDON_SERVER_SIG);
}

The ExampleApp class has two flags keeping track of the status of both the media_sever and media_addon_server. The reason is simply that these are two seperate applications, but equally vital for the media kit to be valid, and we don't want to take any actions, before they're not both running or have both died.

BTW, you might want to unregister with the roster too, most likely in QuitRequested().

 
bool
ExampleApp::QuitRequested()
{
bool quit = false;
// find out if we can quit here
// ...
if (quit)
be_roster->StopWatching(BMessenger(this, this));
return quit;
}

The messages notifying us of the requested events will appear in our MessageReceived() method. Though we receive messages for every application launching or quitting, we're only interested in the two servers.

 
void
ExampleApp::MessageReceived(BMessage* msg)
{
switch (msg->what) {
case B_SOME_APP_LAUNCHED:
case B_SOME_APP_QUIT: {
const char* mimeSig;
if (msg->FindString("be:signature", &mimeSig) == B_OK) {

bool isMediaServer = strcmp(mimeSig, MEDIA_SERVER_SIG) == 0;
bool isAddonServer = strcmp(mimeSig, ADDON_SERVER_SIG) == 0;
if (isMediaServer)
fMediaServerRunning = (msg->what == B_SOME_APP_LAUNCHED);
if (isAddonServer)
fMediaAddOnServerRunning = (msg->what == B_SOME_APP_LAUNCHED);
if (isMediaServer || isAddonServer)
if (!fMediaServerRunning && !fMediaAddOnServerRunning) {
fprintf(stderr, "media server has quit.
");
// everybody can start cleaning up their media nodes
BMessage quitMessage(MSG_MEDIA_SERVER_QUIT);
BroadcastMessage(&quitMessage);
}
else if (fMediaServerRunning && fMediaAddOnServerRunning) {
fprintf(stderr, "media server was launched.
");
// HACK!
// quit our now invalid instance of the media roster
// so that before new nodes are created,
// we get a new roster
BMediaRoster* roster = BMediaRoster::CurrentRoster();
if (roster) {
roster->Lock();
roster->Quit();
}
// give the servers some time to init...
snooze(3000000);
// tell everybody to re-init their media nodes
BMessage launchedMessage(MSG_MEDIA_SERVER_LAUNCHED);
BroadcastMessage(&launchedMessage);
}
}
break;
}
default:
BApplication::MessageReceived(msg);
break;
}
}

Our sample application has a method BroadcastMessage(), that simply sends a message to all of it's document windows or something of that kind. We just assume, that your application supports multiple instances of whatever it has for a media node setup. All of those need to be notified in a centralized way, which our application object takes care of.

Alright, this code above has one interesting bit. It quits the media roster instance, of which there is probably one running in your application team if you interacted with the media_server in any way. This object is just your ordinary BLooper. If by design or for other reasons remains pure speculation at this point in time - this object is of no use anymore, toast so to speak, as soon as the media_sever has been restarted. In another words, your previous connection with the server is broken. So you need a new one, which will be created for you the next time you call BMediaRoster::Roster().

The implications are this: what do you do with your invalid nodes and connections? For system nodes, you can't do anything. They will most likely be gone already. Fortunately, you can get rid of nodes without the roster. BMediaNode::Release() will serve this purpose just fine. However, you should not attempt to break the connection between nodes, that have been connected before the server died. Or you will be in for a nice little meeting with Rheinmachefrau, and that won't be pretty! Actually, the politically correct name for that thread shoud have been "Reinigungskraft", but let's not get into that too far.

All you need in your TearDownNodes() function, is a distinction between a normal clean exit, and the dirty one, that needs to follow a media_server breakdown. In the dirty case, simply ignore existing connections and system nodes, and generally don't use BMediaRoster functions. The other thing to watch out for is locking. Since our application could be notified of a media_server shutdown when another thread in our team is right in the middle of using the BMediaRoster instance, you need to lock the BMediaRoster when you start using it, and unlock it when you're done.

An example node cleanup method could thus look like this:

 
void
ExampleWindow::TearDownNodes(bool disconnect)
{
// err needs to default to B_OK, since the Roster call only
// sets it in case of an error
status_t err = B_OK;
BMediaRoster* mediaRoster = BMediaRoster::Roster(&err);
if (err != B_OK) {
fprintf(stderr, "ExampleWindow::TearDownNodes()"
" - error getting media roster: %s
",
strerror(err));
mediaRoster = NULL;
}
// begin mucking with the media roster
bool mediaRosterLocked = false;
if (mediaRoster && mediaRoster->Lock())
mediaRosterLocked = true;

if (fAudioProducer) {
status_t err;
// Ordinarily we'd stop *all* of the nodes in the chain
// at this point. However, one of the nodes is the System Mixer,
// and stopping the Mixer is a Bad Idea (tm).
// So, we just disconnect from it, and release our references
// to the nodes that we're using. We *are* supposed to do that,
// even for global nodes like the Mixer.
if (disconnect && mediaRoster) {
err = mediaRoster->Disconnect(fAudioConnection.producer.node,
fAudioConnection.source,
fAudioConnection.consumer.node,
fAudioConnection.destination);
if (err != B_OK)
printf("Error disconnecting audio nodes:"
" %ld (%s)
", err, strerror(err));
}

fAudioProducer->Release();
if (disconnect && mediaRoster) {
err = mediaRoster->ReleaseNode(fAudioConnection.consumer);
if (err != B_OK)
printf("Error releasing audio consumer: %s
",
strerror(err));
}
fAudioProducer = NULL;
snooze(20000LL);
}
// we're done mucking with the media roster
if (mediaRosterLocked)
mediaRoster->Unlock();
}


In the above code, fAudioProducer is our own media node object, and it is connected to the system mixer. The connection details (endpoints and nodes) are stored in a custom connection structure fAudioConnection. Ordinary, TearDownNodes() would be called with disconnect = true. Only in the case of a media server shutdown that flag is false. Additionally, this method does not rely on a valid BMediaRoster instance. But if it's there, it locks the object, so that nothing else can interfere.

When the object holding the media node setup, in our example a document window, receives the message from the application, that the server has died or started, this is what happens:

 
void
ExampleWindow::MessageReceived(BMessage* msg)
{
switch (msg->what) {
case MSG_MEDIA_SERVER_QUIT:
if (fNodesRunning)
StopNodes();
TearDownNodes(false);
break;
case MSG_MEDIA_SERVER_LAUNCHED:
SetupNodes();
if (fNodesRunning)
StartNodes();
break;
default:
BWindow::MessageReceived(msg);
break;
}
}

fNodesRunning is not, as you might think, representing the run status of our node setup, but if we have valid nodes at all. According to this flag, you might want to be prepared to give some feedback to the user when something went wrong.

So what's missing? The rest of the code, of course, so here it is:

 
#define ErrorAlert(str, status) \
printf(str " Error: %s
", strerror(status))

status_t
ExampleWindow::SetupNodes()
{
media_raw_audio_format format;
format = media_raw_audio_format::wildcard;
format.frame_rate = 44100.0;
format.channel_count = 2;
format.format = media_raw_audio_format::B_AUDIO_FLOAT;

if (B_HOST_IS_BENDIAN)
format.byte_order = B_MEDIA_BIG_ENDIAN;
else if (B_HOST_IS_LENDIAN)
format.byte_order = B_MEDIA_LITTLE_ENDIAN;

status_t status = B_OK;

// find the media roster
BMediaRoster* mediaRoster = BMediaRoster::Roster(&status);
if (!mediaRoster || status != B_OK) {
ErrorAlert("Can't find the media roster.", status);
mediaRoster = NULL;
return status;
}
if (mediaRoster->Lock()) {
// find the time source
media_node time_source_node;
status = mediaRoster->GetTimeSource(&time_source_node);
if (status != B_OK) {
ErrorAlert("Can't get a time source.", status);
mediaRoster->Unlock();
return status;
}

// the AudioProducer connection

fAudioProducer = new AudioProducer(fChannelManager, this);
status = mediaRoster->RegisterNode(fAudioProducer);
if (status != B_OK) {
ErrorAlert("Unable to register AudioProducer node!", status);
mediaRoster->Unlock();
return status;
}
fAudioConnection.producer = fAudioProducer->Node();

// connect to the mixer
status = mediaRoster->GetAudioMixer(&fAudioConnection.consumer);
if (status != B_OK) {
ErrorAlert("Unable to get the system mixer.", status);
mediaRoster->Unlock();
return status;
}

mediaRoster->SetTimeSourceFor(fAudioConnection.producer.node,
time_source_node.node);

// got the nodes; now we find the endpoints of the connection
media_input mixerInput;
media_output soundOutput;
int32 count = 1;
status = mediaRoster->GetFreeOutputsFor(fAudioConnection.producer,
&soundOutput, 1, &count);
if (status != B_OK) {
ErrorAlert("Unable to get a free output "
"from the producer node.", status);
mediaRoster->Unlock();
return status;
}

count = 1;
status = mediaRoster->GetFreeInputsFor(fAudioConnection.consumer,
&mixerInput, 1, &count);
if (status != B_OK) {
ErrorAlert("Unable to get a free input "
"to the mixer.", status);
mediaRoster->Unlock();
return status;
}

// got the endpoints; now we connect it!
media_format audio_format;
audio_format.type = B_MEDIA_RAW_AUDIO;
audio_format.u.raw_audio = media_raw_audio_format::wildcard;
status = mediaRoster->Connect(soundOutput.source,
mixerInput.destination,
&audio_format,
&soundOutput, &mixerInput);
if (status != B_OK) {
ErrorAlert("Unable to connect nodes.", status);
mediaRoster->Unlock();
return status;
}

// the inputs and outputs might have been reassigned during the
// nodes' negotiation of the Connect().
// That's why we wait until after Connect() finishes
// to save their contents.
fAudioConnection.format = audio_format;
fAudioConnection.source = soundOutput.source;
fAudioConnection.destination = mixerInput.destination;

// Set an appropriate run mode for the producer
mediaRoster->SetRunModeNode(fAudioConnection.producer,
BMediaNode::B_INCREASE_LATENCY);
// done mucking with the media roster
mediaRoster->Unlock();
}
return status;
}

status_t
ExampleWindow::StartNodes()
{
status_t err = B_OK;
BMediaRoster* mediaRoster = BMediaRoster::Roster(&err);
if (err != B_OK) {
fprintf(stderr, "ExampleWindow::StartNodes()"
" - error getting media roster: %s
",
strerror(err));
mediaRoster = NULL;
}

if (mediaRoster && fAudioProducer)
err = B_ERROR; // error returned when locking fails
if (mediaRoster->Lock()) {
// figure out what recording delay to use
bigtime_t latency = 0;
err = mediaRoster->
GetLatencyFor(fAudioProducer->Node(), &latency);
err = mediaRoster->
SetProducerRunModeDelay(fAudioProducer->Node(), latency);

// start the nodes
bigtime_t init_latency = 0;
err = mediaRoster->GetInitialLatencyFor(fAudioProducer->Node(),
&init_latency);
if (err != B_OK)
printf("Can't get initial latency for audio producer node"
" - Error: %s
", strerror(err));

init_latency += estimate_max_scheduling_latency();

BTimeSource* tms, real, perf;
tms = mediaRoster->MakeTimeSourceFor(fAudioProducer->Node());

real = tms->RealTime();
perf = tms->PerformanceTimeFor(real + latency + init_latency);

tms->Release();

err = mediaRoster->StartNode(fAudioConnection.producer, perf);
if (err != B_OK)
printf("Can't start the audio producer"
" - Error: %s
", strerror(err));
// done mucking with the media server
mediaRoster->Unlock();
}
return err;
}

void
ExampleWindow::StopNodes()
{
status_t status = B_OK;
BMediaRoster* mediaRoster = BMediaRoster::Roster(&status);
if (status != B_OK) {
fprintf(stderr, "ExampleWindow::StopNodes()"
" - error getting media roster: %s
",
strerror(status));
mediaRoster = NULL;
}
if (fAudioProducer && mediaRoster && mediaRoster->Lock()) {
// synchronous stop
mediaRoster->StopNode(fAudioConnection.producer, 0, true);
mediaRoster->Unlock();
}
}

Alright, I hope this article gets you up and running handling a media_server restart. If you have anymore questions, feel free to contact me. Best of luck!