Linux ALSA sound card driver 3: Create PCM Device

Source: Internet
Author: User
Tags preg sprintf

Statement: the content of this blog is created at http://blog.csdn.net/droidphone. please refer to it for help. Thank you!

1. What is PCM?

PCM is the abbreviation of Pulse-code modulation. We know that in real life, the voice heard by human ears is a analog signal. PCM is a technology that converts the voice from analog to digital signal, his principle is simply to use a fixed frequency to sample the analog signal. The sampled signal is like a series of continuous pulses with different amplitude on the waveform, quantifies the amplitude of these pulses based on a certain degree of accuracy. These quantified values are continuously output, transmitted, processed, or recorded in the storage media, all of these constitute the digital audio generation process.

Figure 1.1 sampling and quantification of analog audio

 

Two important indicators of PCM signal are sampling frequency and quantization accuracy. Currently, the sampling frequency of CD audio is usually 44100Hz, and the quantization precision is 16 bit. Normally, when playing music, the app reads audio data from the storage medium (MP3, WMA, AAC ......), after decoding, the PCM data is finally sent to the audio driver. In turn, during the recording, the audio driver continuously sends the sampled PCM data back to the application, compress and store tasks by applications. Therefore, the two core tasks of the audio driver are:

  • How does playback convert PCM data sent by user space applications into analog audio that can be recognized by human ears?
  • Capture is an application that collects and quantifies mic analog signals, converts them to PCM signals, and sends them back to the user space.
2. pcm intermediate layer in alsa-driver

ALSA has implemented a powerful PCM Middle Layer for us. In our own driver, we only need to implement some underlying functions that need to access hardware.

 

To access the PCM middle-layer code, you must first include the header file <sound/PCM. h>. If you need to access some functions related to hw_param, <sound/pcm_params.h> may also be included.

 

Each sound card can contain up to four PCM instances. Each PCM instance corresponds to a PCM device file. The limit on the number of PCM instances is the size of the bit occupied by the Linux Device number. If the 64-bit device number is used in the future, we can create more PCM instances. However, in most cases, one PCM instance is sufficient for an embedded device.

 

A pcm instance consists of a playback stream and a capture stream. The two streams have one or more substreams respectively.

Figure 2.1 PCM structure in Sound Card

 

In embedded systems, it is generally not as complex as figure 2.1. In most cases, it is a sound card, a PCM instance, and there is a playback and capture stream under PCM, playback and capture each have a substream.

 

The following figure lists several important structures of the PCM middle layer. We can look at the relationship between these structures from the UML perspective and clarify the relationship between them, we understand the implementation of the PCM intermediate layer.

Figure 2.2 Relationship Diagram of several important structures in the PCM Middle Layer

 

  • Snd_pcm is an snd_device mounted under snd_card.
  • Field in snd_pcm: streams [2]. The two elements in this array point to two snd_pcm_str structures, representing playback stream and capture stream respectively.
  • The substream field in snd_pcm_str, pointing to the snd_pcm_substream Structure
  • Snd_pcm_substream is the core of the PCM middle layer, and most tasks are processed in substream, especially in its ops (snd_pcm_ops) field, many user space applications use ALSA-lib to process driver requests by functions in this structure. Its runtime field points to the snd_pcm_runtime structure. snd_pcm_runtime records important software and hardware Running Environments and parameters of the substream.
3. Create a PCM

 

The intermediate layer of ALSA-Driver provides the API for creating PCM:

 

Int snd_pcm_new (struct snd_card * card, const char * ID, int device, int playback_count, int capture_count,
Struct snd_pcm ** RPCM );

 

Parameter DeviceIndicates the number of PCM devices under the sound card. The first PCM device starts from 0.

Playback_countThis PCM will have several playback substreams.

Capture_countIndicates that the PCM will have several capture substreams.

 

Another API used to set PCM operation function interfaces:

 

Void snd_pcm_set_ops (struct snd_pcm * PCM, int direction, struct snd_pcm_ops * OPS );

 

 

You can use the following sequence diagram to describe a new PCM call:

 

 

 

Figure 3.1 sequence diagram of creating PCM

  • Snd_card_createPCM is a device (Component) under the sound card, so the first step is to create a sound card
  • Snd_pcm_newCall this API to create a PCM before the API will do the following
    • If there is a playback stream, the corresponding substream is also created
    • If any, create a capture stream and the corresponding substream is also created
    • Call snd_device_new () to mount the PCM to the sound card. The dev_register field in the OPS parameter points to the snd_pcm_dev_register function, which is called during the sound card registration stage.
  • Snd_pcm_set_opsSet the control/operation interface function to operate on the PCM. The functions in the snd_pcm_ops structure in the parameter are usually the functions we need to implement.
  • Snd_card_registerRegister a sound card. At this stage, all logical devices under the sound card will be traversed and the registration callback function of each device will be called. For PCM, It is the snd_pcm_dev_register function mentioned in step 2, this callback function establishes the device file nodes used to communicate with the user space application (ALSA-Lib):/dev/snd/pcmcxxdxxp and/dev/snd/pcmcxxdxxc
4. Create a device file node (DEV/snd/pcmcxxdxxp, pcmcxxdxxc) 4.1 struct snd_minor

Each snd_minor struct stores the context information of a logical device under the sound card. It is filled in during the logical device creation phase and can obtain the corresponding information from the struct when the logical device is used. This struct is also required for PCM devices. This struct is defined in include/sound/CORE. h.

Struct snd_minor {<br/> int type;/* sndrv_device_type_xxx */<br/> int card;/* Card Number */<br/> int device; /* Device Number */<br/> const struct file_operations * f_ops;/* File Operations */<br/> void * private_data; /* private data for f_ops-> open */<br/> struct device * dev;/* Device for sysfs */<br/>}; <br/>

In sound/sound. C, A Global Array of the snd_minor pointer is defined:

Static struct snd_minor * snd_minors [256]; <br/>

As mentioned above, in the registration phase of the Sound Card (snd_card_register), the callback function snd_pcm_dev_register () of PCM will be called, and the function snd_register_device_for_dev () will be called in this function ():

Static int snd_pcm_dev_register (struct snd_device * Device) <br/>{< br/> ...... </P> <p>/* Register PCM */<br/> err = snd_register_device_for_dev (devtype, PCM-> card, <br/> PCM-> device, <br/> & snd_pcm_f_ops [CIDX], <br/> PCM, STR, Dev); <br/> ...... <br/>}< br/>

Go to snd_register_device_for_dev ():

Int snd_register_device_for_dev (INT type, struct snd_card * card, int Dev, <br/> const struct file_operations * f_ops, <br/> void * private_data, <br/> const char * Name, struct device * Device) <br/> {<br/> int minor; <br/> struct snd_minor * preg; </P> <p> If (snd_bug_on (! Name) <br/> return-einval; <br/> preg = kmalloc (sizeof * preg, gfp_kernel); <br/> If (preg = NULL) <br/> return-enomem; <br/> preg-> type = type; <br/> preg-> card = card? Card-> Number:-1; <br/> preg-> device = dev; <br/> preg-> f_ops = f_ops; <br/> preg-> private_data = private_data; <br/> mutex_lock (& sound_mutex); <br/> # ifdef config_snd_dynamic_minors <br/> minor = snd_find_free_minor (); <br/> # else <br/> minor = snd_kernel_minor (type, card, Dev); <br/> If (minor> = 0 & snd_minors [minor]) <br/> minor =-ebusy; <br/> # endif <br/> If (minor <0) {<br/> mutex_unlock (& sound_mutex ); <br/> kfree (preg); <br/> return minor; <br/>}< br/> snd_minors [minor] = preg; <br/> preg-> Dev = device_create (sound_class, device, mkdev (Major, minor), <br/> private_data, "% s", name ); <br/> If (is_err (preg-> Dev) {<br/> snd_minors [minor] = NULL; <br/> mutex_unlock (& sound_mutex ); <br/> minor = ptr_err (preg-> Dev); <br/> kfree (preg); <br/> return minor; <br/>}</P> <p> mutex_unlock (& sound_mutex); <br/> return 0; <br/>}< br/>

  • First, assign and initialize fields in the snd_minor structure.

    • Type: sndrv_device_type_pcm_playback/sndrv_device_type_pcm_capture
    • Card: card ID
    • Device: Number of the PCM instance. In most cases, it is 0.
    • F_ops: snd_pcm_f_ops
    • Private_data: point to the PCM instance
  • The index value of the array is determined based on the number of type, card, and PCM. Minor is also used as the device Number of the PCM device.
  • Put the address of the snd_minor structure into the Global Array snd_minors [minor ].
  • Finally, call device_create to create a device node.
4.2 create a device file

 

 

At the end of section 4.1, the device file has been created, but section 4.1 focuses on the assignment process of the snd_minors array. In this section, we will focus on the device file.

 

Return to the callback function snd_pcm_dev_register () of PCM:

Static int snd_pcm_dev_register (struct snd_device * Device) <br/>{< br/> int CIDX, err; <br/> char STR [16]; <br/> struct snd_pcm * PCM; <br/> struct device * dev; </P> <p> PCM = device-> device_data; <br/> ...... <br/> for (CIDX = 0; CIDX <2; CIDX ++) {<br/> ...... <br/> switch (CIDX) {<br/> case sndrv_pcm_stream_playback: <br/> sprintf (STR, "PCMC % ID % IP", PCM-> card-> number, PCM-> device); <br/> devtype = sndrv_de Vice_type_pcm_playback; <br/> break; <br/> case sndrv_pcm_stream_capture: <br/> sprintf (STR, "PCMC % ID % ic", PCM-> card-> number, PCM-> device); <br/> devtype = sndrv_device_type_pcm_capture; <br/> break; <br/>}< br/>/* Device pointer to use, PCM-> Dev takes precedence if <br/> * It is assigned, otherwise fall back to card's device <br/> * if possible */<br/> Dev = PCM-> dev; <br/> If (! Dev) <br/> Dev = snd_card_get_device_link (PCM-> card); <br/>/* Register PCM */<br/> err = snd_register_device_for_dev (devtype, PCM-> card, <br/> PCM-> device, <br/> & snd_pcm_f_ops [CIDX], <br/> PCM, STR, Dev ); <br/> ...... <br/>}< br/> ...... <br/>}< br/>

 

The above Code shows that for a PCM device, two device files can be generated, one for playback and the other for capture. Their Naming rules are also determined in the Code:

  • Playback -- pcmcxdxp, usually only one sound card and one PCM in the system, which is pcmc0d0p
  • Capture -- pcmcxdxc, usually only one sound card and one PCM in the system, which is pcmc0d0c

Snd_pcm_f_ops

Snd_pcm_f_ops is a standard file system file_operations structure array, which is defined in sound/CORE/pcm_native.c:

Const struct file_operations snd_pcm_f_ops [2] ={< br/>{< br/>. owner = this_module, <br/>. write = snd_pcm_write, <br/>. aio_write = snd_pcm_aio_write, <br/>. open = snd_pcm_playback_open, <br/>. release = snd_pcm_release, <br/>. llseek = no_llseek, <br/>. poll = snd_pcm_playback_poll, <br/>. unlocked_ioctl = snd_pcm_playback_ioctl, <br/>. compat_ioctl = snd_pcm_ioctl_compat, <br/>. MMAP = snd_pcm_mmap, <br/>. fasync = snd_pcm_fasync, <br/>. get_unmapped_area = snd_pcm_get_unmapped_area, <br/>}, <br/>{< br/>. owner = this_module, <br/>. read = snd_pcm_read, <br/>. aio_read = snd_pcm_aio_read, <br/>. open = snd_pcm_capture_open, <br/>. release = snd_pcm_release, <br/>. llseek = no_llseek, <br/>. poll = snd_pcm_capture_poll, <br/>. unlocked_ioctl = snd_pcm_capture_ioctl, <br/>. compat_ioctl = snd_pcm_ioctl_compat, <br/>. MMAP = snd_pcm_mmap, <br/>. fasync = snd_pcm_fasync, <br/>. get_unmapped_area = snd_pcm_get_unmapped_area, <br/>}< br/>}; <br/>

Snd_pcm_f_ops is passed in as the snd_register_device_for_dev parameter and recorded in the f_ops field of snd_minors [minor. Finally, create a device node in snd_register_device_for_dev:

Snd_minors [minor] = preg; <br/> preg-> Dev = device_create (sound_class, device, mkdev (Major, minor), <br/> private_data, "% s ", name); <br/>

 

4.3 layers of in-depth, from application to driver layer PCM 4.3.1 character device registration

The alsa_sound_init () function in sound/CORE/sound. C is defined as follows:

Static int _ init alsa_sound_init (void) <br/>{< br/> snd_major = major; <br/> constraint = cards_limit; <br/> If (register_chrdev (Major, "ALSA", & snd_fops) {<br/> snd_printk (kern_err "unable to register native major device Number % d/N", Major ); <br/> return-EIO; <br/>}< br/> If (snd_info_init () <0) {<br/> unregister_chrdev (Major, "ALSA "); <br/> return-enomem; <br/>}< br/> snd_info_minor_register (); <br/> return 0; <br/>}< br/>

The major parameter in register_chrdev is the same as the major parameter when device_create was created for the PCM device. The result is that when the application opens the device file/dev/snd/pcmcxdxp, the open callback function of snd_fops will be entered. The open process will be described in the next section.

4.3.2 enable PCM Device

We learned from the previous section that when a PCM device is opened, the open callback function of snd_fops will be called. Let's first look at the definition of snd_fops:

Static const struct file_operations snd_fops = <br/>{< br/>. Owner = this_module, <br/>. Open = snd_open <br/>}; <br/>

Follow the snd_open function, which first retrieves the device number from inode, and then uses the secondary device number as the index, retrieve the snd_minor structure filled when the PCM device was registered from the snd_minors Global Array (see section 4.1), and then retrieve the f_ops of the PCM device from the snd_minor structure, replace file-> f_op with f_ops of the PCM device, then directly call f_ops-> open () of the PCM device, and then return. Because file-> f_op has been replaced, all the read/write/IOCTL calls of the application will enter the callback function of the PCM device, that is, the callback defined in the snd_pcm_f_ops structure mentioned in section 4.2.

Static int snd_open (struct inode * inode, struct file * file) <br/>{< br/> unsigned int minor = iminor (inode ); <br/> struct snd_minor * mptr = NULL; <br/> const struct file_operations * old_fops; <br/> int err = 0; </P> <p> If (minor> = array_size (snd_minors) <br/> return-enodev; <br/> mutex_lock (& sound_mutex ); <br/> mptr = snd_minors [minor]; <br/> If (mptr = NULL) {<br/> mptr = autoload_device (minor); <br/> If (! Mptr) {<br/> mutex_unlock (& sound_mutex); <br/> return-enodev; <br/>}< br/> old_fops = file-> f_op; <br/> file-> f_op = fops_get (mptr-> f_ops ); <br/> If (file-> f_op = NULL) {<br/> file-> f_op = old_fops; <br/> err =-enodev; <br/>}< br/> mutex_unlock (& sound_mutex); <br/> If (ERR <0) <br/> return err; </P> <p> If (file-> f_op-> open) {<br/> err = file-> f_op-> open (inode, file ); <br/> If (ERR) {<br/> fops_put (file-> f_op); <br/> file-> f_op = fops_get (old_fops ); <br/>}< br/> fops_put (old_fops); <br/> return err; <br/>}</P> <p>

 

The following sequence diagram shows how the application finally calls the callback function in the snd_pcm_f_ops structure:

Figure 4.3.2.1 application operations on PCM Devices

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.