Displaying Newsletter
Issue 29, 30 Oct 2002


  In This Issue:
 
An Introduction to Inline Source Code Documentation with Doxygen by Tyler Dauwalder 

If you're like me, the first time you learn what inline source code documentation is, you think "oh super, comments that take me even longer to write and make me learn some new form of markup; thank you, no." After dwelling on the idea a little more following my initial reaction, however, I warmed up to it enough to convince myself to give it a shot. And the rest, as they say, is history....

If you look closely through the Storage Kit directories in the OpenBeOS source tree, you'll notice an interesting commonality amongst nearly all of our function definitions: the vast majority of them are preceded by a funny looking /*! */ style comment containing a chunk of descriptive text and a bunch of backslashes floating around in odd places. That, my friend, is inline source code documentation.

Dilligent masochists that we are, most of the Storage Kit's public and private APIs are documented using Doxygen, a program that reads specially formatted comments in your code and converts them into a big organized mess of uber-cross-referenced documentation.

For those in the peanut gallery who remain unconvinced, I suggest you go download and install Doxygen 1.2.1 (this is not the latest posted release, but the two other releases are just minor version updates, and this version produces the most asthetically pleasing output of the three :-). After you have Doxygen installed, grab a copy of our daily CVS tarball (be warned, it's 9MB or so :-), and march on over to the 'current/docs/develop/storage/' directory contained therein. Once you arrive, open a Terminal window, run the command:

doxygen doxygen_config
and watch as a bunch of garbage screams past.

After things have settled down, you ought to have a shiny new 'current/docs/develop/storage/html/index.html' file waiting to be opened. Open it.

Assuming everything worked properly, you will have before you the automagically generated OpenBeOS Storage Kit Documentation. The main page is pretty boring, but the list of links at the top is where the real excitement lies. The Class Hierarchy link leads to what I consider to be the most instantly useful page, but I encourage you to nose around the various other pages as well. There's a ton of information in there! All sorts of crazy cross referencing that required next to zero extra effort on our part. Inheritance diagrams, too. I've been using this thing for nine months now and it still amazes me.

Now if you browse around long enough, you'll probably start to notice a number of things that could be significantly improved. Not all functions have their parameters and return values completely and consistently documented. Everything is just lumped together, whether private or public. Some of the inheritance diagrams tend to blend with the grey background. And though reasonably complete, there are notable pieces that have been ignored.

One important thing you need to realize here, however, is that I have no idea what I'm doing with respect to configuring Doxygen. The doxygen_config file we're using is a marginally modified version of the provided default template. Further, masocists though we may be, we're also quite lazy at heart (I am at least; I think Ingo actually documents everything :-P), and thus certain things that don't seem terribly imporant at the time are, on occasion, ignored and left undocumented.

The point I'm hoping to get across here, however, is that this is still a highly useful collection of information, in spite of it being created only through the efforts of lazy programmers who hate documenting their work (though it's worth noting that it grows on you the more you do it).

Just think of what a great OpenBeBook we could have if most of the programmers involved invested a little extra time to even partially document their work as they went, and then somebody or a group of somebodies who really knew what they were doing with respect to both documentation in general and doxygen in specific dug in, filled in the cracks, and otherwise made things look professional.

I'd be willing to bet that a properly configured (and possibly slightly modified) copy of Doxygen could generate something very aesthetically similar to the BeBook we all know and love, but with the added bonus of having all content and cross references maintained and updated automatically as the source code changes. Plus we'd have the option of including the various extra indicies and diagrams Doxygen can produce. This may not be a big deal now while we're simply recreating BeOS R5, but once OpenBeOS R1 is out and we start to move beyond it, new and relatively easily maintainable documentation is going to become increasingly important.

So anyway, evangalism aside, those of you who are interested in what I'm getting at are probably wondering just where to begin. Well, for you self-reliant types, I would suggest wandering over to the Doxygen web site and checking out their online manual. All in all, it's pretty helpful and should get you going relatively painlessly. Combine that with the examples littered throughout the Storage Kit source code, and you should be pretty well off. Otherwise, wait here with bated breath and I shall return next time (or maybe the time after that... ;-) with the best crash course in Doxygen use that I can possibly distill from my limited pool of knowledge on the subject. Until then...

 
I Will Survive by Stephan Aßmus 

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.\n");
                    // 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.\n");
                    // 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\n",
                        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)\n", err, strerror(err));
        }

        fAudioProducer->Release();
        if (disconnect && mediaRoster) {
            err = mediaRoster->ReleaseNode(fAudioConnection.consumer);
            if (err != B_OK)
                printf("Error releasing audio consumer: %s\n",
                       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\n", 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\n",
                        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\n", 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\n", 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\n",
                        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 via email. Best of luck!

 
Get Involved by Michael Phipps 
Get Involved

There are two topics that, according to the old saw, you should never discuss in polite company: Religion and Politics. This time of year, though, in the United States, you have no choice but to hear about politics. Day and night, night and day, commercial after commercial. This one is no good, that one lies, the other one is stealing from you.

It is enough to drive you nuts. I want to contemplate, this week, why there is so much name-calling and back-stabbing. I think that it is time for all Americans (and this really applies to any other country that I know of, too, but I will talk about that which I know) to reflect a little bit on their political system and why they should be more involved. I want to do this, though, without discussing my particular view on the issues. :-)

Voter turnout has reached record lows over the past 10 years or so. This is a symptom of a larger issue. People, in general, don't think that they have an effect on government. They forget that politicians love their power and want to keep it. They forget that the perception of voters turning against those in power can be enough to make a difference. Not that every person should write a letter to their particular Congresspeople once a week telling them how to vote - that is the quickest route to the crackpot list. An occasional, comprehensive single issue paper, backed with some research and, preferably, signed by multiple people from their district is far more likely to get by the gatekeeper at the office and to the politician's desk. Forget internet polls, marching in the street or even calling radio shows to complain.

Everyone should vote. It takes only a few minutes (about 10, where I live). You can take a piece of paper with your choices on it and know what you are doing. It is really not all that hard and it is a worthwhile activity. If you were, say, a librarian that catered to a group of people that accepted whatever you did (bought only old, used books at garage sales), how seriously would you take your job? Why would you really agonize over how you spend your budget if no one cares? You wouldn't -- you might start all full of professional pride and desire to do the best that you can, but in a short period of time, the apathy of the other workers (who play Unreal all day) and the lack of team spirit will bring you down. Voting is the clearest, most obvious signal that we can send to say "Hey - we are watching you, we care about our government and if you don't do a good job, you can find a different job."

US government is a very interesting topic. One wise commentator recently noted that while everyone focuses on the President, his decisions and policies, for the most part, don't have anywhere near the impact as that of the governor of your state. The governor can and does affect most of the taxes you pay, most of the laws that you obey and most of the changes in the economics of your state. Certainly the US economy as a whole has some impact on states. But something as simple as the tone that a governor sets can make a difference in a whole state. County and city governments can make even more difference. The mayor of my city has a significant impact on the way that the police and teachers do their jobs; this affects citizens far more than most of what goes on in Washington D.C. We as citizens need to pay attention to these "minor" races in the "off-year" elections. These are the most important elections in our day to day lives.

Finally, I would like to encourage people to get involved directly. Find a candidate that you believe in and campaign for them. Volunteer to help at the polls. Maybe you could even become a candidate in your town or county. We need to show the politicians that we are watching, paying attention and that we care about the issues of the day. If we don't, who will?