Listing 3. Simple Sound Playback
- /*
- This example reads standard from input and writes
- to the default PCM device for 5 seconds of data.
- */
- /* Use the newer ALSA API */
- #define ALSA_PCM_NEW_HW_PARAMS_API
- #include <alsa/asoundlib.h>
- int main() {
- long loops;
- int rc;
- int size;
- snd_pcm_t *handle;
- snd_pcm_hw_params_t *params;
- unsigned int val;
- int dir;
- snd_pcm_uframes_t frames;
- char *buffer;
- /* Open PCM device for playback. */
- rc = snd_pcm_open(&handle, "default",
- SND_PCM_STREAM_PLAYBACK, 0);
- if (rc < 0) {
- fprintf(stderr,
- "unable to open pcm device: %s\n",
- snd_strerror(rc));
- exit(1);
- }
- /* Allocate a hardware parameters object. */
- snd_pcm_hw_params_alloca(¶ms);
- /* Fill it in with default values. */
- snd_pcm_hw_params_any(handle, params);
- /* Set the desired hardware parameters. */
- /* Interleaved mode */
- snd_pcm_hw_params_set_access(handle, params,
- SND_PCM_ACCESS_RW_INTERLEAVED);
- /* Signed 16-bit little-endian format */
- snd_pcm_hw_params_set_format(handle, params,
- SND_PCM_FORMAT_S16_LE);
- /* Two channels (stereo) */
- snd_pcm_hw_params_set_channels(handle, params, 2);
- /* 44100 bits/second sampling rate (CD quality) */
- val = 44100;
- snd_pcm_hw_params_set_rate_near(handle, params,
- &val, &dir);
- /* Set period size to 32 frames. */
- frames = 32;
- snd_pcm_hw_params_set_period_size_near(handle,
- params, &frames, &dir);
- /* Write the parameters to the driver */
- rc = snd_pcm_hw_params(handle, params);
- if (rc < 0) {
- fprintf(stderr,
- "unable to set hw parameters: %s\n",
- snd_strerror(rc));
- exit(1);
- }
- /* Use a buffer large enough to hold one period */
- snd_pcm_hw_params_get_period_size(params, &frames,
- &dir);
- size = frames * 4; /* 2 bytes/sample, 2 channels */
- buffer = (char *) malloc(size);
- /* We want to loop for 5 seconds */
- snd_pcm_hw_params_get_period_time(params,
- &val, &dir);
- /* 5 seconds in microseconds divided by
- * period time */
- loops = 5000000 / val;
- while (loops > 0) {
- loops--;
- rc = read(0, buffer, size);
- if (rc == 0) {
- fprintf(stderr, "end of file on input\n");
- break;
- } else if (rc != size) {
- fprintf(stderr,
- "short read: read %d bytes\n", rc);
- }
- rc = snd_pcm_writei(handle, buffer, frames);
- if (rc == -EPIPE) {
- /* EPIPE means underrun */
- fprintf(stderr, "underrun occurred\n");
- snd_pcm_prepare(handle);
- } else if (rc < 0) {
- fprintf(stderr,
- "error from writei: %s\n",
- snd_strerror(rc));
- } else if (rc != (int)frames) {
- fprintf(stderr,
- "short write, write %d frames\n", rc);
- }
- }
- snd_pcm_drain(handle);
- snd_pcm_close(handle);
- free(buffer);
- return 0;
- }
Listing 3 extends the previous example by writing sound samples to the sound card to produce playback. In this case we read bytes from standard input, enough for one period, and write them to the sound card until five seconds of data has been transferred.
The beginning of the program is the same as in the previous example—the PCM device is opened and the hardware parameters are set. We use the period size chosen by ALSA and make this the size of our buffer for storing samples. We then find out that period time so we can calculate how many periods the program should process in order to run for five seconds.
In the loop that manages data, we read from standard input and fill our buffer with one period of samples. We check for and handle errors resulting from reaching the end of file or reading a different number of bytes from what was expected.
To send data to the PCM device, we use the snd_pcm_writei call. It operates much like the kernel write system call, except that the size is specified in frames. We check the return code for a number of error conditions. A return code of EPIPE indicates that underrun occurred, which causes the PCM stream to go into the XRUN state and stop processing data. The standard method to recover from this state is to use the snd_pcm_prepare function call to put the stream in the PREPARED state so it can start again the next time we write data to the stream. If we receive a different error result, we display the error code and continue. Finally, if the number of frames written is not what was expected, we display an error message.
The program loops until five seconds' worth of frames has been transferred or end of file read occurs on the input. We then call snd_pcm_drain to allow any pending sound samples to be transferred, then close the stream. We free the dynamically allocated buffer and exit.
We should see that the program is not useful unless the input is redirected to something other than a console. Try running it with the device /dev/urandom, which produces random data, like this:
./example3 < /dev/urandom
The random data should produce white noise for five seconds.
Next, try redirecting the input to /dev/null or /dev/zero and compare the results. Change some parameters, such as the sampling rate and data format, and see how it affects the results.
Listing 4. Simple Sound Recording
- /*
- This example reads from the default PCM device
- and writes to standard output for 5 seconds of data.
- */
- /* Use the newer ALSA API */
- #define ALSA_PCM_NEW_HW_PARAMS_API
- #include <alsa/asoundlib.h>
- int main() {
- long loops;
- int rc;
- int size;
- snd_pcm_t *handle;
- snd_pcm_hw_params_t *params;
- unsigned int val;
- int dir;
- snd_pcm_uframes_t frames;
- char *buffer;
- /* Open PCM device for recording (capture). */
- rc = snd_pcm_open(&handle, "default",
- SND_PCM_STREAM_CAPTURE, 0);
- if (rc < 0) {
- fprintf(stderr,
- "unable to open pcm device: %s\n",
- snd_strerror(rc));
- exit(1);
- }
- /* Allocate a hardware parameters object. */
- snd_pcm_hw_params_alloca(¶ms);
- /* Fill it in with default values. */
- snd_pcm_hw_params_any(handle, params);
- /* Set the desired hardware parameters. */
- /* Interleaved mode */
- snd_pcm_hw_params_set_access(handle, params,
- SND_PCM_ACCESS_RW_INTERLEAVED);
- /* Signed 16-bit little-endian format */
- snd_pcm_hw_params_set_format(handle, params,
- SND_PCM_FORMAT_S16_LE);
- /* Two channels (stereo) */
- snd_pcm_hw_params_set_channels(handle, params, 2);
- /* 44100 bits/second sampling rate (CD quality) */
- val = 44100;
- snd_pcm_hw_params_set_rate_near(handle, params,
- &val, &dir);
- /* Set period size to 32 frames. */
- frames = 32;
- snd_pcm_hw_params_set_period_size_near(handle,
- params, &frames, &dir);
- /* Write the parameters to the driver */
- rc = snd_pcm_hw_params(handle, params);
- if (rc < 0) {
- fprintf(stderr,
- "unable to set hw parameters: %s\n",
- snd_strerror(rc));
- exit(1);
- }
- /* Use a buffer large enough to hold one period */
- snd_pcm_hw_params_get_period_size(params,
- &frames, &dir);
- size = frames * 4; /* 2 bytes/sample, 2 channels */
- buffer = (char *) malloc(size);
- /* We want to loop for 5 seconds */
- snd_pcm_hw_params_get_period_time(params,
- &val, &dir);
- loops = 5000000 / val;
- while (loops > 0) {
- loops--;
- rc = snd_pcm_readi(handle, buffer, frames);
- if (rc == -EPIPE) {
- /* EPIPE means overrun */
- fprintf(stderr, "overrun occurred\n");
- snd_pcm_prepare(handle);
- } else if (rc < 0) {
- fprintf(stderr,
- "error from read: %s\n",
- snd_strerror(rc));
- } else if (rc != (int)frames) {
- fprintf(stderr, "short read, read %d frames\n", rc);
- }
- rc = write(1, buffer, size);
- if (rc != size)
- fprintf(stderr,
- "short write: wrote %d bytes\n", rc);
- }
- snd_pcm_drain(handle);
- snd_pcm_close(handle);
- free(buffer);
- return 0;
- }
Listing 4 is much like Listing 3, except that we perform PCM capture (recording). When we open the PCM stream, we specify the mode as SND_PCM_STREAM_CAPTURE. In the main processing loop, we read the samples from the sound hardware using snd_pcm_readi and write it to standard output using write. We check for overrun and handle it in the same manner as we did underrun in Listing 3.
Running Listing 4 records approximately five seconds of data and sends it to standard out; you should redirect it to a file. If you have a microphone connected to your sound card, use a mixer program to set the recording source and level. Alternatively, you can run a CD player program and set the recording source to CD. Try running Listing 4 and redirecting the output to a file. You then can run Listing 3 to play back the data:
./listing4 > sound.raw
./listing3 < sound.raw
If your sound card supports full duplex sound, you should be able to pipe the programs together and hear the recorded sound coming out of the sound card by typing: ./listing4 | ./listing3. By changing the PCM parameters you can experiment with the effect of sampling rates and formats.
Advanced Features
In the previous examples, the PCM streams were operating in blocking mode, that is, the calls would not return until the data had been transferred. In an interactive event-driven application, this situation could lock up the application for unacceptably long periods of time. ALSA allows opening a stream in nonblocking mode where the read and write functions return immediately. If data transfers are pending and the calls cannot be processed, ALSA returns an error code of EBUSY.
Many graphical applications use callbacks to handle events. ALSA supports opening a PCM stream in asynchronous mode. This allows registering a callback function to be called when a period of sample data has been transferred.
The snd_pcm_readi and snd_pcm_writei calls used here are similar to the Linux read and write system calls. The letter i indicates that the frames are interleaved; corresponding functions exist for non-interleaved mode. Many devices under Linux also support the mmap system call, which maps them into memory where they can be manipulated with pointers. Finally, ALSA supports opening a PCM channel in mmap mode, which allows efficient zero copy access to sound data.
Conclusion
I hope this article has motivated you to try some ALSA programming. As the 2.6 kernel becomes commonly used by Linux distributions, ALSA should become more widely used, and its advanced features should help Linux audio applications move forward.
My thanks to Jaroslav Kysela and Takashi Iwai for reviewing a draft of this article and providing me with useful comments.
转载于:https://blog.51cto.com/buaadallas/403833