linux音频设备两种框架: OSS(有两个最基本的音频设备 mixer和 dsp), ALSA。

音频设备硬件接口:

1.  PCM

由时钟BLCK,帧同步信号FS,接收数据DR,发送数据DX组成。在FS上升沿,数据从MSB开始,FS等于采样频率。

2.  IIS

当LRCLK为高时,左声道数据被传输;当LRCLK为低时,右声道数据被传输。

3.  AC97

AC97采用AC-link与外部的编码器相连,AC-link包括时钟,同步信号校正,和从编码到处理器及从处理器到编码的数据队列。其数据帧包括12个20位的时间段及16位tag段,256个数据队列。把帧分成时间段使传输控制信号和音频数据仅通过4根线到达9个音频通道或转换成其他数据流成为可能。与IIS相比,AC97明显减少了整体管脚数。

 

Linux的OSS设备驱动

1.     mixer接口

int register_sound_mixer(struct file_operations *fops, int dev);

mixer为典型的字符设备,编码的主要工作是实现 fops的open(),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\n", cmd);

        return -ENOSYS;

    }

   

    audio_mix_modcnt++;

    return 0;

}

2.     dsp接口

int register_sound_dsp(struct file_operations *fops, int dev);

dsp也是典型的字符设备,同样主要工作是实现fops的操作。

Dsp的poll()函数向用户反馈目前能否读取DMA缓冲区。

 

OSS用户空间编程步骤:

DSP编程:

1.  打开设备文件/dev/dsp 全双工才能读写打开

2.  设置缓冲区大小 紧跟在打开之后,ioctl()函数来设置

3.  设置声道数量

4.  设置采样格式和采样频率

5.  读写/dev/dsp实现播放或录音

OSS的dsp实例

/*                                                  

 * sound.c  

 * 先录制几秒种音频数据,将其存放在内存缓冲区中,然后再进行回放,其所有的功能都是通过读写/dev/dsp设备文件来完成                                        

 */                                                 

#include <unistd.h>                                 

#include <fcntl.h>                                  

#include <sys/types.h>                              

#include <sys/ioctl.h>                              

#include <stdlib.h>                                 

#include <stdio.h>                                  

#include <linux/soundcard.h>                        

#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:\n");                     

    status = read(fd, buf, sizeof(buf)); /* 录音 */ 

    if (status != sizeof(buf))                       

      perror("read wrong number of bytes");         

    printf("You said:\n");                          

    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 <unistd.h>                                                  

#include <stdlib.h>                                                 

#include <stdio.h>                                                  

#include <sys/ioctl.h>                                              

#include <fcntl.h>                                                   

#include <linux/soundcard.h>                                        

/* 用来存储所有可用混音设备的名称 */                                

const char *sound_device_names[] = SOUND_DEVICE_NAMES;              

int fd;                  /* 混音设备所对应的文件描述符 */           

int devmask, stereodevs; /* 混音器信息对应的位图掩码 */             

char *name;                                                          

/* 显示命令的使用方法及所有可用的混音设备 */                        

void usage()                                                        

{                                                                   

  int i;                                                             

  fprintf(stderr, "usage: %s <device> <left-gain%%> <right-gain%%>\n"

      "       %s <device> <gain%%>\n\n"                               

      "Where <device> is one of:\n", name, name);                      

  for (i = 0 ; i < SOUND_MIXER_NRDEVICES ; i++)                     

    if ((1 << i) & devmask) /* 只显示有效的混音设备 */              

      fprintf(stderr, "%s ", sound_device_names[i]);                

  fprintf(stderr, "\n");                                            

  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\n", 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\n", 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%%\n", 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设备

每个声卡最多可以有4个PCM实例,一个实例对应一个设备文件,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()完成编解码器附加的初始化。

3.     修改寄存器

Snd_ac97_updata(),Snd_ac97_write(),前者值存在时不会再设置

Snd_ac97_updata_bits()更新寄存器某些位

4.     proc文件

ALSA AC97接口会创建proc文件,查看编解码器目前的状态和寄存器

 

ALSA用户空间编程(应用alsa-lib)

过程:打开-设置参数-读写音频数据

/*                                                                                                      

 *  This extra small demo sends a random samples to your speakers.                                     

 */                                                                                                     

                                                                                                       

#include "../include/asoundlib.h"                                                                       

                                                                                                       

static char *device = "default";                        /* playback device */                          

                                                                                                        

snd_output_t *output = NULL;                                                                           

unsigned char buffer[16*1024];                          /* some random data */                          

                                                                                                       

int main(void)                                                                                         

{                                                                                                       

        int err;                                                                                       

        unsigned int i;                                                                                 

        snd_pcm_t *handle;                                                                             

        snd_pcm_sframes_t frames;                                                                      

                                                                                                        

        for (i = 0; i < sizeof(buffer); i++)                                                           

                buffer[i] = random() & 0xff;                                                            

                                                                                                       

        if ((err = snd_pcm_open(&handle, device, SND_PCM_STREAM_PLAYBACK, 0)) < 0) {                   

                printf("Playback open error: %s\n", snd_strerror(err));                                

                exit(EXIT_FAILURE);                                                                    

        }                                                                                               

        if ((err = snd_pcm_set_params(handle,                                                          

                                      SND_PCM_FORMAT_U8,                                                

                                      SND_PCM_ACCESS_RW_INTERLEAVED,                                   

                                      1,                                                               

                                      48000,                                                           

                                      1,                                                               

                                      500000)) < 0) {   /* 0.5sec */                                    

                printf("Playback open error: %s\n", snd_strerror(err));                                

                exit(EXIT_FAILURE);                                                                    

        }                                                                                              

                                                                                                       

        for (i = 0; i < 16; i++) {                                                                      

                frames = snd_pcm_writei(handle, buffer, sizeof(buffer));                               

                if (frames < 0)                                                                         

                        frames = snd_pcm_recover(handle, frames, 0);                                   

                if (frames < 0) {                                                                      

                        printf("snd_pcm_writei failed: %s\n", snd_strerror(err));                      

                        break;                                                                         

                }                                                                                       

                if (frames > 0 && frames < (long)sizeof(buffer))                                       

                        printf("Short write (expected %li, wrote %li)\n", (long)sizeof(buffer), frames);

        }                                                                                               

                                                                                                       

        snd_pcm_close(handle);                                                                          

        return 0;                                                                                      

}  

Logo

更多推荐