![]() |
![]() |
![]() |
Throughout this chapter you'll see examples that contain code fragments that come from a functional, text-mode .wav file player. The complete file is in the wave.c example in the appendix. You may wish to compile and run the application now and then reference the running code as you progress through this chapter.
![]() |
New QNX RTP APIs are being developed for future releases. When they become available, we'll update the documentation. |
The first thing you need to do in order to play audio is to open a connection to the audio device. This connection is represented by an audio handle that you must pass into every API call. But, before you can open the device you must select it.
At any given time you may have more than one audio card active in your system, and each card may have more than one PCM device. You can see what devices are currently active on your system by looking in the /dev/snd directory. For example, the Trident 4D-Wave driver includes the following devices:
The specific card value assigned to a piece of hardware is determined at runtime. It's dependent on the order in which the drivers are started. Each card is assigned the lowest card number available at the time that it registers itself. The device values are driver specific and generally correspond to hardware ports. There are three techniques that your application can use to determine its card and device numbers: explicit selection, automatic selection, and using the mpsettings configuration file.
If you've intimate knowledge and total control over your system configuration, you can explicitly hardcode the card and device numbers directly into your application. This approach is generally not recommended because it's the most inflexible of the three.
The API includes two calls that can be used to find suitable card and device pairs for you;
This option is Photon-specific. The Neutrino Media Player, phplay uses calls into libmedia.so to read and write the file $(HOME)/.ph/mpsettings. This is the persistent store of the phplay configuration settings.
Three calls defined in <photon/MvReg.h> are used to fill and export the MvSetup_t structure. The mpsettings file is a way to override what would normally come out of a call to snd_pcm_open_preferred().
![]() |
When phplay users press the Defaults button to restore the mpsettings file to a default state, libmedia.so calls snd_pcm_open_preferred() and snd_pcm_find(). |
![]() |
More information on the mpsettings file is available in the Multimedia Technote for the QNX RTP. |
As you can see from the following example, wave.c has two possible open calls. If the card and device numbers are specified on the command line by the user, the device is opened explicitly using snd_pcm_open(). Otherwise, the default playback device is opened by snd_pcm_open_preferred() and the card and device variables are implicitly set to appropriate values. In either case, the pcm_handle now represents a valid connection to the audio hardware.
if (card == -1) { if ((rtn = snd_pcm_open_preferred (&pcm_handle, &card, &dev, SND_PCM_OPEN_PLAYBACK)) < 0) return err ("device open"); } else { if ((rtn = snd_pcm_open (&pcm_handle, card, dev, SND_PCM_OPEN_PLAYBACK)) < 0) return err ("device open"); }
Now you have a valid handle for your audio device. However, before you can use it, you need to configure it.
There are two main interfaces to the asound library: regular or plugin.
![]() |
Don't mix and match the interfaces. If you need to use the plugin interface, be consistent and only use plugin calls, if one is available. |
There are two modes that you can use to read and write to and from the drivers: block mode and stream mode. You set the mode in the snd_pcm_channel_params structure that's passed as an argument to params.mode. Valid choices are: SND_PCM_MODE_BLOCK and SND_PCM_MODE_STREAM.
The first call in the following example is to snd_pcm_plugin_set_disable(). It's used to disable the Mmap interface. This is optional. By default, the plugin interface invokes the Mmap interface on your behalf (block mode only). A status call through the Mmap interface doesn't return count (the number of bytes in the hardware buffer) or scount (the number of bytes processed, or played, since last underrun). As described in the Audio Synchronization Mechanisms chapter, these values can be useful for synchronization (e.g. lip sync) of different media. If you require synchronization, you'll want to disable the Mmap interface.
The following example shows how wave.c configures the device to use the maximum fragment size supported by the device, and the smallest number of fragments (which is 2). It also configures the device for playback of interleaved samples at the rate, format, and voices (mono or stereo) specified by the .wav file.
if ((rtn = snd_pcm_plugin_set_disable (pcm_handle, PLUGIN_DISABLE_MMAP)) < 0) { fprintf (stderr, "snd_pcm_plugin_set_disable failed: %s\n", snd_strerror (rtn)); return -1; } memset (&pi, 0, sizeof (pi)); pi.channel = SND_PCM_CHANNEL_PLAYBACK; if ((rtn = snd_pcm_plugin_info (pcm_handle, &pi)) < 0) { fprintf (stderr, "snd_pcm_plugin_info failed: %s\n", snd_strerror (rtn)); return -1; } bzero (&pp, 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; pp.buf.block.frags_max = 1; pp.buf.block.frags_min = 1; pp.format.interleave = 1; pp.format.rate = mSampleRate; pp.format.voices = mSampleChannels; if (mSampleBits == 8) pp.format.format = SND_PCM_SFMT_U8; else pp.format.format = SND_PCM_SFMT_S16_LE; if ((rtn = snd_pcm_plugin_params (pcm_handle, &pp)) < 0) { fprintf (stderr, "snd_pcm_plugin_params failed: %s\n", snd_strerror (rtn)); return -1; }
After configuring the device, you can read back the current settings as shown in the following example. This is optional, but recommended. If it's invalid, the frag_size parameter (unlike the other parameters) may be changed by the driver to a valid setting. So, the only way that you can be certain of the size and number of your buffer fragments is to verify them.
memset (&setup, 0, sizeof (setup)); setup.channel = SND_PCM_CHANNEL_PLAYBACK; setup.mixer_gid = &group.gid; if ((rtn = snd_pcm_plugin_setup (pcm_handle, &setup)) < 0) { fprintf (stderr, "snd_pcm_plugin_setup failed: %s\n", snd_strerror (rtn)); return -1; } printf ("Format %s \n", snd_pcm_get_format_name (setup.format.format)); printf ("Frag Size %d \n", setup.buf.block.frag_size); printf ("Rate %d \n", setup.format.rate); bsize = setup.buf.block.frag_size;
When you confirm your setup, you can optionally specify a pointer to a mixer_gid (as shown in the previous example) to request information on the mixer group associated with this PCM subdevice. For more information on using the mixer, see the Controlling volume and balance chapter.
The channel must be in the SND_PCM_STATUS_PREPARED or SND_PCM_STATUS_RUNNING state for it to accept writes (or reads) from the application. This is a very simple but necessary call that must happen after the audio device is configured, but before the first write occurs.
if ((rtn = snd_pcm_plugin_prepare (pcm_handle, SND_PCM_CHANNEL_PLAYBACK)) < 0) fprintf (stderr, "snd_pcm_plugin_prepare failed: %s\n", snd_strerror (rtn));
Writes to the audio subsystem can be blocking or nonblocking. They must be handled differently, and are suited for different designs. In general, we recommend using blocking calls because they're simpler to use. Nonblocking writes are only advantageous in single-threaded applications that have multiple data sinks and/or sources that must be monitored (e.g. a single-threaded Photon app). Unless you've a lot of existing audio code that requires single-threaded execution, it's much simpler and easier to spin-off a worker thread for audio and use the blocking calls. Both blocking and nonblocking writes return the number of bytes accepted by the driver or a negative error code.
The following example verifies that the number of bytes written matches the number of bytes requested for the write. If the write returns a value less then the requested, an error occurs and is handled as follows:
snd_pcm_channel_status_t status; int written = 0; if ((n = fread (mSampleBfr1, 1, min (mSamples - N, bsize), file1)) <= 0) continue; written = snd_pcm_plugin_write (pcm_handle, mSampleBfr1, n); if (written < n) { memset(&status, 0, sizeof(status)); if (snd_pcm_plugin_status (pcm_handle, &status) < 0) { fprintf (stderr, "underrun: playback channel status error\n"); exit (1); } 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) { fprintf (stderr, "underrun: playback channel prepare error\n"); exit (1); } } if (written < 0) written = 0; written += snd_pcm_plugin_write (pcm_handle, mSampleBfr1 + written, n - written); } N += written;
Before your first write, and any time the driver underruns, you must prepare the channel to ensure that everything is ready for you to start playing (or capturing) data. During a prepare, all the data stored in the driver that's related to snd_pcm_channel_status() is reset. Most importantly scount, the rolling count of bytes written to the device, is set to zero.
When an underrun occurs, it stalls the driver. No data is processed, and all writes fail until the channel is reprepared.
You close the audio device by calling snd_pcm_close(). You can optionally flush or drain the buffer.
There are two methods that you can use to ensure that the hardware buffers are completely empty:
Since the regular interface doesn't allow for partial writes, silence padding is meaningless. It simply returns zero for success after all data is played.
The flush calls regularly appear before the channel is closed to ensure that no data is left unplayed.
n = snd_pcm_plugin_flush (pcm_handle, SND_PCM_CHANNEL_PLAYBACK); rtn = snd_mixer_close (mixer_handle); rtn = snd_pcm_close (pcm_handle);
![]() |
![]() |
![]() |