Set up the libasound audio mixer and PCM components

To abstract the libasound and PCM setup, we can create a function that performs all of the setup operations. In this section, we will write the function setup_snd(). To set up these components, we use the libasound library.

Set up the libasound mixer and PCM components

Let's begin with the signature of our sound setup function.

int setup_snd(const char * name)

The function accepts a parameter, const char *name, which is the name of the device to set up. If the parameter is NULL, the default value is "pcmPreferred". The return value is SUCCESS on a successful run and FAILURE on an unsuccessful run.

Now, we set up the libasound mixer and PCM components in our function. First, we declare the following variables: an integer value for the size of the audio fragment, an integer value for the number of fragments, an integer value for the return value, and a character array for the device name.
int fragsize = -1;
int num_frags = -1;
int rtn;
char *dev_name;
Set the name of the device based on the parameter. If the parameter is NULL, use "pcmPreferred".
if (NULL == name) {
    dev_name = "pcmPreferred";
} else {
    dev_name = (char *) name;
}
Open the PCM device handle using snd_pcm_open_name(). Note that we are passing a pointer to the snd_pcm_t structure; the snd_pcm_open_name function fills this structure with the appropriate information. If the return value from the function is less than 0, we have an error case. In an error case, we display the error to the user and exit with an error condition.
if ((rtn = snd_pcm_open_name(&pcm_handle, dev_name,
                             SND_PCM_OPEN_PLAYBACK)) < 0) {
    snprintf(msg, MSG_SIZE, "snd_pcm_open_name failed: %s\n",
             snd_strerror(rtn));
    show_dialog_message(msg);
    return FAILURE;
}
Obtain the capabilities of the PCM device using snd_pcm_info(). We can access the PCM information using structure variables. If the function returns a value less than 0, we have an error case. In an error case, we display the error to the user, close the PCM handle, and exit with an error condition. Last, we set our global card variable to the sound card number.
if ((rtn = snd_pcm_info(pcm_handle, &info)) < 0) {
    snprintf(msg, MSG_SIZE, "snd_pcm_info failed: %s\n",
             snd_strerror(rtn));
    goto setup_failure;
}
card = info.card;
Next, we can disable memory-mapped input and output (which maps files or devices into memory). This step isn't required for the PlayWav sample, but we include it here in case it's required for your program. If the snd_pcm_plugin_set_disable() function returns a value less than 0, we have an error case. In an error case, we display the error to the user, close the PCM handle, and exit with an error condition.
if ((rtn = snd_pcm_plugin_set_disable(pcm_handle,
                                      PLUGIN_DISABLE_MMAP)) < 0) {
    snprintf(msg, MSG_SIZE, "snd_pcm_plugin_set_disable failed: %s\n",
             snd_strerror(rtn));
    goto setup_failure;
}
We use memset() to set all of the values in the typedef structure snd_pcm_channel_info_t to 0. Then, we set the channel information to playback mode using SND_PCM_CHANNEL_PLAYBACK. Next, we obtain channel information using the snd_pcm_plugin_info() function. An audio channel is a separate stream of audio information. Examples of such channel systems include mono audio devices, which have one channel, stereo sources, which have two channels (left and right), and so on. If the function returns a value less than 0, we have an error case. In an error case, we display the error to the user, close the PCM handle, and exit with an error condition.
memset(&pi, 0, sizeof(pi));
pi.channel = SND_PCM_CHANNEL_PLAYBACK;
if ((rtn = snd_pcm_plugin_info(pcm_handle, &pi)) < 0) {
    snprintf(msg, MSG_SIZE, "snd_pcm_plugin_info failed: %s\n",
             snd_strerror(rtn));
    goto setup_failure;
}

Clear the snd_pcm_channel_params_t structure using the memset() function. Then we configure the PCM parameter information based on .wav file format. To do so, we set the channel direction (to play the audio file instead of record new audio), channel mode, start mode (to start playing audio when the whole queue is filled), stop mode (to stop playing audio when an underrun or overrun occurs), audio fragment size, maximum audio fragment size, and the number of audio fragments. An underrun occurs if data is written at lower speed than it's being read, and an overrun occurs if data is written at higher speed than it's being read.

Then we set format information based on the .wav format that we obtained for interleaving samples, the requested rate in Hz, the number of audio channels, and the format (some form of compression or normal PCM format). If we use a variable from our .wav header structure, we must convert the value to little-endian format using the ENDIAN_LE<bits used for type>() macro. Then we set the name of the sw_mixer subchannel group to the wave playback channel. Finally, we call the snd_pcm_plugin_params() function to obtain additional information that we couldn't determine using the .wav format. If this function returns a value less than 0, we have an error case. In an error case, we display the error to the user, close the PCM handle, and exit with an error condition.
memset(&pp, 0, sizeof(pp));
pp.mode = SND_PCM_MODE_BLOCK;
pp.channel = SND_PCM_CHANNEL_PLAYBACK;
pp.start_mode = SND_PCM_START_FULL;
pp.stop_mode = SND_PCM_STOP_STOP;
pp.buf.block.frag_size = pi.max_fragment_size;
if (fragsize != -1) {
    pp.buf.block.frag_size = fragsize;
}
pp.buf.block.frags_max = num_frags;
pp.buf.block.frags_min = 1;
pp.format.interleave = 1;
pp.format.rate = sample_rate;
pp.format.voices = sample_channels;

if (ENDIAN_LE16(wav_header.format_tag) == 6)
    pp.format.format = SND_PCM_SFMT_A_LAW;
else if (ENDIAN_LE16(wav_header.format_tag) == 7)
    pp.format.format = SND_PCM_SFMT_MU_LAW;
else if (sample_bits == 8)
    pp.format.format = SND_PCM_SFMT_U8;
else if (sample_bits == 24)
    pp.format.format = SND_PCM_SFMT_S24;
else
    pp.format.format = SND_PCM_SFMT_S16_LE;
strcpy(pp.sw_mixer_subchn_name, "Wave playback channel");

if ((rtn = snd_pcm_plugin_params(pcm_handle, &pp)) < 0) {
    snprintf(msg, MSG_SIZE, "snd_pcm_plugin_params failed: %s\n",
             snd_strerror(rtn));
    goto setup_failure;
Prepare the hardware for playback by using the snd_pcm_plugin_prepare() function. You can use the plug-in interface for format conversion to or from the original format. Again, if this function returns a value less than 0, we have an error case. In an error case, we display the error to the user, close the PCM handle, and exit with an error condition.
if ((rtn = snd_pcm_plugin_prepare(pcm_handle,
                                  SND_PCM_CHANNEL_PLAYBACK)) < 0) {
    snprintf(msg, MSG_SIZE, "snd_pcm_plugin_prepare failed: %s\n",
             snd_strerror(rtn));
    goto setup_failure;
}

Now we set up the setup and group structures. First, clear both structures using the memset() function. Then set the setup.channel to playback mode using the SND_PCM_CHANNEL_PLAYBACK macro. Set the mixer group ID in the setup structure to point to the mixer group ID memory location in the group structure, so when we obtain the mixer group information, our PCM channel configuration structure already references the appropriate mixer group. Then we call snd_pcm_plugin_setup() to fill the setup structure with information about the current configuration of the PCM channel. If this function returns a value less than 0, we have an error case. In an error case, we display the error to the user, close the PCM handle, and exit with an error condition.

Note that snd_pcm_plugin_setup() selects the most appropriate mixer group with respect to the PCM subchannel. Then, we check that the mixer group ID name is set by checking if the first character, group.gid.name[0], is equal to 0. If the name is not set, we have an error case, so display the error to the user, close the PCM handle, and exit with an error condition.
memset(&setup, 0, sizeof(setup));
memset(&group, 0, sizeof(group));
setup.channel = SND_PCM_CHANNEL_PLAYBACK;
setup.mixer_gid = &group.gid;

if ((rtn = snd_pcm_plugin_setup(pcm_handle, &setup)) < 0) {
    snprintf(msg, MSG_SIZE, "snd_pcm_plugin_setup failed: %s\n",
             snd_strerror(rtn));
    goto setup_failure;
}
if (group.gid.name[0] == 0) {
    snprintf(msg, MSG_SIZE, "Mixer Pcm Group [%s] Not Set \n",
             group.gid.name);
    goto setup_failure;
}
The last step in setting up the libasound mixer and PCM components is opening a handle to the mixer using the snd_mixer_open() function. Note that we are passing a pointer to the snd_mixer_t structure, which the function populates. We also pass the sound card number and mixer device for this subchannel to the function. Again, if this function returns a value less than 0, we have an error case. In an error case, we display the error to the user, close the PCM handle, and exit with an error condition.
if ((rtn = snd_mixer_open(&mixer_handle, card,
                          setup.mixer_device)) < 0) {
    snprintf(msg, MSG_SIZE, "snd_mixer_open failed: %s\n",
             snd_strerror(rtn));
    goto setup_failure;
}
Now that we've set up everything for the libasound mixer and PCM components, let's display some of the information that we obtained to the user.
char tmp[MSG_SIZE];
snprintf(msg, MSG_SIZE, "Format %s \n", snd_pcm_get_format_name(setup.format.format));
snprintf(tmp, MSG_SIZE, "Frag Size %d \n", setup.buf.block.frag_size);
strlcat(msg, tmp, MSG_SIZE);
snprintf(tmp, MSG_SIZE, "Total Frags %d \n", setup.buf.block.frags);
strlcat(msg, tmp, MSG_SIZE);
snprintf(tmp, MSG_SIZE, "Rate %d \n", setup.format.rate);
strlcat(msg, tmp, MSG_SIZE);
snprintf(tmp, MSG_SIZE, "Voices %d \n", setup.format.voices);
strlcat(msg, tmp, MSG_SIZE);
snprintf(tmp, MSG_SIZE, "Mixer Pcm Group [%s]\n", group.gid.name);
strlcat(msg, tmp, MSG_SIZE);
show_dialog_message(msg);
For error cases, we display a message to the user, close the PCM handle, and exit with a failure condition.
setup_failure:
    show_dialog_message(msg);
    snd_pcm_close(pcm_handle);
    return FAILURE;
}

Call our setup_snd() function

Now that we've written setup_snd(), we call this function from our calling function using the default name, "pcmPreferred". If the return value is FAILURE, we have an error case. If an error case occurs at this point, we 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 an error code.
if (setup_snd(NULL)) {
    goto fail2;
}

Last modified: 2014-09-29



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

comments powered by Disqus