Set up the main event and audio loop

After your call to setup_snd() returns successfully, you have a few more tasks to complete in main() before you can start writing your main event and audio loops.

First, obtain the audio buffer size. Then, you must read the data chunk from the .wav file and allocate the appropriate amount of memory for the buffer. If the memory allocation fails, you will have an error case. When this occurs, 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, 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 you still have samples to write). Second, it checks whether the number of bytes read is more than 0 (so that you're not stuck reading zero bytes).

For the main event loop, you need a variable for BlackBerry Platform Services events, and you can use the bps_event_t structure. In the condition of the main event loop, store the event by calling bps_get_event() and then check if it's equal to BPS_SUCCESS (0).

When that check evaluates to TRUE (not 0), you can check if the event has been 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) {
   /*
    * If it's a NAVIGATOR_EXIT event then we're done and
    * can stop processing events, clean up, and exit
    */

Handle navigator exit events

Begin by checking for navigator exit events that are generated when the user closes the app. To check for these events, check if the event domain is equal to the unique domain ID for the navigator service. If the domains are equal, then check if the event code is equal to NAVIGATOR_EXIT. If the event code is equal to NAVIGATOR_EXIT, then you have an exit scenario. In this exit scenario, you should 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 you want to check for is an audio device event. When this event occurs, a new audio device has been enabled. Confirm that the new audio device path is valid, 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, you 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, an error has occurred and you must close the mixer and PCM handle, in addition to the actions you take if setup_snd() fails.

If an error occurs when you close the mixer, you must close the PCM handle, in addition to the steps you take if setup_snd() fails and if the audio device path is NULL.

If an error occurs when you close the PCM handle, you can 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()) {

/*
 * If it is a audio device event then it means a new audio device
 * has been enabled and a switch is required.  We close the old,
 * open the new audio device using the path and get the card number so
 * that we can close and re-open the mixer with the new card
 * number.
 */

    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)) {
   /*
    * setup_snd() closes pcm and mixer handles in the case of error so we
    * skip clean up of the handles in the case of failure.
    */
        goto fail3;
    }
  }
}

In the main audio loop

Now that you've handled the main event loop, you can handle the main audio loop. First, set up the file descriptor sets. There are handles for two file descriptor sets: read and write. If your app is part of the foreground process group, you must include the standard input stream to the read descriptor set, so that you can display dialog boxes. Then, you 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 you can 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 you 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, check if the PCM file descriptor is ready. If it is, you can fill the sample buffer with audio data. If fread() doesn't read anything, you must skip the remaining code in the main audio loop and continue on. But if you read the audio data successfully, you can write the sample data to the audio device. You can 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), you can confirm it using the snd_pcm_plugin_status() function. If the return value is less than 0, there is a buffer underrun.

If the function succeeds and the status is SND_PCM_STATUS_READY or SND_PCM_STATUS_UNDERRUN, you still have a buffer underrun. When you have a buffer underrun you can display a message to the user, but you must 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, you check if the bytes written are less than 0 (an error case), so you can 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, you don't have a buffer underrun and you can add the number of bytes written this iteration to the total number of bytes written. You 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 discussed, there are failure and success scenarios, depending on what happens in the execution of the app. On a successful run, you 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 app. On unsuccessful runs, you must take appropriate action depending on the failure scenario.

success:
    bytes_read = snd_pcm_plugin_flush(pcm_handle,
                                      SND_PCM_CHANNEL_PLAYBACK);
    final_return_code = 0;
             /*    
             *there are return codes to these close calls, but we would do the same    
             *thing regardless of error or success so we do not check the return codes.
             */
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 box.
         *
         * 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 the dialog, if it exists and cleanup screen resources.
        */
    destroy_dialog ();
    cleanup_screen();
    bps_shutdown();

    return final_return_code;
}

Last modified: 2015-07-24



Got questions about leaving a comment? Get answers from our Disqus FAQ.

comments powered by Disqus