Set up the main event and audio loop

After our call to setup_snd() returns successfully, we have a few more operations in main() to complete before we start writing our main event and audio loops.

First, we obtain the audio buffer size. Then, we read the data chunk from the .wav file and allocate the appropriate amount of memory for the buffer. If the memory allocation fails, we have an error case. In that scenario, we free the misallocated buffer, close the .wav file, destroy the dialog boxes, clean up the screen, close the BlackBerry Platform Services infrastructure to stop receiving events, and exit with an error condition.
bsize = setup.buf.block.frag_size;
samples = find_tag(file, "data");
sample_buffer = malloc(bsize);
if (!sample_buffer) {
    goto fail3;
}
Next, we initialize the read and write file descriptor sets and initialize bytes_read to 1.
FD_ZERO(&rfds);
FD_ZERO(&wfds);
bytes_read = 1;

Main event and audio loop conditions

The main audio loop checks two conditions. First, it checks whether the total number of samples written is smaller than the total number of samples (which indicates that we still have samples to write). Second, it checks whether the number of bytes read is more than 0 (so that we're not stuck reading no bytes).

For the main event loop, we need a variable for BlackBerry Platform Services events, and we use the bps_event_t structure. In the condition of the main event loop, we store the event by calling bps_get_event() and then check if it is equal to BPS_SUCCESS (0). If that check evaluates to TRUE (not 0), we then check if the event is set (not equal to 0). In the event loop condition, == (is equal to) is of higher precedence than && (short-circuit bit-wise AND), so == is evaluated before &&.
while (total_written < samples && bytes_read > 0 ) {
    bps_event_t *event = NULL;

    while (BPS_SUCCESS == bps_get_event(&event, 0) && event) {

In the main event loop

Handle navigator exit events

First, we want to check for navigator exit events that are generated when the user closes the application. To check for this scenario, we must check if the event domain is equal to the unique domain ID for the navigator service. If the domains are equal, then we check if the event code is equal to NAVIGATOR_EXIT. If the event code is equal to NAVIGATOR_EXIT, we have an exit scenario. In this exit scenario, we flush the buffer, close mixer and PCM handles, free the sample buffer, close the .wav file, destroy the dialog box, clean up the screen, close the BlackBerry Platform Services infrastructure to stop receiving events, and exit with a success condition.
if (bps_event_get_domain(event) == navigator_get_domain()) {
    if (NAVIGATOR_EXIT == bps_event_get_code(event)) {
        exit_application = 1;
        goto success;
    }
}

Handle audio device events

The next event we check for is an audio device event. If this event occurs, we know that a new audio device was enabled. We want to confirm that the new audio device path is valid, and then we can close the mixer and PCM handles, and rerun the setup_snd() function while passing the new audio device path as the argument. If setup_snd() fails, we must free the sample buffer, close the .wav file, destroy the dialog box, clean up the screen, and exit with a failure condition. If the audio device path is NULL, we have an error case and must close the mixer and PCM handle, in addition to the actions we take if setup_snd() fails. If an error occurs when we close the mixer, we must close the PCM handle, in addition to the steps we take if setup_snd() fails and if the audio device path is NULL. If an error case occurs when we close the PCM handle, we display a message to the user, close the PCM handle, and exit with a failure condition.
if (bps_event_get_domain(event) == audiodevice_get_domain()) {

    const char *audiodevice_path = audiodevice_event_get_path(event);

    if (NULL == audiodevice_path) {
        snprintf(msg, MSG_SIZE, "audiodevice_event_get_path failed:
                 %s\n", snd_strerror(rtn));
        show_dialog_message(msg);
        goto fail5;
    }

    if ((rtn = snd_mixer_close(mixer_handle)) < 0) {
        snprintf(msg, MSG_SIZE, "snd_mixer_close failed: %s\n",
                 snd_strerror(rtn));
        show_dialog_message(msg);
        goto fail4;
    }

    if ((rtn = snd_pcm_close(pcm_handle)) < 0) {
        snprintf(msg, MSG_SIZE, "snd_pcm_close failed: %s\n",
                 snd_strerror(rtn));
        show_dialog_message(msg);
        goto fail3;
    }

    if (setup_snd(audiodevice_path)) {
        goto fail3;
    }
}

In the main audio loop

Now that we handled the main event loop, we can handle the main audio loop. First, we set up the file descriptor sets. We have handles for two file descriptor sets: read and write. If our application is part of the foreground process group, we must include the standard input stream to the read descriptor set, so that we can display dialog boxes. Then, we add the mixer file descriptor to the read descriptor set and add the PCM file descriptor to the write descriptor set. The select() function (which is declared in select.h) requires that the variable rtn be set to the descriptor set with the most descriptors attached plus one. Then we call select() to wait for the mixer file descriptor, the PCM file descriptor, and possibly the standard input to change status without using a timeout or signal mask. This status change means that we can start processing audio data.
if (tcgetpgrp(0) == getpid())
    FD_SET(STDIN_FILENO, &rfds);
FD_SET(snd_mixer_file_descriptor(mixer_handle), &rfds);
FD_SET(snd_pcm_file_descriptor(pcm_handle, SND_PCM_CHANNEL_PLAYBACK),
       &wfds);

rtn = max(snd_mixer_file_descriptor(mixer_handle),
          snd_pcm_file_descriptor(pcm_handle,
          SND_PCM_CHANNEL_PLAYBACK));

if (select(rtn + 1, &rfds, &wfds, NULL, NULL) == -1) {
    err("select");
    goto fail5;
}
Next, we check if the PCM file descriptor is ready. If it is, we can fill the sample buffer with audio data. Note that if fread() doesn't read anything, we must skip the remaining code in the main audio loop and continue on. But if we read the audio data successfully, we can write the sample data to the audio device. You should also note that we use a snd_pcm_channel_status_t structure to check for buffer underruns.
if (FD_ISSET(snd_pcm_file_descriptor(pcm_handle,
             SND_PCM_CHANNEL_PLAYBACK), &wfds)) {
    snd_pcm_channel_status_t status;
    int written = 0;

    if ((bytes_read = fread(sample_buffer, 1, min(samples -
                            total_written, bsize), file)) <= 0)
        continue;
    written = snd_pcm_plugin_write(pcm_handle, sample_buffer,
                                   bytes_read);
If there's the possibility of a buffer underrun (that is, if the number of bytes written is less than the number of bytes read), we confirm it using the snd_pcm_plugin_status() function. If the return value is less than 0, we know there is an underrun. If the function succeeds and the status is SND_PCM_STATUS_READY or SND_PCM_STATUS_UNDERRUN, we still have an underrun. In an underrun scenario, we display a message to the user, close the PCM and mixer handles, free the sample buffer, close the .wav file, destroy the dialog box, clean up the screen, close the BlackBerry Platform Services infrastructure to stop receiving events, and exit with a failure condition. In the final scenario in which the number of bytes written is less than the number of bytes read, we check if the bytes written is less than 0 (an error case), so we set the number of bytes written to 0 and write out the data in the sample buffer to the playback channel.
if (written < bytes_read) {
    memset(&status, 0, sizeof(status));
    status.channel = SND_PCM_CHANNEL_PLAYBACK;
    if (snd_pcm_plugin_status(pcm_handle, &status) < 0) {
        show_dialog_message("underrun: playback channel status
                             error\n");
        goto fail5;
    }

    if (status.status == SND_PCM_STATUS_READY ||
        status.status == SND_PCM_STATUS_UNDERRUN) {
        if (snd_pcm_plugin_prepare(pcm_handle, SND_PCM_CHANNEL_PLAYBACK)
            < 0) {
            show_dialog_message("underrun: playback channel prepare
                                 error\n");
            goto fail5;
        }
    }
    if (written < 0)
        written = 0;
    written += snd_pcm_plugin_write(pcm_handle, sample_buffer +
                                    written, bytes_read - written);
}

If the number of bytes written is greater than or equal to the number of bytes read, we don't have a buffer underrun and we can add the number of bytes written this iteration to the total number of bytes written. We can also close the if statement that checks if the PCM device is ready and close the main audio loop.

total_written += written;

Successful and unsuccessful runs of main()

As we discussed throughout the tutorial, there are failure and success scenarios, depending on what happens in the execution of the application. On a successful run, we flush the audio buffer by playing the remaining samples in the buffer, close the mixer and PCM handles, free the sample buffer, close the .wav file, destroy the dialog box, clean up the screen, close the BlackBerry Platform Services infrastructure to stop receiving events, and exit the application. On unsuccessful runs, we vary what we must do depending on the failure scenario.
success:
    bytes_read = snd_pcm_plugin_flush(pcm_handle,
                                      SND_PCM_CHANNEL_PLAYBACK);
    final_return_code = 0;
fail5:
    snd_mixer_close(mixer_handle);
fail4:
    snd_pcm_close(pcm_handle);
fail3:
    free(sample_buffer);
    sample_buffer = NULL;
fail2:
    fclose(file);
fail1:


    while (!exit_application) {
        /*
         * Something went wrong so there is probably an error message
         * and we don't want to exit right away because we want the
         * user to see the message in the dialog.
         *
         * Using a negative timeout (-1) in the call to
         * bps_get_event(...) ensures that we don't busy wait by
         * blocking until an event is available.
         */
        bps_event_t *event = NULL;
        bps_get_event(&event, -1);

        if (event) {
            /*
             * If it is a NAVIGATOR_EXIT event then we are done so stop
             * processing events, clean up and exit
             */
            if (bps_event_get_domain(event) == navigator_get_domain()) {
                if (NAVIGATOR_EXIT == bps_event_get_code(event)) {
                    exit_application = 1;
                }
            }
        }
    }
    destroy_dialog();
    cleanup_screen();
    bps_shutdown();

    return final_return_code;

We're finished! Now you can add .wav files to your applications to create a better user experience and make more money from your application. You can also use the QSA to handle other audio files in your application. For more information on the QSA, see the Multimedia documentation.

If you want more information about the Native SDK audio architecture, mixer architecture, and additional audio information, take a look at the following links:

Last modified: 2013-12-21

comments powered by Disqus