如何成为一个Linux系统内核开发者

你想成知道如何成为一个Linux内核开发者么?或者你的老板告诉你,“去为这个设备写一个Linux驱动。“这篇文档的目的,就是通过描述你需要经历的过程和提示你如何和社区一起工作,来教给你为达到这些目的所需要知道的所有知识。本文也尝试解释社区为什么这样工作的一些原因。

  内核几乎全是用C写成的,有一些架构相关的部分是用汇编语言写成的。熟练掌握C语言是内核开发的必备条件。汇编语言(任何架构)的了解不是必须的,除非你准备做某个架构的底层开发。虽然下面这些书不能完全代替扎实的C语言教学和/或者成年累月的经验,他们还是不错的参考,如果用得着的话: – "The C Programming Language" 作者: Kernighan and Ritchie [Prentice Hall]- "Practical C Programming" 作者: Steve Oualline [O’Reilly]

  内核是用 GNU C 和 GNU 工具链写成的。虽然它符合 ISO C89 标准,它还是使用了一些标准中没有的扩展。内核是自成体系的 C 环境,它并不依赖标准C库,所以某些C语言标准是不支持的。任意长度long long类型除法和浮点数是不被允许的。有时候会很难理解内核对于它所使用的工具链和扩展的假定,而且不幸的是也没有关于它们的绝对的参考。请查阅gcc 的info页(`info gcc`)以获取有关信息。

  请记住你是在尝试学习如何与已经存在的开发社区一起工作。这是一群成分复杂的人们,他们对于代码,风格和步骤有高的标准。这些标准是经过时间检验的。

  他们发现遵循这些标准对于这样一个大规模的且地理上分散的团队是最佳的选择。尝试提前学习尽可能多的有关这些标准的知识,因为它们都有很好的文档;不要期望别人会遵照你或者你公司的行事方式。

  法律问题

  Linux内核源代码依照GPL发布。请参考源代码树下的COPYING文件,以获取有关这个许可证的详细信息。如果你对这个许可证有疑问,请联系你的律师,不要在Linux内核邮件列表里询问。邮件列表里的人们不是律师,你不应该依赖于他们对于法律问题的解释。

  文档

  Linux内核源代码树有很多文档,它们对于学习如何与内核社区交流来说有不可估量的价值。当新的功能加进内核的时候,通常建议作者把解释这个新功能的文档也加进内核。如果一个内核变动导致了内核对用户空间界面的改变,建议你把这个信息或者一个解释了这个变动的manpage的补丁发送给手册页的维护者 [email protected]。

  这里有一个内核源代码树里需要阅读的文件列表:

  README

  这个文件简单介绍了Linux内核的背景,并描述了配置和编译内核需要做哪些事情。内核新手应该从这里开始。

  Do*****entation/Changes

  这个文件介绍了成功编译和运行内核所需要各种不同软件的列表。

  Do*****entation/CodingStyle

  这个文件描述了Linux内核代码风格,还有背后的一些原因。所有的新代码的要符合这个文档里的准则。大多数维护者只会接受符合这些规则的补丁,很多人只看符合正确风格的代码。

  Do*****entation/SubmittingPatches

  Do*****entation/SubmittingDrivers

  这些文件非常详细的介绍了如何成功的创建和发送一个补丁,包括(但不限于):

  -Email内容

  -Email格式

  -发送给谁

  遵守所有这些规则并不能保证成功(对所有的补丁都需要进行内容和风格的详细检查),但是不遵守这些规则就一定不会成功。

  其他关于如何创建补丁的很好的文章有:

  “The Perfect Patch"

  www.zip.com.au/~akpm/linux/patches/stuff/tpp.txt

  "Linux kernle patch submission format"

  linux.yyz.us/patch-format.html

  Do*****entation/stable_api_nonsense.txt

  这个文件解释了有意识的决定-不在内核里使用稳定的API-的原因,包括:

  -子系统分隔层(为了兼容?)

  -操作系统之间的驱动可移植性

  -缓和(或者阻止)内核源代码树的急速变动

  这个文档对于了解Linux的开发哲学是非常关键的,对于由开发其他操作系统转而开发Linux人也是很重要的。

  Do*****entation/SecurityBugs

  如果你感觉到你发现了Linux内核里的一个安全问题,请遵照这个文档里所描述的步骤来提醒内核开发者,并帮助解决问题。

  Do*****entation/ManagementStyle

  这个文档描述了Linux内核维护者如何运作,以及他们为什么这样做。它对于任何内核开发新手(或者任何对本话题感兴趣的人)来说是非常重要的。

  因为它解释了一些惯有的错误概念,可解决有关内核维护者独特行为的疑惑。

  Do*****entation/stable_kernel_rules.txt

  本文件描述了稳定版本内核释出的规则,还有如果你想对其中的一个版本做一些改动应该做些什么。

  Do*****entation/kernel-docs.txt

  一个有关内核开发的外部文档的列表。如果你在内核内部文档里没有找到? 要找的东西,你可以参考这个列表。

  Do*****entation/applying-patches.txt

  介绍了对于什么是补丁,以及如何应用补丁于不同的内核开发分支。

  内核也有很多可以从源代码自动产生的文档。这包括内核内部API的全面描述,有关如何处理好锁定的规则。这些文档会被创建于 Do*****entation/DocBook/文件夹中。在内核主源码树中通过运行下面的命令可以创建出PDF,Postscript, HTML 和manpage等不同格式的文档: make pdfdocsmake psdocsmake htmldocsmake mandocs

  成为一个内核开发者

  如果你对Linux内核开发一无所知,你可以看看Linux KernelNewbies项目:

  kernelnewbies.org

  它包含一个邮件列表,在那里你可以问任何有关内核开发的基础问题(在问问题之前先搜索一下存档,很可能这个问题已经被解答过了。)它还有一个IRC频道,你可以在里面实时的提问。它还有很多有用的文档,对于学习Linux内核开发很有用。

  这个网站有有关代码组织,子系统,当前项目(代码树之内的和之外的)的基本信息。它也描述了一些基本的“物流”信息,比如怎么样编译内核和怎么样打补丁。

  如果你不知道从何处起步,但是你想找一些任务来做以加入内核开发社区,请看一下Linux Kernel Janitor项目:

  janitor.kernelnewbies.org/

  这是一个很好的起步的地方。它描述了一些相对来说简单的内核中需要清理的和解决的问题。和负责这个项目的开发者一起工作,你会学到如何令你的补丁进入Linux内核树的基本知识,而且可能会为你指明下一步的发展方向,如果你自己尚不明确的话。

  如果你已经有了一段代码想要放到内核树里,但是需要某种形式的帮助,那么kernel-mentors项目就可以帮你的忙了。这是一个邮件列表,可以在下面找到:

  selenic.com/mailman/listinfo/kernel-mentors

  在你对Linux内核代码作任何实际的改动之前,必须要了解相关的代码是如何工作的。为了达到这个目的,没有比直接读它(很多困难的地方都有很好的注释)更好的方法了,甚至可能是在某个特殊工具的帮助下来阅读。很值得推荐的这样一种工具是Linux Cross-Reference项目,它可以把源代码以一种自我引用的、索引的网页形式显示出来。一个非常好的最新的内核代码仓库可以在这里找到: //sosdg.org/~coywolf/lxr

  所有这些事情有时候很难做到。要想完美做到这些要求可能需要几年的时间。这是一个持续的发展过程,需要很多耐心和决心。但是不要放弃,这是可以实现的。很多人已经做到了这一点,每个人都经历过你现在这个阶段。

C中const的使用

基本解释

  const是一个C语言的关键字,它限定一个变量不允许被改变。使用const在一定程度上可以提高程序的健壮性,另外,在观看别人代码的时候,清晰理解const所起的作用,对理解对方的程序也有一些帮助。
虽然这听起来很简单,但实际上,const的使用也是c语言中一个比较微妙的地方,微妙在何处呢?请看下面几个问题。

问题:const变量 & 常量

为什么我象下面的例子一样用一个const变量来初始化数组,ANSI C的编译器会报告一个错误呢?

const int n = 5;
int a[n];

答案与分析:

1)、这个问题讨论的是“常量”与“只读变量”的区别。常量肯定是只读的,例如5, “abc”,等,肯定是只读的,因为程序中根本没有地方存放它的值,当然也就不能够去修改它。而“只读变量”则是在内存中开辟一个地方来存放它的值,只不过这个值由编译器限定不允许被修改。C语言关键字const就是用来限定一个变量不允许被改变的修饰符(Qualifier)。上述代码中变量n被修饰为只读变量,可惜再怎么修饰也不是常量。而ANSI C规定数组定义时维度必须是“常量”,“只读变量”也是不可以的。

2)、注意:在ANSI C中,这种写法是错误的,因为数组的大小应该是个常量,而const int n,n只是一个变量(常量 != 不可变的变量,但在标准C++中,这样定义的是一个常量,这种写法是对的),实际上,根据编译过程及内存分配来看,这种用法本来就应该是合理的,只是ANSI C对数组的规定限制了它。

3)、那么,在ANSI C 语言中用什么来定义常量呢?答案是enum类型和#define宏,这两个都可以用来定义常量。

问题:const变量 & const 限定的内容

  下面的代码编译器会报一个错误,请问,哪一个语句是错误的呢?

typedef char * pStr;
char string[4] = "abc";
const char *p1 = string;
const pStr p2 = string;
p1++;
p2++;

答案与分析:

问题出在p2++上。

1)、const使用的基本形式: const char m;

限定m不可变。

2)、替换1式中的m, const char *pm;

限定*pm不可变,当然pm是可变的,因此问题中p1++是对的。

3)、替换1式char, const newType m;

限定m不可变,问题中的charptr就是一种新类型,因此问题中p2不可变,p2++是错误的。

问题:const变量 & 字符串常量

请问下面的代码有什么问题?

char *p = "i’m hungry!";
p[0]= ‘I’;

答案与分析:

上面的代码可能会造成内存的非法写操作。分析如下, “i’m hungry”实质上是字符串常量,而常量往往被编译器放在只读的内存区,不可写。p初始指向这个只读的内存区,而p[0] = ‘I’则企图去写这个地方,编译器当然不会答应。

问题:const变量 & 字符串常量2

请问char a[3] = "abc" 合法吗?使用它有什么隐患?

答案与分析:

在标准C中这是合法的,但是它的生存环境非常狭小;它定义一个大小为3的数组,初始化为“abc”,,注意,它没有通常的字符串终止符”,因此这个数组只是看起来像C语言中的字符串,实质上却不是,因此所有对字符串进行处理的函数,比如strcpy、printf等,都不能够被使用在这个假字符串上。

问题5:const & 指针

类型声明中const用来修饰一个常量,有如下两种写法,那么,请问,下面分别用const限定不可变的内容是什么?

1)、const在前面

const int nValue; //nValue是const
const char *pContent; //*pContent是const, pContent可变
const (char *) pContent;//pContent是const,*pContent可变
char* const pContent; //pContent是const,*pContent可变
const char* const pContent; //pContent和*pContent都是const

2)、const在后面,与上面的声明对等

int const nValue; // nValue是const
char const * pContent;// *pContent是const, pContent可变
(char *) const pContent;//pContent是const,*pContent可变
char* const pContent;// pContent是const,*pContent可变
char const* const pContent;// pContent和*pContent都是const

答案与分析:

const和指针一起使用是C语言中一个很常见的困惑之处,在实际开发中,特别是在看别人代码的时候,常常会因为这样而不好判断作者的意图,下面讲一下我的判断原则:

沿着*号划一条线,如果const位于*的左侧,则const就是用来修饰指针所指向的变量,即指针指向为常量;如果const位于*的右侧,const就是修饰指针本身,即指针本身是常量。你可以根据这个规则来看上面声明的实际意义,相信定会一目了然。

另外,需要注意:对于const (char *) ; 因为char *是一个整体,相当于一个类型(如 char),因此,这是限定指针是const。

另=======

const用于函数时出现三个位置:
例如:
const returnVal   function (const list_array)const;

形式不过如此
第一个const意思是:返回值是常量
第二个const意思是:函数过程中不能修改list_array的值
第三个const意思是:函数过程不能隐式的修改function参数的值

===

const char*, char const*, char*const的区别问题几乎是C++面试中每次都会有的题目。
事实上这个概念谁都有只是三种声明方式非常相似很容易记混。
Bjarne在他的The C++ Programming Language里面给出过一个助记的方法:
把一个声明从右向左读。
char * const cp; ( * 读成 pointer to )
cp is a const pointer to char
const char * p;
p is a pointer to const char;
char const * p;
同上因为C++里面没有const*的运算符,所以const只能属于前面的类型。

另:下面定义的一个指向字符串的常量指针:
char * const prt1 = stringprt1;
其中,ptr1是一个常量指针。因此,下面赋值是非法的。
ptr1 = stringprt2;
而下面的赋值是合法的:
*ptr1 = "m";
因为指针ptr1所指向的变量是可以更新的,不可更新的是常量指针ptr1所指的方向(别的字符串)。
下面定义了一个指向字符串常量的指针:
const * ptr2 = stringprt1;
其中,ptr2是一个指向字符串常量的指针。ptr2所指向的字符串不能更新的,而ptr2是可以更新的。因此,
*ptr2 = "x";
是非法的,而:
ptr2 = stringptr2;
是合法的。
所以,在使用const修饰指针时,应该注意const的位置。定义一个指向字符串的指针常量和定义一个指向字符串常量的指针时,const修饰符的位置不同,前者const放在*和指针名之间,后者const放在类型说明符前。

在Linux中如何提高文件系统的使用效率

文件系统是整个UNIX系统中与用户关系最密切,用户操作最频繁的部分,随着系统运行时间的延续,文件系统的使用效率也跟着下降,这主要表现为:硬盘空间的减少,垃圾信息的增加,寻址时间的增加等。本文将介绍几种提高文件系统使用效率的方法,和大家共享,欢迎更正和补充。

  首先,我们应该对 UNIX文件系统的结构有一个了解。文件系统是UNIX系统中的文件,目录,以及对这些文件和目录进行管理的数据结构的总称。UNIX文件系统包括引导块、超级块、i节点区、文件存储区、进程对换区等几部分。引导块占用第0号物理块,不属于文件系统管辖,如果系统中有多个文件系统,只有根文件系统才有引导程序放在引导块中,其余文件系统都不使用引导块;超级块占用第1号物理块,是文件系统的控制块,超级块包括:文件系统的大小、空闲块数目、空闲块索引表、空闲i节点数目、空闲i节点索引表、封锁标记等。超级块是系统为文件分配存储空间、回收存储空间的依据。而i节点是对文件进行控制和管理的一种数据结构。一个文件对应一个i节点,每个i节点都有一个唯一的i节点号,i节点由64个字节组成,保存了文件的属性和类型、存放文件内容的物理块地址、最近一次的存取时间、最近一次的修改时间、创建此文件的时间。要注意哦:i节点中并不包括文件名,文件名和文件占用的i节点的i节点号放在目录文件的目录项中。文件存储区是存放文件内容的区域,文件存储区中各数据块的使用情况在超级块中由记录,系统利用超级块中的记录完成对数据块的分配和回收。在文件系统的末尾还可能有进程对换区,这里保留了对换到内存中的进程的映象,它不属于文件系统管辖。相信通过上面的这段文字,兄弟们应该对文件系统有一个大致的了解了。

  为提高UNIX系统的运行效率,文件系统应该保持一定的空闲空间,理论上,空闲空间至少应占文件系统总空间的15%,当空闲空间小于总空间的15%时, UNIX系统操作将明显变慢!因此系统管理员应该了解文件系统空闲时间和i节点的使用情况,保证系统的高效运行。系统管理员可以使用df命令了解空闲空间和i节点的使用情况。使用“df -v 文件系统”命令,可以显示文件系统的使用情况:该文件系统数据块的总数、已使用数据块的数量、空闲数据块的数量、使用的数据快占总数据块的百分比。如果想显示文件系统的i节点总数、空闲i节点的数目、已使用i节点数目以及已使用i节点占总i节点的百分比,可使用-i选项。比如在Solaris下,可执行: df -o i(注意哦:i前没有“-”)。

  当文件系统中的空闲空间减少,以至于使文件系统的效率降低时,可采用以下措施:

  1.删除临时目录下的文件

  临时目录下存放的都是临时文件,临时文件是在程序执行期间根据需要创建的,但没有被及时删除。系统中的临时目录一般有/var/tmp、/tmp和 /usr/tmp。管理员可以手工删除他们,也可以建立一个作业调度,使清理工作自动进行。比如在 /usr/spool/cron/crontabs/root中加入以下代码:

  30 2 * * * find /var/tmp –atime 7 –exec rm{}  ; >/dev/null  
  30 2 * * * find /usr/tmp –atime 7 –exec rm{} ; >/dev/null
  30 2 * * * find /tmp –atime 7 –exec rm{} ; >/dev/null

  2.删除core文件

  当系统中一些进程由于收到一些信号而非正常结束时,系统便建立一个core文件,记录进程当时的一些信息,包括进程状态、数据以及硬件寄存器的值等。在这些core文件没有用的时候,可以考虑删除它们。core文件的删除,应该首先确认要删除的core文件是否还有使用价值,可通过file core命令查看是由哪些进程产生的,在确认对调试程序没用的时候再进行删除,也可以通过下面的命令来删除10天内未存取过的core文件:find / -name core –atime +10 –exec rm –f {}

3.清除系统日志文件

  日志文件包含了有关系统运行情况的信息和用户访问系统的情况,系统在运行过程中,会不断地把新的信息添加到日志文件中,因此日志文件会急剧增多,系统中主要的日志文件有:

  /usr/adm/acct 记帐日志文件;

  /usr/adm/messages 系统信息日志文件;

  /usr/adm/sulog 命令使用日志文件;

  /usr/adm/vold.log 卷管理日志文件;

  /usr/spool/uucp/LOGFILE uucp的记录;

  /usr/spool/uucp/.Log/.Old/* 旧的uucp日志文件;

  /usr/spool/lp/logs/requests 打印请求记录;

  /var/log/syslog 系统日志文件。

  上述有的日志文件对分析系统出现的故障有帮助,因此在清除时要慎重。

  4.压缩不常使用的文件

  对平时不经常使用的文件可以进行压缩,以便节省空间,压缩与解压缩的命令有:compress和uncompress;gzip和gunzip等命令。

  5.减少小文件的使用

  文件系统中的i节点数目是一定的,系统中如果小文件太多,会浪费很多的i节点,这样可能会导致系统中虽然还有磁盘空间,但无法创建新文件的情况。可以把多个小文件合并成一个大文件以节省i节点,也可以删除系统中不再需要的文件。

  6.增加文件系统i节点的数目

  从文件系统的结构看,一个文件占用一个i节点和若干个数据块。当i节点用完时,可考虑在硬盘上重新建立文件系统,指定比较大的i节点数目。这时首先要备份原文件系统,然后卸载该文件系统,使用mkfs重新建立文件系统,指定更大的i节点数目,该命令的格式为:

  #mkfs 设备文件数目:新的i节点数目关于i节点数,管理员应该根据实际情况,综合考虑存储设备的物理大小和文件系统中文件的平均大小等因素来定,个人认为,并不是越多越好。在新建文件系统后,可通过df –o i命令,参考原来文件系统的节点数,来定义新的i节点数。把新的文件系统安装到系统中,最后利用备份恢复文件系统的内容。

来自:天极网

[转]ALSA 0.9.0 HOWTO — 基本PCM音频

最近在写一个LINUX下的播放器,在做音频输出的时候想到了ALSA-LIB,这是LINUX下音频输出的一个库,差不多是事实标准了.所以我找了这么一篇HOWTO作为参考.为了加深理解,我把其中最重要也是大家最常用到的回放(PLAYBACK)部分的文字翻译了一遍.由于本人尚未通过英语4级,译文难免有大把错误,还请众位看官不吝指正.

ALSA 0.9.0 HOWTO — 基本PCM音频

作者: Matthias Nagorni
译者: Kevin Lei
转载请注明出处:http://blog.csdn.net/kevinleicn/

1. 介绍

这个HOWTO计划提供一个简短的介绍,使用ALSA 0.9.0写一个简单的音频应用程序.

Section2解释了PCM音频最基本的函数.如果你删除其中的解释文本,那么最后你会得到一个极小的PCM回放程序. Section3简短地讨论了一些PCM截获函数.

在Section4 你将学习怎么为ALSA音序器写一个简单的客户端.这一章节基于seqdemo.c这个例子,一个可以接收MIDI事件并且表示大多数重要的事件类型的程序.Section5演示ALSA MIDI音序器怎样被用于"路由"MIDI事件,从一个输入端口到一些输出端口.这一段基于midiroute.c这个例子.

Section6 使PCM回放和MIDI输入联结起来,并且解释了迷你合成器–miniFMsynth.c.这一章节引入基于回调的音频回放,就像Paul Davis在linux-audio-dev邮件列表里建议的那样.Section7对基于ALSA音序器队列的MIDI调度提供一个短小的介绍,基于 miniArp.c这个例子.

推荐你也看看doxygen生成的ALSA库函数参考.

编译一个ALSA应用程序:

只要用-lasound参数并且确保你已包含了
#include <alsa/asoundlib.h>

2. 基本PCM音频

为ALSA 0.9.0写一个简单的PCM应用程序我们首先需要一个PCM设备的句柄.然后我们必须指定同时可供回放或截获的PCM流的方向.我们同样必须提供一些关于我们想要使用的设置选项的信息,比如缓冲区大小,采样率,PCM数据格式等.所以,首先我们声明:

/* Handle for the PCM device */
snd_pcm_t *pcm_handle;

/* Playback stream */
snd_pcm_stream_t stream = SND_PCM_STREAM_PLAYBACK;

/* This structure contains information about */
/* the hardware and can be used to specify the */
/* configuration to be used for the PCM stream. */
snd_pcm_hw_params_t *hwparams;

最重要的ALSA PCM设备接口是"plughw"和"hw"接口. 如果你使用"plughw"接口,你不需要很在意声卡硬件.如果你的声卡不支持你指定的采样率或采样格式,你的数据将自动被转换.这同样适用于声道的访问类型和号码.对于"hw"接口,你必须检查你的硬件是否支持你想要使用的设置选项.

/* Name of the PCM device, like plughw:0,0 */
/* The first number is the number of the soundcard, */
/* the second number is the number of the device. */
char *pcm_name;

然后我们初始化变量并分配一个hwparams结构:

/* Init pcm_name. Of course, later you */
/* will make this configurable ;-) */
pcm_name = strdup("plughw:0,0");

/* Allocate the snd_pcm_hw_params_t structure on the stack. */
snd_pcm_hw_params_alloca(&hwparams);

现在我们可以打开PCM设备:

/* Open PCM. The last parameter of this function is the mode. */
/* If this is set to 0, the standard mode is used. Possible */
/* other values are SND_PCM_NONBLOCK and SND_PCM_ASYNC. */
/* If SND_PCM_NONBLOCK is used, read / write access to the */
/* PCM device will return immediately. If SND_PCM_ASYNC is */
/* specified, SIGIO will be emitted whenever a period has */
/* been completely processed by the soundcard. */
if (snd_pcm_open(&pcm_handle, pcm_name, stream, 0) < 0) {
fprintf(stderr, "Error opening PCM device %sn", pcm_name);
return(-1);
}

在我们可以往声卡写PCM数据之前,我们必须指定访问类型,采样格式,采样率,声道号码,周期数目以及周期大小.首先,我们以声卡的全部设置选项空间来初始化hwparams结构.

/* Init hwparams with full configuration space */
if (snd_pcm_hw_params_any(pcm_handle, hwparams) < 0) {
fprintf(stderr, "Can not configure this PCM device.n");
return(-1);
}

关于合适的设置选项的信息,能使用命名了的函数的一个集合来获得.

snd_pcm_hw_params_can_<capability>
snd_pcm_hw_params_is_<property>
snd_pcm_hw_params_get_<parameter>

绝大多数重要的参数的可用性,换句话说,访问类型,缓冲区大小,声道号码,采样格式,采样率,以及周期数目,可以以函数命名的一个集合来检验.

snd_pcm_hw_params_test_<parameter>

如果"hw"接口被使用,这些查询函数尤其重要.设置选项空间能以一个函数命名的集合限制在某一设置选项

snd_pcm_hw_params_set_<parameter>

例如,我们假设声卡可以被设置为16位,Little Endian数据的立体声回放,44100Hz采样率.相应地,我们限制设置选项空间匹配于这个设置选项:

int rate = 44100; /* Sample rate */
int exact_rate; /* Sample rate returned by */
/* snd_pcm_hw_params_set_rate_near */
int dir; /* exact_rate == rate –> dir = 0 */
/* exact_rate < rate –> dir = -1 */
/* exact_rate > rate –> dir = 1 */
int periods = 2; /* Number of periods */
snd_pcm_uframes_t periodsize = 8192; /* Periodsize (bytes) */

访问类型指定了哪一个多声道数据储存在缓冲区的方法.对于交错访问,缓冲区里的每一个帧为声道容纳连续的采样数据.对于16位立体声数据,这意味着缓冲区以字为单位为左右声道交错地容纳采样数据.对于非交错访问,每一个周期为第一个声道容纳所有采样数据接着是第二个声道的采样数据.

/* Set access type. This can be either */
/* SND_PCM_ACCESS_RW_INTERLEAVED or */
/* SND_PCM_ACCESS_RW_NONINTERLEAVED. */
/* There are also access types for MMAPed */
/* access, but this is beyond the scope */
/* of this introduction. */
if (snd_pcm_hw_params_set_access(pcm_handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED) < 0) {
fprintf(stderr, "Error setting access.n");
return(-1);
}

/* Set sample format */
if (snd_pcm_hw_params_set_format(pcm_handle, hwparams, SND_PCM_FORMAT_S16_LE) < 0) {
fprintf(stderr, "Error setting format.n");
return(-1);
}

/* Set sample rate. If the exact rate is not supported */
/* by the hardware, use nearest possible rate. */
exact_rate = rate;
if (snd_pcm_hw_params_set_rate_near(pcm_handle, hwparams, &exact_rate, 0) < 0) {
fprintf(stderr, "Error setting rate.n");
return(-1);
}
if (rate != exact_rate) {
fprintf(stderr, "The rate %d Hz is not supported by your hardware.n
==> Using %d Hz instead.n", rate, exact_rate);
}

/* Set number of channels */
if (snd_pcm_hw_params_set_channels(pcm_handle, hwparams, 2) < 0) {
fprintf(stderr, "Error setting channels.n");
return(-1);
}

/* Set number of periods. Periods used to be called fragments. */
if (snd_pcm_hw_params_set_periods(pcm_handle, hwparams, periods, 0) < 0) {
fprintf(stderr, "Error setting periods.n");
return(-1);
}

缓冲区尺寸的单元依赖于函数.一些时候是字节,一些时候是必须指定的帧的数目.一个帧是对所有声道的采样数据数组.对于16位立体声数据,一个帧的长度是4个字节.

/* Set buffer size (in frames). The resulting latency is given by */
/* latency = periodsize * periods / (rate * bytes_per_frame) */
if (snd_pcm_hw_params_set_buffer_size(pcm_handle, hwparams, (periodsize * periods)>>2) < 0) {
fprintf(stderr, "Error setting buffersize.n");
return(-1);
}

如果你的硬件不支持2的N次方的缓冲区大小,你可以使用snd_pcm_hw_params_set_buffer_size_near函数.这个函数工作起来与snd_pcm_hw_params_set_rate_near相似.现在我们为PCM设备申请由pcm_handle指向的设置选项.这同样也将准备好PCM设备.

/* Apply HW parameter settings to */
/* PCM device and prepare device */
if (snd_pcm_hw_params(pcm_handle, hwparams) < 0) {
fprintf(stderr, "Error setting HW params.n");
return(-1);
}

在PCM设备被设置以后,我们可以开始对其写PCM数据.第一个写访问将开始PCM回放.对于交错写访问,我们使用函数:

/* Write num_frames frames from buffer data to */
/* the PCM device pointed to by pcm_handle. */
/* Returns the number of frames actually written. */
snd_pcm_sframes_t snd_pcm_writei(pcm_handle, data, num_frames);

对于非交错访问,我们将必须使用函数:

/* Write num_frames frames from buffer data to */
/* the PCM device pointed to by pcm_handle. */
/* Returns the number of frames actually written. */
snd_pcm_sframes_t snd_pcm_writen(pcm_handle, data, num_frames);

在PCM回放开始之后,我们必须确保我们的应用程序发送足够的数据到声卡缓冲区.否则,将发生缓冲区欠载.当这样一个缓冲区欠载发生以后,snd_pcm_prepare将被调用.一个简单的立体声锯齿波能以这样的方式生成:

unsigned char *data;
int pcmreturn, l1, l2;
short s1, s2;
int frames;

data = (unsigned char *)malloc(periodsize);
frames = periodsize >> 2;
for(l1 = 0; l1 < 100; l1++) {
for(l2 = 0; l2 < num_frames; l2++) {
s1 = (l2 % 128) * 100 – 5000;
s2 = (l2 % 256) * 100 – 5000;
data[4*l2] = (unsigned char)s1;
data[4*l2+1] = s1 >> 8;
data[4*l2+2] = (unsigned char)s2;
data[4*l2+3] = s2 >> 8;
}
while ((pcmreturn = snd_pcm_writei(pcm_handle, data, frames)) < 0) {
snd_pcm_prepare(pcm_handle);
fprintf(stderr, "<<<<<<<<<<<<<<< Buffer Underrun >>>>>>>>>>>>>>>n");
}
}

如果我们想中止回放,我们既可以使用snd_pcm_drop,也可以使用snd_pcm_drain.第一个函数将立即中止回放并丢弃未回放的帧.后一个函数将在回放完所有帧后中止回放.

/* Stop PCM device and drop pending frames */
snd_pcm_drop(pcm_handle);

/* Stop PCM device after pending frames have been played */
snd_pcm_drain(pcm_handle);

(全文完)

参考链接:

原文
http://www.suse.de/~mana/alsa090_howto.html

Linux音频编程指南
http://www-128.ibm.com/developerworks/cn/linux/l-audio/

A Tutorial on Using the ALSA Audio API
http://www.equalarea.com/paul/alsa-audio.html

The ALSA library API reference
http://www.alsa-project.org/alsa-doc/alsa-lib/

编辑者: kevinleicn (06-08-15 19:05)