arm嵌入式开发之linux音频设备

2019-07-12 17:17发布

   linux音频设备两种框架:OSS(有两个最基本的音频设备mixerdsp),ALSA 音频设备硬件接口: 1.  PCM 由时钟BLCK,帧同步信号FS,接收数据DR,发送数据DX组成。在FS上升沿,数据从MSB开始,FS等于采样频率。 2.  IIS LRCLK为高时,左声道数据被传输;当LRCLK为低时,右声道数据被传输。 3.  AC97 AC97采用AC-link与外部的编码器相连,AC-link包括时钟,同步信号校正,和从编码到处理器及从处理器到编码的数据队列。其数据帧包括1220位的时间段及16tag段,256个数据队列。把帧分成时间段使传输控制信号和音频数据仅通过4根线到达9个音频通道或转换成其他数据流成为可能。与IIS相比,AC97明显减少了整体管脚数。   LinuxOSS设备驱动 1.     mixer接口 int register_sound_mixer(struct file_operations *fops, int dev); mixer为典型的字符设备,编码的主要工作是实现 fopsopen(),ioctl()等函数。 static int smdk2410_mixer_ioctl(struct inode *inode, struct file *file,                                 unsigned int cmd, unsigned long arg) {     int ret;     long val = 0;         switch (cmd) {     case SOUND_MIXER_INFO:         {             mixer_info info;             strncpy(info.id, "UDA1341", sizeof(info.id));             strncpy(info.name,"Philips UDA1341", sizeof(info.name));             info.modify_counter = audio_mix_modcnt;             return copy_to_user((void *)arg, &info, sizeof(info));         }             case SOUND_OLD_MIXER_INFO:         {             _old_mixer_info info;             strncpy(info.id, "UDA1341", sizeof(info.id));             strncpy(info.name,"Philips UDA1341", sizeof(info.name));             return copy_to_user((void *)arg, &info, sizeof(info));         }             case SOUND_MIXER_READ_STEREODEVS:         return put_user(0, (long *) arg);             case SOUND_MIXER_READ_CAPS:         val = SOUND_CAP_EXCL_INPUT;         return put_user(val, (long *) arg);             case SOUND_MIXER_WRITE_VOLUME:         ret = get_user(val, (long *) arg);         if (ret)             return ret;         uda1341_volume = 63 - (((val & 0xff) + 1) * 63) / 100;         uda1341_l3_address(UDA1341_REG_DATA0);         uda1341_l3_data(uda1341_volume);         break;             case SOUND_MIXER_READ_VOLUME:         val = ((63 - uda1341_volume) * 100) / 63;         val |= val << 8;         return put_user(val, (long *) arg);             case SOUND_MIXER_READ_IGAIN:         val = ((31- mixer_igain) * 100) / 31;         return put_user(val, (int *) arg);             case SOUND_MIXER_WRITE_IGAIN:         ret = get_user(val, (int *) arg);         if (ret)             return ret;         mixer_igain = 31 - (val * 31 / 100);         /* use mixer gain channel 1*/         uda1341_l3_address(UDA1341_REG_DATA0);         uda1341_l3_data(EXTADDR(EXT0));         uda1341_l3_data(EXTDATA(EXT0_CH1_GAIN(mixer_igain)));         break;             default:         DPRINTK("mixer ioctl %u unknown ", cmd);         return -ENOSYS;     }         audio_mix_modcnt++;     return 0; } 2.     dsp接口 int register_sound_dsp(struct file_operations *fops, int dev); dsp也是典型的字符设备,同样主要工作是实现fops的操作。 Dsppoll()函数向用户反馈目前能否读取DMA缓冲区。   OSS用户空间编程步骤: DSP编程: 1.  打开设备文件/dev/dsp 全双工才能读写打开 2.  设置缓冲区大小 紧跟在打开之后,ioctl()函数来设置 3.  设置声道数量 4.  设置采样格式和采样频率 5.  读写/dev/dsp实现播放或录音 OSSdsp实例 /*                                                    * sound.c    * 先录制几秒种音频数据,将其存放在内存缓冲区中,然后再进行回放,其所有的功能都是通过读写/dev/dsp设备文件来完成                                          */                                                  #include                                   #include                                    #include                                #include                                #include                                   #include                                    #include                          #define LENGTH 3    /* 存储秒数 */                   #define RATE 8000   /* 采样频率 */                   #define SIZE 8      /* 量化位数 */                   #define CHANNELS 1  /* 声道数目 */                   /* 用于保存数字音频数据的内存缓冲区 */               unsigned char buf[LENGTH*RATE*SIZE*CHANNELS/8];      int main()                                           {                                                      int fd;   /* 声音设备的文件描述符 */                   int arg;  /* 用于ioctl调用的参数 */                  int status;   /* 系统调用的返回值 */                 /* 打开声音设备 */                                   fd = open("/dev/dsp", O_RDWR);                       if (fd < 0) {                                          perror("open of /dev/dsp failed");                   exit(1);                                            }                                                    /* 设置采样时的量化位数 */                           arg = SIZE;                                          status = ioctl(fd, SOUND_PCM_WRITE_BITS, &arg);      if (status == -1)                                       perror("SOUND_PCM_WRITE_BITS ioctl failed");       if (arg != SIZE)                                       perror("unable to set sample size");               /* 设置采样时的声道数目 */                           arg = CHANNELS;                                       status = ioctl(fd, SOUND_PCM_WRITE_CHANNELS, &arg);   if (status == -1)                                      perror("SOUND_PCM_WRITE_CHANNELS ioctl failed");   if (arg != CHANNELS)                                   perror("unable to set number of channels");        /* 设置采样时的采样频率 */                           arg = RATE;                                          status = ioctl(fd, SOUND_PCM_WRITE_RATE, &arg);      if (status == -1)                                      perror("SOUND_PCM_WRITE_WRITE ioctl failed");      /* 循环直到按下Control-C */                        while (1) {                                            printf("Say something: ");                          status = read(fd, buf, sizeof(buf)); /* 录音 */      if (status != sizeof(buf))                              perror("read wrong number of bytes");              printf("You said: ");                               status = write(fd, buf, sizeof(buf)); /* 回放 */     if (status != sizeof(buf))                             perror("wrote wrong number of bytes");             /* 在继续录音前等待回放结束 */                       status = ioctl(fd, SOUND_PCM_SYNC, 0);               if (status == -1)                                      perror("SOUND_PCM_SYNC ioctl failed");           }                                                   }                                                    Mixer编程: 通过驱动程序提供的设备文件/dev/mixer编程。一般通过Ioctl()系统调用来完成,所有的控制命令都以sound_mixer或者mixer开头 1.     sound_mixer_read宏读取获得的麦克的输入增益(百分比形式) ioctl(fd, sound_mixer_read(sound_mixer_mic), &vol); 提取左右声道的增益: Int left,right; left  = vol & 0xff;                                              right = (vol & 0xff00) >> 8;      2.     sound_mixer_write /* 将两个声道的值合到同一变量中 */                                   level = (right << 8) + left;                                                                                                               /* 设置增益 */                                                       status = ioctl(fd, MIXER_WRITE(device), &level);                     if (status == -1) {                                                     perror("MIXER_WRITE ioctl failed");                                  exit(1);                                                           }           3.     查询mixer信息 OSS mixer实例     /*                                                                     * mixer.c   * 对各种混音通道的增益进行调节,其所有的功能都通过读写/dev/mixer设备文件来完成                                                          */                                                                  #include                                                    #include                                                   #include                                                    #include                                                #include                                                     #include                                          /* 用来存储所有可用混音设备的名称 */                                 const char *sound_device_names[] = SOUND_DEVICE_NAMES;               int fd;                  /* 混音设备所对应的文件描述符 */            int devmask, stereodevs; /* 混音器信息对应的位图掩码 */              char *name;                                                           /* 显示命令的使用方法及所有可用的混音设备 */                         void usage()                                                         {                                                                      int i;                                                                fprintf(stderr, "usage: %s "       "       %s "                                      "Where is one of: ", name, name);                         for (i = 0 ; i < SOUND_MIXER_NRDEVICES ; i++)                          if ((1 << i) & devmask) /* 只显示有效的混音设备 */                     fprintf(stderr, "%s ", sound_device_names[i]);                   fprintf(stderr, " ");                                               exit(1);                                                           }                                                                     int main(int argc, char *argv[])                                     {                                                                      int left, right, level;  /* 增益设置 */                               int status;              /* 系统调用的返回值 */                      int device;              /* 选用的混音设备 */                        char *dev;               /* 混音设备的名称 */                        int i;                                                                name = argv[0];                                                      /* 以只读方式打开混音设备 */                                         fd = open("/dev/mixer", O_RDONLY);                                   if (fd == -1) {                                                         perror("unable to open /dev/mixer");                                 exit(1);                                                           }                                                                                                                                          /* 获得所需要的信息 */                                               status = ioctl(fd, SOUND_MIXER_READ_DEVMASK, &devmask);              if (status == -1)                                                      perror("SOUND_MIXER_READ_DEVMASK ioctl failed");                    status = ioctl(fd, SOUND_MIXER_READ_STEREODEVS, &stereodevs);        if (status == -1)                                                      perror("SOUND_MIXER_READ_STEREODEVS ioctl failed");                /* 检查用户输入 */                                                    if (argc != 3 && argc != 4)                                            usage();                                                           /* 保存用户输入的混音器名称 */                                       dev = argv[1];                                                        /* 确定即将用到的混音设备 */                                         for (i = 0 ; i < SOUND_MIXER_NRDEVICES ; i++)                          if (((1 << i) & devmask) && !strcmp(dev, sound_device_names[i]))       break;                                                            if (i == SOUND_MIXER_NRDEVICES) { /* 没有找到匹配项 */                 fprintf(stderr, "%s is not a valid mixer device ", dev);            usage();                                                           }                                                                    /* 查找到有效的混音设备 */                                           device = i;                                                          /* 获取增益值 */                                                      if (argc == 4) {                                                       /* 左、右声道均给定 */                                               left  = atoi(argv[2]);                                               right = atoi(argv[3]);                                              } else {                                                               /* 左、右声道设为相等 */                                             left  = atoi(argv[2]);                                               right = atoi(argv[2]);                                              }                                                                                                                                         /* 对非立体声设备给出警告信息 */                                     if ((left != right) && !((1 << i) & stereodevs)) {                     fprintf(stderr, "warning: %s is not a stereo device ", dev);      }                                                                                                                                          /* 将两个声道的值合到同一变量中 */                                   level = (right << 8) + left;                                                                                                              /* 设置增益 */                                                       status = ioctl(fd, MIXER_WRITE(device), &level);                     if (status == -1) {                                                    perror("MIXER_WRITE ioctl failed");                                  exit(1);                                                            }                                                                    /* 获得从驱动返回的左右声道的增益 */                                 left  = level & 0xff;                                                right = (level & 0xff00) >> 8;                                       /* 显示实际设置的增益 */                                             fprintf(stderr, "%s gain set to %d%% / %d%% ", dev, left, right);   /* 关闭混音设备 */                                                   close(fd);  return 0;                                                          }  
 
Linux的ALSA音频设备驱动   ALSA为开放源码的体系,除了像OSS提供的内核驱动程序模块之外,还专门为简化应用程序的编写提供了函数库。兼容于OSS,包括驱动包alsa-driver(很大的内核程序),开发包alsa-libs,开发包插件alsa-libplugins,设置管理工具包alsa-utils,其他相关处理小程序包alsa-tools,特殊音频固件支持包alsa-firmware,OSS接口兼容模拟层工具alsa-oss  ALSA内核提供给用户很多接口,也以文件的方式提供,但这些接口被提供给alsa-lib使用,不是直接给应用程序使用。应用程序使用alsa-lib,或更高级的接口。   Card和组件管理 Card管理这个声卡上的所有设备(组件),对声卡而言必须创建一个card。 1.创建card struct snd_card *snd_card_new(int idx, const char *xid, struct module *module,int extra_size) 2.创建组件 int snd_device_new(struct snd_card *card, snd_device_type_t type,void *device_data, struct snd_device_ops *ops) 3.组件释放 4.芯片特定的数据 一般以struct xxxchip的形式组织,包含芯片相关的i/o端口地址,资源指针,中断号等。 定义芯片特定数据方法: struct xxxchip { }; card=snd_card_new(int idx, const char *xid, struct module *module,sizeof(struct xxxchip)); struct xxxchip *chip=card->private_data; 6.  注册和释放声卡 Int snd_card_register(struct snd_card *card); Int snd_card_free(struct snd_card *card)   PCM设备 每个声卡最多可以有4PCM实例,一个实例对应一个设备文件,PCM实例由播放和录音流组成,每个PCM流又有一个或多个子流组成。 1.     PCM实例构造 Int snd_pcm_new(struct snd_card *card, char *id, int device, int playback_count, int capture_count, struct snd_pcm **rpcm);4,5参数表示录放的子流数 2.     设置PCM操作 Void snd_pcm_set_ops(struct snd_pcm *pcm,int direction, struct snd_pcm_ops *ops); struct snd_pcm_ops 中的所有操作都需要事先通过snd_pcm_substream_chip()获得xxxchip指针。 3.分配缓冲区 Int snd_pcm_lib_preallocate_pages_for_all(struct snd_pcm *pcm, int type, void *data, size_t size, size_t max); 4.设置标志 Pcm->info_flags=SNDRV_PCM_INFO_HALF_DUPLEX;半双工 5.PCM信息运行时的结构体 snd_pcm_runtime通过substream->runtime获得 包括硬件信息,dma缓冲区信息,运行状态,私有数据,中断回调函数等   控制接口control(mixer基于control内核api实现) ALSA中用snd_kcontrol结构体描述。 struct snd_kcontrol_new {     snd_ctl_elem_iface_t iface; /* interface identifier */     unsigned int device;        /* device/client number */     unsigned int subdevice;     /* subdevice (substream) number */     unsigned char *name;        /* ASCII name of item */     unsigned int index;     /* index of item */     unsigned int access;        /* access rights */     unsigned int count;     /* count of same elements */     snd_kcontrol_info_t *info;     snd_kcontrol_get_t *get;     snd_kcontrol_put_t *put;     union {         snd_kcontrol_tlv_rw_t *c;         const unsigned int *p;     } tlv;     unsigned long private_value; }; Name是名称标识字符串,定义标准是“source direction function 下面几种不采用以上格式: 全局控制 capture source/switch/volume全局录音源,输入开关,音量控制 playback switch/volume 全局输出开关,音量控制 音量控制 tone control-xxx 3D控制 3D control-xxx 麦克风增益 mic boost Access控制访问权限 private_value字段包含长整型值通过它给info(),get(),put()函数传递参数 info()函数用于获得control的详细信息(填充第二个参数) static int snd_xxxctl_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo); get()用于得到control的目前值并返回到用户空间。 Put()用于从用户空间写入值。 构造control方法: Int snd_ctl_add(struct snd_card *card, struct snd_kcontrol *kcontrol); 创建一个snd_kcontrol,并添加到card中; Struct snd_kcontrol *snd_ctl_newl(struct snd_kcontrol_new *ncontrol, void *private_data); 创建一个snd_kcontrol并返回其指针 在中断服务程序中改或变更一个control,可以调用snd_ctl_notify() Void snd_ctl_notify(struct snd_card *card,unsigned int mask, struct snd_ctl_elem_id *id);    第二个参数为事件掩码,第三个为control元素的ID指针。   AC97 API接口 编码层被很好的定义,我们只需编写少量底层的控制函数。    1.  AC97 实例构造 首先调用snd_ac97_bus()构建AC97 总线及操作,再调用snd_ac97_mixer()注册混音器  函数原型 int snd_ac97_bus(struct snd_card *card, int num, struct snd_ac97_bus_ops  *ops, void *private_data, struct snd_ac97_bus **rbus);                                                                    int snd_ac97_mixer(struct snd_ac97_bus *bus, struct snd_ac97_template *template, struct snd_ac97 **rac97); 2.snd_ac97_bus_ops结构体成员 read(),write()完成底层的硬件访问,reset()复位编解码器,wait()编解码器标准初始化过程中的特定等待,init()完成编解码器附加的初