shows audio operation on an example Linux Bluetooth MP3 player built around an embedded SoC. You can program the Linux cell phone (that we used in , "Serial Drivers," and , "Video Drivers") to download songs from the Internet at night when phone rates are presumably cheaper and upload it to the MP3 player's Compact Flash (CF) disk via Bluetooth so that you can listen to the songs next day during office commute.
Figure 13.4. Audio on a Linux MP3 player.
Our task is to develop the audio software for this device. An application on the player reads songs from the CF disk and decodes it into system memory. A kernel ALSA driver gathers the music data from system memory and dispatches it to transmit buffers that are part of the SoC's audio controller. This PCM data is forwarded to the codec, which plays the music through the device's speaker. As in the case of the navigation system discussed in the preceding chapter, we will assume that Linux supports this SoC, and that all architecture-dependent services such as DMA are supported by the kernel.
The audio software for the MP3 player thus consists of two parts:
-
A user program that decodes MP3 files reads from the CF disk and converts it into raw PCM. To write a native ALSA decoder application, you can leverage the helper routines offered by the alsa-lib library. The section "" looks at how ALSA applications interact with ALSA drivers.
You also have the option of customizing public domain MP3 players such as madplay () to suit this device.
-
A low-level kernel ALSA audio driver. The following section is devoted to writing this driver.
One possible hardware implementation of the device shown in is by using a PowerPC 405LP SoC and a Texas Instruments TLV320 audio codec. The CPU core in that case is the 405 processor and the on-chip audio controller is the Codec Serial Interface (CSI). SoCs commonly have a high-performance internal local bus that connects to controllers, such as DRAM and video, and a separate on-chip peripheral bus to interface with low-speed peripherals such as serial ports, I2C, and GPIO. In the case of the 405LP, the former is called the Processor Local Bus (PLB) and the latter is known as the On-chip Peripheral Bus (OPB). The PCMCIA/CF controller hangs off the PLB, whereas the audio controller interface connects to the OPB.
An audio driver is built out of three main ingredients:
-
Routines that handle playback
-
Routines that handle capture
-
Mixer control functions
Our driver implements playback, but does not support recording because the MP3 player in the example has no microphone. The driver also simplifies the mixer function. Rather than offering the full compliment of volume controls, such as speaker, earphone, and line-out, it allows only a single generic volume control.
The register layout of the MP3 player's audio hardware shown in mirrors these assumptions and simplifications, and does not conform to standards such as AC'97 alluded to earlier. So, the codec has a SAMPLING_RATE_REGISTER to configure the playback (digital-to-analog) sampling rate but no registers to set the capture (analog-to-digital) rate. The VOLUME_REGISTER configures a single global volume.
| Register Name | Description |
|---|---|
| VOLUME_REGISTER | Controls the codec's global volume. |
| SAMPLING_RATE_REGISTER | Sets the codec's sampling rate for digital-to-analog conversion. |
| CLOCK_INPUT_REGISTER | Configures the codec's clock source, divisors, and so on. |
| CONTROL_REGISTER | Enables interrupts, configures interrupt cause (such as completion of a buffer transfer), resets hardware, enables/disables bus operation, and so on. |
| STATUS_REGISTER | Status of codec audio events. |
| DMA_ADDRESS_REGISTER | The example hardware supports a single DMA buffer descriptor. Real-world cards may support multiple descriptors and may have additional registers to hold parameters such as the descriptor that is currently being processed, the position of the current sample inside the buffer, and so on. DMA is performed to the buffers in the audio controller, so this register resides in the controller's memory space. |
| DMA_SIZE_REGISTER | Holds the size of the DMA transfer to/from the SoC. This register also resides inside the audio controller. |
is a skeletal ALSA audio driver for the MP3 player and liberally employs pseudo code (within comments) to cut out extraneous detail. ALSA is a sophisticated framework, and conforming audio drivers are usually several thousand lines long. gets you started only on your audio driver explorations. Continue your learning by falling back to the mighty Linux-Sound sources inside the top-level sound/ directory.
Driver Methods and StructuresOur example driver is implemented as a platform driver. Let's take a look at the steps performed by the platform driver's probe() method, mycard_audio_probe(). We will digress a bit under each step to explain related concepts and important data structures that we encounter, and this will take us to other parts of the driver and help tie things together.
mycard_audio_probe()does the following:
|
1. |
Creates an instance of a sound card by invoking snd_card_new(): struct snd_card *card = snd_card_new(-1, id[dev->id], THIS_MODULE, 0); The first argument to snd_card_new() is the card index (that identifies this card among multiple sound cards in the system), the second argument is the ID that'll be stored in the id field of the returned snd_card structure, the third argument is the owner module, and the last argument is the size of a private data field that'll be made available via the private_data field of the returned snd_card structure (usually to store chip-specific data such as interrupt levels and I/O addresses). snd_card represents the created sound card and is defined as follows in include/sound/core.h: struct snd_card { int number; /* Card index */ char id[16]; /* Card ID */ /* ... */ struct module *module; /* Owner module */ void *private_data; /* Private data */ /* ... */ struct list_head controls; /* All controls for this card */ struct device *dev; /* Device assigned to this card*/ /* ... */ }; The remove() counterpart of the probe method, mycard_audio_remove(), releases the snd_card from the ALSA framework using snd_card_free(). |
|
2. |
Creates a PCM playback instance and associates it with the card created in Step 1, using snd_pcm_new(): int snd_pcm_new(struct snd_card *card, char *id, int device, int playback_count, int capture_count, struct snd_pcm **pcm); The arguments are, respectively, the sound card instance created in Step 1, an identifier string, the device index, the number of supported playback streams, the number of supported capture streams (0 in our example), and a pointer to store the allocated PCM instance. The allocated PCM instance is defined as follows in include/sound/pcm.h: Code View: struct snd_pcm { struct snd_card *card; /* Associated snd_card */ /* ... */ struct snd_pcm_str streams[2]; /* Playback and capture streams of this PCM component. Each stream may support substreams if your h/w supports it */ /* ... */ struct device *dev; /* Associated hardware device */ }; The snd_device_new() routine lies at the core of snd_pcm_new() and other similar component instantiation functions. snd_device_new() ties a component and a set of operations with the associated snd_card (see Step 3). |
|
3. |
Connects playback operations with the PCM instance created in Step 2, by calling snd_pcm_set_ops(). The snd_pcm_ops structure specifies these operations for transferring PCM audio to the codec. accomplishes this as follows: Code View: /* Operators for the PCM playback stream */ static struct snd_pcm_ops mycard_playback_ops = { .open = mycard_pb_open, /* Open */ .close = mycard_pb_close, /* Close */ .ioctl = snd_pcm_lib_ioctl, /* Use to handle special commands, else specify the generic ioctl handler snd_pcm_lib_ioctl()*/ .hw_params = mycard_hw_params, /* Called when higher layers set hardware parameters such as audio format. DMA buffer allocation is also done from here */ .hw_free = mycard_hw_free, /* Free resources allocated in mycard_hw_params() */ .prepare = mycard_pb_prepare, /* Prepare to transfer the audio stream. Set audio format such as S16_LE (explained soon), enable interrupts,.. */ .trigger = mycard_pb_trigger, /* Called when the PCM engine starts, stops, or pauses. The second argument specifies why it was called. This function cannot go to sleep */ }; /* Connect the operations with the PCM instance */ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &mycard_playback_ops); In , mycard_pb_prepare() configures the sampling rate into the SAMPLING_RATE_REGISTER, clock source into the CLOCKING_INPUT_REGISTER, and transmit complete interrupt enablement into the CONTROL_REGISTER. The trigger() method, mycard_pb_trigger(), maps an audio buffer populated by the ALSA framework on-the-fly using dma_map_single(). (We discussed streaming DMA in , "Peripheral Component Interconnect.") The mapped DMA buffer address is programmed into the DMA_ADDRESS_REGISTER. This register is part of the audio controller in the SoC, unlike the earlier registers that reside inside the codec. The audio controller forwards the DMA'ed data to the codec for playback. Another related object is the snd_pcm_hardware structure, which announces the PCM component's hardware capabilities. For our example device, this is defined in as follows: Code View: /* Hardware capabilities of the PCM playback stream */ static struct snd_pcm_hardware mycard_playback_stereo = { .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME); /* mmap() is supported. The stream has pause/resume capabilities */ .formats = SNDRV_PCM_FMTBIT_S16_LE,/* Signed 16 bits per channel, little endian */ .rates = SNDRV_PCM_RATE_8000_48000,/* DAC Sampling rate range */ .rate_min = 8000, /* Minimum sampling rate */ .rate_max = 48000, /* Maximum sampling rate */ .channels_min = 2, /* Supports a left and a right channel */ .channels_max = 2, /* Supports a left and a right channel */ .buffer_bytes_max = 32768, /* Max buffer size */ }; This object is tied with the associated snd_pcm from the open() operator, mycard_playback_open(), using the PCM runtime instance. Each open PCM stream has a runtime object called snd_pcm_runtime that contains all information needed to manage that stream. This is a gigantic structure of software and hardware configurations defined in include/sound/pcm.h and contains snd_pcm_hardware as one of its component fields. |
|
4. |
Preallocates buffers using snd_pcm_lib_preallocate_pages_for_all(). DMA buffers are subsequently obtained from this preallocated area by mycard_hw_params() using snd_pcm_lib_malloc_pages() and stored in the PCM runtime instance alluded to in Step 3. mycard_pb_trigger() DMA-maps this buffer while starting a PCM operation and unmaps it while stopping the PCM operation. |
|
5. |
Associates a mixer control element with the sound card using snd_ctl_add() for global volume control: snd_ctl_add(card, snd_ctl_new1(&mycard_playback_vol, &myctl_private)); snd_ctl_new1() takes an snd_kcontrol_new structure as its first argument and returns a pointer to an snd_kcontrol structure. defines this as follows: static struct snd_kcontrol_new mycard_playback_vol = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, /* Ctrl element is of type MIXER */ .name = "MP3 volume", /* Name */ .index = 0, /* Codec No: 0 */ .info = mycard_pb_vol_info, /* Volume info */ .get = mycard_pb_vol_get, /* Get volume */ .put = mycard_pb_vol_put, /* Set volume */ }; The snd_kcontrol structure describes a control element. Our driver uses it as a knob for general volume control. snd_ctl_add() registers an snd_kcontrol element with the ALSA framework. The constituent control methods are invoked when user applications such as alsamixer are executed. In , the snd_kcontrol put() method, mycard_playback_volume_put(), writes requested volume settings to the codec's VOLUME_REGISTER. |
|
6. |
And finally, registers the sound card with the ALSA framework: snd_card_register(card); |
codec_write_reg() (used, but left unimplemented in ) writes values to codec registers by communicating over the bus that connects the audio controller in the SoC to the external codec. If the underlying bus protocol is I2C or SPI, for example, codec_write_reg() uses the interface functions discussed in , "The Inter-Integrated Circuit Protocol."
If you want to create a /proc interface in your driver for dumping registers during debug or to export a parameter during normal operation, use the services of snd_card_proc_new() and friends. does not use /proc interface files.
If you build and load the driver module in , you will see two new device nodes appearing on the MP3 player: /dev/snd/pcmC0D0p and /dev/snd/controlC0. The former is the interface for audio playback, and the latter is the interface for mixer control. The MP3 decoder application, with the help of alsa-lib, streams music by operating over these device nodes.
Listing 13.1. ALSA Driver for the Linux MP3 Player
|
Code View: include #include #include #include #include #include #include /* Playback rates supported by the codec */ static unsigned int mycard_rates[] = { 8000, 48000, }; /* Hardware constraints for the playback channel */ static struct snd_pcm_hw_constraint_list mycard_playback_rates = { .count = ARRAY_SIZE(mycard_rates), .list = mycard_rates, .mask = 0, }; static struct platform_device *mycard_device; static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* Hardware capabilities of the PCM stream */ static struct snd_pcm_hardware mycard_playback_stereo = { .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_BLOCK_TRANSFER), .formats = SNDRV_PCM_FMTBIT_S16_LE, /* 16 bits per channel, little endian */ .rates = SNDRV_PCM_RATE_8000_48000, /* DAC Sampling rate range */ .rate_min = 8000, /* Minimum sampling rate */ .rate_max = 48000, /* Maximum sampling rate */ .channels_min = 2, /* Supports a left and a right channel */ .channels_max = 2, /* Supports a left and a right channel */ .buffer_bytes_max = 32768, /* Maximum buffer size */ }; /* Open the device in playback mode */ static int mycard_pb_open(struct snd_pcm_substream *substream) { struct snd_pcm_runtime *runtime = substream->runtime; /* Initialize driver structures */ /* ... */ /* Initialize codec registers */ /* ... */ /* Associate the hardware capabilities of this PCM component */ runtime->hw = mycard_playback_stereo; /* Inform the ALSA framework about the constraints that the codec has. For example, in this case, it supports PCM sampling rates of 8000Hz and 48000Hz only */ snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, &mycard_playback_rates); return 0; } /* Close */ static int mycard_pb_close(struct snd_pcm_substream *substream) { /* Disable the codec, stop DMA, free data structures */ /* ... */ return 0; } /* Write to codec registers by communicating over the bus that connects the SoC to the codec */ void codec_write_reg(uint codec_register, uint value) { /* ... */ } /* Prepare to transfer an audio stream to the codec */ static int mycard_pb_prepare(struct snd_pcm_substream *substream) { /* Enable Transmit DMA complete interrupt by writing to CONTROL_REGISTER using codec_write_reg() */ /* Set the sampling rate by writing to SAMPLING_RATE_REGISTER */ /* Configure clock source and enable clocking by writing to CLOCK_INPUT_REGISTER */ /* Allocate DMA descriptors for audio transfer */ return 0; } /* Audio trigger/stop/.. */ static int mycard_pb_trigger(struct snd_pcm_substream *substream, int cmd) { switch (cmd) { case SNDRV_PCM_TRIGGER_START: /* Map the audio substream's runtime audio buffer (which is an offset into runtime->dma_area) using dma_map_single(), populate the resulting address to the audio controller's DMA_ADDRESS_REGISTER, and perform DMA */ /* ... */ break; case SNDRV_PCM_TRIGGER_STOP: /* Shut the stream. Unmap DMA buffer using dma_unmap_single() */ /* ... */ break; default: return -EINVAL; break; } return 0; } /* Allocate DMA buffers using memory preallocated for DMA from the probe() method. dma_[map|unmap]_single() operate on this area later on */ static int mycard_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *hw_params) { /* Use preallocated memory from mycard_audio_probe() to satisfy this memory request */ return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params)); } /* Reverse of mycard_hw_params() */ static int mycard_hw_free(struct snd_pcm_substream *substream) { return snd_pcm_lib_free_pages(substream); } /* Volume info */ static int mycard_pb_vol_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) { uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; /* Integer type */ uinfo->count = 1; /* Number of values */ uinfo->value.integer.min = 0; /* Minimum volume gain */ uinfo->value.integer.max = 10; /* Maximum volume gain */ uinfo->value.integer.step = 1; /* In steps of 1 */ return 0; } /* Playback volume knob */ static int mycard_pb_vol_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *uvalue) { int global_volume = uvalue->value.integer.value[0]; /* Write global_volume to VOLUME_REGISTER using codec_write_reg() */ /* ... */ /* If the volume changed from the current value, return 1. If there is an error, return negative code. Else return 0 */ } /* Get playback volume */ static int mycard_pb_vol_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *uvalue) { /* Read global_volume from VOLUME_REGISTER and return it via uvalue->integer.value[0] */ /* ... */ return 0; } /* Entry points for the playback mixer */ static struct snd_kcontrol_new mycard_playback_vol = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, /* Control is of type MIXER */ .name = "MP3 Volume", /* Name */ .index = 0, /* Codec No: 0 */ .info = mycard_pb_vol_info, /* Volume info */ .get = mycard_pb_vol_get, /* Get volume */ .put = mycard_pb_vol_put, /* Set volume */ }; /* Operators for the PCM playback stream */ static struct snd_pcm_ops mycard_playback_ops = { .open = mycard_playback_open, /* Open */ .close = mycard_playback_close, /* Close */ .ioctl = snd_pcm_lib_ioctl, /* Generic ioctl handler */ .hw_params = mycard_hw_params, /* Hardware parameters */ .hw_free = mycard_hw_free, /* Free h/w params */ .prepare = mycard_playback_prepare, /* Prepare to transfer audio stream */ .trigger = mycard_playback_trigger, /* Called when the PCM engine starts/stops/pauses */ }; /* Platform driver probe() method */ static int __init mycard_audio_probe(struct platform_device *dev) { struct snd_card *card; struct snd_pcm *pcm; int myctl_private; /* Instantiate an snd_card structure */ card = snd_card_new(-1, id[dev->id], THIS_MODULE, 0); /* Create a new PCM instance with 1 playback substream and 0 capture streams */ snd_pcm_new(card, "mycard_pcm", 0, 1, 0, &pcm); /* Set up our initial DMA buffers */ snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS, snd_dma_continuous_data (GFP_KERNEL), 256*1024, 256*1024); /* Connect playback operations with the PCM instance */ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &mycard_playback_ops); /* Associate a mixer control element with this card */ snd_ctl_add(card, snd_ctl_new1(&mycard_playback_vol, &myctl_private)); strcpy(card->driver, "mycard"); /* Register the sound card */ snd_card_register(card); /* Store card for access from other methods */ platform_set_drvdata(dev, card); return 0; } /* Platform driver remove() method */ static int mycard_audio_remove(struct platform_device *dev) { snd_card_free(platform_get_drvdata(dev)); platform_set_drvdata(dev, NULL); return 0; } /* Platform driver definition */ static struct platform_driver mycard_audio_driver = { .probe = mycard_audio_probe, /* Probe method */ .remove = mycard_audio_remove, /* Remove method */ .driver = { .name = "mycard_ALSA", }, }; /* Driver Initialization */ static int __init mycard_audio_init(void) { /* Register the platform driver and device */ platform_driver_register(&mycard_audio_driver); mycard_device = platform_device_register_simple("mycard_ALSA", -1, NULL, 0); return 0; } /* Driver Exit */ static void __exit mycard_audio_exit(void) { platform_device_unregister(mycard_device); platform_driver_unregister(&mycard_audio_driver); } module_init(mycard_audio_init); module_exit(mycard_audio_exit); MODULE_LICENSE("GPL"); |
ALSA Programming
To understand how the user space alsa-lib library interacts with kernel space ALSA drivers, let's write a simple application that sets the volume gain of the MP3 player. We will map the alsa-lib services used by the application to the mixer control methods defined in . Let's begin by loading the driver and examining the mixer's capabilities:
In the volume-control application, first allocate space for the alsa-lib objects necessary to perform the volume-control operation:
Next, set the interface type to SND_CTL_ELEM_IFACE_MIXER as specified in the mycard_playback_vol structure in :
Now set the numid for the MP3 volume obtained from the amixer output above:
Open the mixer node, /dev/snd/controlC0. The third argument to snd_ctl_open() specifies the card number in the node name:
Elicit the type field in the snd_ctl_elem_info structure defined in mycard_pb_vol_info() in as follows:
Get the supported codec volume range by communicating with the mycard_pb_vol_info() driver method:
As per the definition of mycard_pb_vol_info() in , the minimum and maximum values returned by the above alsa-lib helper routines are 0 and 10, respectively.
Finally, set the desired volume and write it to the codec:
The call to snd_ctl_elem_write() results in the invocation of mycard_pb_vol_put(), which writes the desired volume gain to the codec's VOLUME_REGISTER.
| MP3 Decoding Complexity
The MP3 decoder application running on the player, as shown in , requires a supply rate of MP3 frames from the CF disk that can sustain the common MP3 sampling rate of 128KBps. This is usually not a problem for most low-MIPs devices, but in case it is, consider buffering each song in memory before decoding it. (MP3 frames at 128KBps roughly consume 1MB per minute of music.) MP3 decoding is lightweight and can usually be accomplished on-the-fly, but MP3 encoding is heavy-duty and cannot be achieved in real time without hardware assist. Voice codecs such as G.711 and G.729 used in Voice over IP (VoIP) environments can, however, encode and decode audio data in real time. |
You may turn on options under Device Drivers Sound
Advanced Linux Sound Architecture in the kernel configuration menu to include ALSA debug code (CONFIG_SND_DEBUG), verbose printk() messages (CONFIG_SND_VERBOSE_PRINTK), and verbose procfs content (CONFIG_SND_VERBOSE_PROCFS).
Procfs information pertaining to ALSA drivers resides in /proc/asound/. Look inside /sys/class/sound/ for the device model information associated with each sound-class device.
If you think you have found a bug in an ALSA driver, post it to the alsa-devel mailing list (http://mailman.alsa-project.org/mailman/listinfo/alsa-devel). The linux-audio-dev mailing list (http://music.columbia.edu/mailman/listinfo/linux-audio-dev/), also called the Linux Audio Developers (LAD) list, discusses questions related to the Linux-sound architecture and audio applications.