您的位置:首页 > 运维架构 > Linux

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

2013-03-05 00:30 330 查看
   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;                                                                                      

}  

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: