OSS--跨平台的音频接口简介
来源:IBM DW中国 作者:汤凯 时间:2007-04-22 点击: [收藏] [投稿]
OSS(Open Sound System)是 unix 平台上一个统一的音频接口,
即只要音频处理应用程序按照OSS的API来编写,那么在移植到另外一个平台时,只需要重新编译即可。
OSS(Open Sound
System)是unix平台上一个统一的音频接口。以前,每个Unix厂商都会提供一个自己专有的API,用来处理音频。这就意味着为一种Unix平台
编写的音频处理应用程序,在移植到另外一种Unix平台上时,必须要重写。不仅如此,在一种平台上具备的功能,可能在另外一个平台上无法实现。但是,
OSS出现以后情况就大不一样了,只要音频处理应用程序按照OSS的API来编写,那么在移植到另外一个平台时,只需要重新编译即可。因此,OSS提供了
源代码级的可移植性。
同时,很多的Unix工作站中,只能提供录音与放音的功能。有了OSS后,给这些工作站带来了
MIDI功能,加上音频流、语音识别/生成、计算机电话(CT)、JAVA以及其它的多媒体技术,在Unix工作站中,同样可以享受到同Windows、
Macintosh环境一样的音频世界。另外,OSS还提供了与视频和动画播放同步的音频能力,这对在Unix中实现动画、游戏提供了帮助。
本
文首先解释在音频编程时经常遇到的名词、设备文件的含义,然后分别在录音、播放、Mixer方面对OSS接口的使用方法进行介绍。由于OSS
API十分丰富,因此在本文中只介绍那些最为常用的接口。对于OSS API的一个完整描述,可以参考[1]。
一、基础知识
数
字音频设备(有时也称codec,PCM,DSP,ADC/DAC设备):播放或录制数字化的声音。它的指标主要有:采样速率(电话为8K,DVD为
96K)、channel数目(单声道,立体声)、采样分辨率(8-bit,16-bit)。
mixer(混频器):用来控制多个输入、
输出的音量,也控制输入(microphone,line-in,CD)之间的切换。
synthesizer(合成器):通过一些预先定
义好的波形来合成声音,有时用在游戏中声音效果的产生。
MIDI
接口:MIDI接口是为了连接舞台上的synthesizer、键盘、道具、灯光控制器的一种串行接口。
在Unix系统中,所有的设备都
被统一成文件,通过对文件的访问方式(首先open,然后read/write,同时可以使用ioctl读取/设置参数,最后close)来访问设备。在
OSS中,主要有以下的几种设备文件:
* /dev/mixer:访问声卡中内置的mixer,调整音量大小,选择音源。
* /dev/sndstat:测试声卡,执行cat /dev/sndstat会显示声卡驱动的信息。
* /dev/dsp
、/dev/dspW、/dev/audio:读这个设备就相当于录音,写这个设备就相当于放音。/dev/dsp与/dev/audio之间的区别在于
采样的编码不同,/dev/audio使用μ律编码,/dev/dsp使用8-bit(无符号)线性编码,/dev/dspW使用16-bit(有符号)
线形编码。/dev/audio主要是为了与SunOS兼容,所以尽量不要使用。
* l
/dev/sequencer:访问声卡内置的,或者连接在MIDI接口的synthesizer。
这些设备文件的设备编号见[1]。
二、
音频编程
OSS为音频编程提供三种设备,分别是/dev/dsp,/dev/dspW和/dev/audio,前面已经提到了它们之间的
区别。
用户可以直接使用Unix的命令来放音和录音,命令cat /dev/dsp
>xyz可用来录音,录音的结果放在xyz文件中;命令cat xyz >/dev/dsp播放声音文件xyz。
如果通过编
程的方式来使用这些设备,那么Unix平台通过文件系统提供了统一的访问接口。程序员可以通过文件的操作函数直接控制这些设备,这些操作函数包
括:open、close、read、write、ioctl等。下面我们就分别讨论打开音频设备、放音、录音和参数调整。
1.
打开音频设备
1) 头文件定义
/*
* Standard includes
*/
#include
#include
#include
#include
/*
* Mandatory
variables.
*/
#define BUF_SIZE 4096
int audio_fd;
unsigned
char audio_buffer[BUF_SIZE];
2) 打开设备
if
((audio_fd = open(DEVICE_NAME, open_mode, 0)) == -1) {
/* Open of
device failed */
perror(DEVICE_NAME);
exit(1);
}
open_mode
有三种选择:O_RDONLY,O_WRONLY和O_RDWR,分别表示只读、只写和读写。OSS建议尽量使用只读或只写,只有在全双工的情况下(即录
音和放音同时)才使用读写模式。
2. 录音
int len;
if ((len =
read(audio_fd, audio_buffer, count)) == -1) {
perror("audio read");
exit(1);
}
count
为录音数据的字节个数(建议为2的指数),但不能超过audio_buffer的大小。从读字节的个数可以精确的测量时间,例如8kHZ 16-bit
stereo的速率为8000*2*2=32000bytes/second,这是知道何时停止录音的唯一方法。
3. 放音
放
音实际上和录音很类似,只不过把read改成write即可,相应的audio_buffer中为音频数据,count为数据的长度。
注
意,用户始终要读/写一个完整的采样。例如一个16-bit的立体声模式下,每个采样有4个字节,所以应用程序每次必须读/写4的倍数个字节
sudo
apt-get install tftpd-hpa tftp-hpa
打开/etc/default/tftpd-hpa它的配置文件。
#Defaults
for tftpd-hpa
RUN_DAEMON="no"
OPTIONS="-l -s /var/lib/tftpboot"
修
改设置如下:
#Defaults for tftpd-hpa
RUN_DAEMON="yes"
OPTIONS="-l -s
/home/zdreamx/tftpboot"
其中/home/zdreamx/tftpboot是自己设定的目录,可以根据情况修改。
选
项参考
OPTIONS
-l Run the server in standalone (listen) mode, rather
than run from
inetd. In listen mode, the -t option is ignored, and
the -a
option can be used to specify a specific local address or port
to
listen to.
-a [address][:port]
Specify a specific address and
port to listen to when called
with the -l option. The default is to
listen to the tftp port
specified in /etc/services on all local
addresses.
listen 的 ip address 和 Port
-c Allow new files to be
created. By default, tftpd will only
allow upload of files that
already exist. Files are created
with default permissions allowing
anyone to read or write them,
unless the -p or -U options are
specified.
-s (决定tftp根目录)Change root directory on startup. This
means the remote host
does not need to pass along the directory as
part of the trans-
fer, and may add security. When -s is specified,
exactly one
directory should be specified on the command line. The
use of
this option is recommended for security as well as
compatibility
with some boot ROMs which cannot be easily made to
include a
directory name in its request.
重启OK
/*
此文件中定义了下面所有形如SND_的变量*/
#include
#include
#include
#include
#include
#include
int main()
{
/*
id:读取音频文件描述符;fd:写入的文件描述符。i,j为临时变量*/
int id,fd,i,j;
/*
存储音频数据的缓冲区,可以调整*/
char testbuf[4096];
/* 打开声卡设备,失败则退出*/
if (
( id = open ( "/dev/audio", O_RDWR ) ) < 0 ) {
fprintf (stderr,
" Can't open sound device!\n");
exit ( -1 ) ;
}
/*
打开输出文件,失败则退出*/
if ( ( fd = open ("test.wav",O_RDWR))<0){
fprintf ( stderr, " Can't open output file!\n");
exit (-1 );
}
/*
设置适当的参数,使得声音设备工作正常*/
/* 详细情况请参考Linux关于声卡编程的文档*/
i=0;
ioctl
(id,SNDCTL_DSP_RESET,(char *)&i) ;
ioctl
(id,SNDCTL_DSP_SYNC,(char *)&i);
i=1;
ioctl
(id,SNDCTL_DSP_NONBLOCK,(char *)&i);
i=8000;
ioctl
(id,SNDCTL_DSP_SPEED,(char *)&i);
i=1;
ioctl
(id,SNDCTL_DSP_CHANNELS,(char *)&i);
i=8;
ioctl
(id,SNDCTL_DSP_SETFMT,(char *)&i);
i=3;
ioctl
(id,SNDCTL_DSP_SETTRIGGER,(char *)&i);
i=3;
ioctl
(id,SNDCTL_DSP_SETFRAGMENT,(char *)&i);
i=1;
ioctl
(id,SNDCTL_DSP_PROFILE,(char *)&i);
/* 读取一定数量的音频数据,并将之写到输出文件中去*/
for
( j=0; j<100;){
i=read(id,testbuf,4096);
if(i>0){
write(fd,testbuf,i);
j++;
}
}
/* 关闭输入、输出文件*/
close(fd);
close(id);
exit(0);
}
上
有篇利用libao结合minimad的播放mp3程序,试用发现电流声过大,而且又要调用libao还不如直接对声卡编程。将minimad解码声音用
以下命令写入一个文件中,
$./minimad <丁香花.mp3 1>temp
然后用程序播放即可,命令如下:
$./a.out
temp
在virtual
pc下还有略微的电流声而在另外一台redhat9中速度过快,有些变调,暂时还不知道怎么调节。播放的实质其实关键是要调节几个参数。
#include
#include
#include
#include
#include
#include
int
main(int argc, char *argv[])
{
int id,fd,i;
char buf[4096];
int
rate; /* 采样频率 44.1KHz*/
int format; /* 量化位数 16*/
int channels;
/* 声道数 2*/
int setting; /* 高16位标明分片最大序号,低16位标明缓冲区的尺寸*/
if
(argc != 2)
{
fprintf (stderr, "usage: %s !\n",
argv[0]);
exit ( -1 ) ;
}
if ( ( fd = open
(argv[1],O_RDONLY))<0)
{
fprintf ( stderr, " Can't open
sound file!\n");
exit (-1 );
}
if ( ( id = open (
"/dev/dsp", O_WRONLY) ) < 0 )
{
fprintf (stderr, " Can't
open sound device!\n");
exit ( -1 ) ;
}
/* 此项在Virtual
PC中可减少电流声 */
setting = 0x0002000F;
ioctl(id,
SNDCTL_DSP_SETFRAGMENT, &setting);
rate = 441000;
ioctl(id,
SNDCTL_DSP_SPEED, &rate);
format = AFMT_S16_LE;
ioctl(id,
SNDCTL_DSP_SETFMT, &format);
channels = 2;
ioctl(id,
SNDCTL_DSP_CHANNELS, &channels);
while((i = read(fd,buf,
sizeof(buf)))>0)
{
write(id,buf,i);
}
close(fd);
close(id);
exit(0);
}
采
用libmad+libao实现最简单的mp3播放器
2008-07-25 13:28
一、概述
1.libmad介绍
MAD
(libmad) is a high-quality MPEG audio decoder. It currently supports
MPEG-1
and the MPEG-2 extension to Lower Sampling Frequencies, as well as
the
so-called MPEG 2.5 format. All three audio layers (Layer I, Layer II,
and
Layer III a.k.a. MP3) are fully implemented.
MAD does not yet
support MPEG-2 multichannel audio (although it should be
backward
compatible with such streams) nor does it currently support AAC.
MAD
has the following special features:
- 24-bit PCM output
-
100% fixed-point (integer) computation
- completely new
implementation based on the ISO/IEC standards
- distributed under the
terms of the GNU General Public License (GPL)
2.libao介绍
libao
is a cross platform audio library that allows program to output audio
using a simple API on a wide varity of platform. It currently supports:
. Null output (handy for testing without a sound device)
. Wav
files
. AV files
. OSS (open sound system, used on linux and
freebsd)
. ALSA (advanced linux sound archiecture)
.
PulseAudio (next generation GNOME sound server )
. esd (EsounD or
enlightened sound daemon)
. AIX
. Sun/ NetBSD/OpenBSD
.
IRIX
. NAS (network audio server)
二、源代码说明
libmad附带了
一个示例程序minimad,
但是仅仅是将解码结果输出到屏幕上,而libao则是基于OSS、ALSA等之上的音频高级API,可以将pcm输出,通过多种方式播放出来,因此将两者
结合起来,编写一个可以播放mp3的程序。
编译用的脚本
#!/bin/bash
gcc -o
minimad minimad.c -I . -lmad -lao -lm
~
改进后的源代码清单
#
include
# include
# include
# include
# include
"mad.h"
#i nclude
#i nclude
/*
*
This is perhaps the simplest example use of the MAD high-level API.
*
Standard input is mapped into memory via mmap(), then the high-level
API
* is invoked with three callbacks: input, output, and error. The
output
* callback converts MAD's high-resolution PCM samples to 16
bits, then
* writes them to standard output in little-endian,
stereo-interleaved
* format.
*/
static int decode(unsigned
char const *, unsigned long);
static void myplay();
ao_device
*device;
ao_sample_format format;
int default_driver;
int
main(int argc, char *argv[])
{
struct stat stat;
void
*fdm;
//gn add begin
ao_initialize();
// -- Setup for
default driver --
default_driver = ao_default_driver_id();
format.bits
= 16;
format.channels = 2;
format.rate = 44100;
format.byte_format
= AO_FMT_LITTLE;
// -- Open driver --
device =
ao_open_live(default_driver, &format, NULL );
if (device == NULL)
{
fprintf(stderr, "Error opening device.\n");
exit (1);
}
//gn
add end
if (argc != 1)
return 1;
if
(fstat(STDIN_FILENO, &stat) == -1 ||
stat.st_size == 0)
return
2;
fdm = mmap(0, stat.st_size, PROT_READ, MAP_SHARED,
STDIN_FILENO, 0);
if (fdm == MAP_FAILED)
return 3;
decode(fdm,
stat.st_size);
if (munmap(fdm, stat.st_size) == -1)
return 4;
//gnadd
begin
//myplay();
ao_close(device);
ao_shutdown();
//gnadd
end
return 0;
}
/*
* This is a private message
structure. A generic pointer to this structure
* is passed to each of
the callback functions. Put here any data you need
* to access from
within the callbacks.
*/
struct buffer {
unsigned char
const *start;
unsigned long length;
};
/*
* This is the
input callback. The purpose of this callback is to (re)fill
* the
stream buffer which is to be decoded. In this example, an entire file
*
has been mapped into memory, so we just call mad_stream_buffer() with
the
* address and length of the mapping. When this callback is called
a second
* time, we are finished decoding.
*/
static
enum
mad_flow input(void *data,
struct mad_stream *stream)
{
struct
buffer *buffer = data;
if (!buffer->length)
return
MAD_FLOW_STOP;
mad_stream_buffer(stream, buffer->start,
buffer->length);
buffer->length = 0;
return
MAD_FLOW_CONTINUE;
}
/*
* The following utility routine
performs simple rounding, clipping, and
* scaling of MAD's
high-resolution samples down to 16 bits. It does not
* perform any
dithering or noise shaping, which would be recommended to
* obtain
any exceptional audio quality. It is therefore not recommended to
*
use this routine if high-quality output is desired.
*/
static
inline
signed int scale(mad_fixed_t sample)
{
/* round */
sample
+= (1L << (MAD_F_FRACBITS - 16));
/* clip */
if (sample
>= MAD_F_ONE)
sample = MAD_F_ONE - 1;
else if (sample <
-MAD_F_ONE)
sample = -MAD_F_ONE;
/* quantize */
return
sample >> (MAD_F_FRACBITS + 1 - 16);
}
/*
* This is
the output callback function. It is called after each frame of
* MPEG
audio data has been completely decoded. The purpose of this callback
*
is to output (or play) the decoded PCM audio.
*/
static void
myplay()
{
char *buffer;
int buf_size;
int sample;
float
freq = 440.0;
int i;
buf_size = format.bits/8 *
format.channels * format.rate;
buffer = calloc(buf_size,
sizeof(char));
for
(i = 0; i < format.rate; i++) {
sample = (int)(0.75 * 32768.0 *
sin(2
* M_PI * freq * ((float) i/format.rate)));
/* Put the same stuff
in left and right channel */
buffer[4*i] = buffer[4*i+2] = sample
& 0xff;
buffer[4*i+1] = buffer[4*i+3] = (sample >> 8) &
0xff;
}
ao_play(device, buffer, buf_size);
}
struct
audio_dither {
mad_fixed_t error[3];
mad_fixed_t random;
};
/*
*
NAME: prng()
* DESCRIPTION: 32-bit pseudo-random number generator
*/
static
inline
unsigned long prng(unsigned long state)
{
return (state
* 0x0019660dL + 0x3c6ef35fL) & 0xffffffffL;
}
/*
*
NAME: audio_linear_dither()
* DESCRIPTION: generic linear sample
quantize and dither routine
*/
inline
signed long
audio_linear_dither(unsigned int bits, mad_fixed_t sample,
struct
audio_dither *dither)
{
unsigned int scalebits;
mad_fixed_t
output, mask, random;
enum {
MIN = -MAD_F_ONE,
MAX =
MAD_F_ONE - 1
};
/* noise shape */
sample +=
dither->error[0] - dither->error[1] + dither->error[2];
dither->error[2]
= dither->error[1];
dither->error[1] = dither->error[0] /
2;
/* bias */
output = sample + (1L << (MAD_F_FRACBITS +
1 - bits - 1));
scalebits = MAD_F_FRACBITS + 1 - bits;
mask =
(1L << scalebits) - 1;
/* dither */
random =
prng(dither->random);
output += (random & mask) -
(dither->random & mask);
dither->random = random;
/*
clip */
if (output > MAX) {
output = MAX;
if (sample
> MAX)
sample = MAX;
}
else if (output < MIN) {
output
= MIN;
if (sample < MIN)
sample = MIN;
}
/*
quantize */
output &= ~mask;
/* error feedback */
dither->error[0]
= sample - output;
/* scale */
return output >>
scalebits;
}
enum mad_flow output(void *data,
struct
mad_header const *header,
struct mad_pcm *pcm)
{
register int
nsamples = pcm->length;
mad_fixed_t const *left_ch =
pcm->samples[0], *right_ch = pcm->samples[1];
static
unsigned char stream[1152*4]; /* 1152 because that's what mad has as a
max; *4 because
there are 4 distinct bytes per sample (in 2 channel
case) */
static unsigned int rate = 0;
static int channels = 0;
static
struct audio_dither dither;
register char * ptr = stream;
register
signed int sample;
register mad_fixed_t tempsample;
/* We
need to know information about the file before we can open the
playdevice
in some cases. So, we do it here. */
if
(pcm->channels == 2)
{
while (nsamples--)
{
signed int
sample;
sample = scale(*left_ch++);
// sample = (signed int)
audio_linear_dither(16, tempsample, &dither);
stream[(pcm->length-nsamples)*4
] = ((sample >> 0) & 0xff);
stream[(pcm->length-nsamples)*4
+1] = ((sample >> 8) & 0xff);
sample =
scale(*right_ch++);
stream[(pcm->length-nsamples)*4+2 ] = ((sample
>> 0) & 0xff);
stream[(pcm->length-nsamples)*4 +3] =
((sample >> 8) & 0xff);
}
ao_play(device, stream,
pcm->length * 4);
}
else /* Just straight mono output */
{
while
(nsamples--)
{
signed int sample;
sample = scale(*left_ch++);
stream[(pcm->length-nsamples)*2
] = ((sample >> 0) & 0xff);
stream[(pcm->length-nsamples)*2
+1] = ((sample >> 8) & 0xff);
}
ao_play(device, stream,
pcm->length * 2);
}
return MAD_FLOW_CONTINUE;
}
/*
*
This is the error callback function. It is called whenever a decoding
*
error occurs. The error is indicated by stream->error; the list of
*
possible MAD_ERROR_* errors can be found in the mad.h (or
*
libmad/stream.h) header file.
*/
static
enum mad_flow
error(void *data,
struct mad_stream *stream,
struct mad_frame
*frame)
{
struct buffer *buffer = data;
fprintf(stderr,
"decoding error 0x%04x (%s) at byte offset %u\n",
stream->error,
mad_stream_errorstr(stream),
stream->this_frame -
buffer->start);
/* return MAD_FLOW_BREAK here to stop decoding
(and propagate an error) */
return MAD_FLOW_CONTINUE;
}
/*
*
This is the function called by main() above to perform all the
*
decoding. It instantiates a decoder object and configures it with the
*
input, output, and error callback functions above. A single call to
*
mad_decoder_run() continues until a callback function returns
*
MAD_FLOW_STOP (to stop decoding) or MAD_FLOW_BREAK (to stop decoding and
*
signal an error).
*/
static
int decode(unsigned char const
*start, unsigned long length)
{
struct buffer buffer;
struct
mad_decoder decoder;
int result;
/* initialize our private
message structure */
buffer.start = start;
buffer.length =
length;
/* configure input, output, and error functions */
mad_decoder_init(&decoder,
&buffer,
input, 0 /* header */, 0 /* filter */, output,
error,
0 /* message */);
/* start decoding */
result =
mad_decoder_run(&decoder, MAD_DECODER_MODE_SYNC);
/* release
the decoder */
mad_decoder_finish(&decoder);
return
result;
}
/* 此文件中定义了下面所有形如SND_的变量*/
#include
#include
#include
#include
#include
#include
int main()
{
/*
id:读取音频文件描述符;fd:写入的文件描述符。i,j为临时变量*/
int id,fd,i,j;
/*
存储音频数据的缓冲区,可以调整*/
char testbuf[4096];
/* 打开声卡设备,失败则退出*/
if ( (
id = open ( "/dev/audio", O_RDWR ) ) < 0 ) {
fprintf (stderr, "
Can't open sound device!\n");
exit ( -1 ) ;
}
/* 打开输出文件,失败则退出*/
if
( ( fd = open ("test.wav",O_RDWR))<0){
fprintf ( stderr, " Can't
open output file!\n");
exit (-1 );
}
/* 设置适当的参数,使得声音设备工作正常*/
/*
详细情况请参考Linux关于声卡编程的文档*/
i=0;
ioctl (id,SNDCTL_DSP_RESET,(char
*)&i) ;
ioctl (id,SNDCTL_DSP_SYNC,(char *)&i);
i=1;
ioctl
(id,SNDCTL_DSP_NONBLOCK,(char *)&i);
i=8000;
ioctl
(id,SNDCTL_DSP_SPEED,(char *)&i);
i=1;
ioctl
(id,SNDCTL_DSP_CHANNELS,(char *)&i);
i=8;
ioctl
(id,SNDCTL_DSP_SETFMT,(char *)&i);
i=3;
ioctl
(id,SNDCTL_DSP_SETTRIGGER,(char *)&i);
i=3;
ioctl
(id,SNDCTL_DSP_SETFRAGMENT,(char *)&i);
i=1;
ioctl
(id,SNDCTL_DSP_PROFILE,(char *)&i);
/* 读取一定数量的音频数据,并将之写到输出文件中去*/
for
( j=0; j<100;){
i=read(id,testbuf,4096);
if(i>0){
write(fd,testbuf,i);
j++;
}
}
/*
关闭输入、输出文件*/
close(fd);
close(id);
exit(0);
}
Linux
声音设备编程实例
由于这些文件不是普通的文件,所以我们不能用ANSI
C(标准C)的fopen、fclose等来操作文件,而应该使用系统文件I/O处理函数(open、read、write、lseek和close)来
处理这些设备文件。ioctl()或许是Linux下最庞杂的函数,它可以控制各种文件的属性,在Linux声音设备编程中,最重要的就是使用此函数正确
设置必要的参数。
下面我们举两个实际的例子来说明如何实现Linux下的声音编程。由于此类编程涉及到系统设备的读写,所以,很多时
候需要你有root权限,如果你将下面的例子编译后不能正确执行,那么,首先请你检查是否是因为没有操纵某个设备的权限。
1.
对内部扬声器编程
内部扬声器是控制台的一部分,所以它对应的设备文件为/dev/console。变量KIOCSOUND在头文件
/usr /include /linux /kd.h中声明,ioctl函数使用它可以来控制扬声器的发声,使用规则为:
ioctl
( fd, KIOCSOUND, (int) tone);
fd为文件设备号,tone
是音频值。当tone为0时,终止发声。必须一提的是它所理解的音频和我们平常以为的音频是不同的,由于计算机主板定时器的时钟频率为1.19MHZ,所
以要进行正确的发声,必须进行如下的转换:
扬声器音频值=1190000/我们期望的音频值。
扬声器发声时间的长短
我们通过函数usleep(unsigned long usec)来控制。它是在头文件/usr /include
/unistd.h中定义的,让程序睡眠usec微秒。下面即是让扬声器按指定的长度和音频发声的程序的完整清单:
#include
< fcntl.h >
#include < stdio.h >
#include <
stdlib.h >
#include < string.h >
#include < unistd.h
>
#include < sys/ioctl.h >
#include < sys/types.h >
#include
< linux/kd.h >
/* 设定默认值 */
#define DEFAULT_FREQ 440 /*
设定一个合适的频率 */
#define DEFAULT_LENGTH 200 /* 200 微秒,发声的长度是以微秒为单位的*/
#define
DEFAULT_REPS 1 /* 默认不重复发声 */
#define DEFAULT_DELAY 100 /* 同样以微秒为单位*/
/*
定义一个结构,存储所需的数据*/
typedef struct {
int freq; /* 我们期望输出的频率,单位为Hz */
int
length; /* 发声长度,以微秒为单位*/
int reps; /* 重复的次数*/
int delay; /*
两次发声间隔,以微秒为单位*/
} beep_parms_t;
/* 打印帮助信息并退出*/
void
usage_bail ( const char *executable_name ) {
printf ( "Usage: \n \t%s
[-f frequency] [-l length] [-r reps] [-d delay] \n ",
executable_name
);
exit(1);
}
/ * 分析运行参数,各项意义如下:
* "-f <以HZ为单位的频率值
>"
* "-l <以毫秒为单位的发声时长 >"
* "-r <重复次数 >"
* "-d
<以毫秒为单位的间歇时长 >"
*/
void parse_command_line(char **argv,
beep_parms_t *result) {
char *arg0 = *(argv++);
while ( *argv ) {
if
( !strcmp( *argv,"-f" )) { /*频率*/
int freq = atoi ( *( ++argv ) );
if
( ( freq <= 0 ) | | ( freq > 10000 ) ) {
fprintf ( stderr,
"Bad parameter: frequency must be from 1..10000\n" );
exit (1) ;
}
else {
result->freq = freq;
argv++;
}
} else if ( !
strcmp ( *argv, "-l" ) ) { /*时长*/
int length = atoi ( *(++argv ) );
if
(length < 0) {
fprintf(stderr, "Bad parameter: length must be
>= 0\n");
exit(1);
} else {
result->length = length;
argv++;
}
}
else if (!strcmp(*argv, "-r")) { /*重复次数*/
int reps =
atoi(*(++argv));
if (reps < 0) {
fprintf(stderr, "Bad
parameter: reps must be >= 0
");
exit(1);
} else {
result->reps
= reps;
argv++;
}
} else if (!strcmp(*argv, "-d")) { /* 延时 */
int
delay = atoi(*(++argv));
if (delay < 0) {
fprintf(stderr, "Bad
parameter: delay must be >= 0
");
exit(1);
} else {
result->delay
= delay;
argv++;
}
} else {
fprintf(stderr, "Bad parameter:
%s
", *argv);
usage_bail(arg0);
}
}
}
int
main(int argc, char **argv) {
int console_fd;
int i; /* 循环计数器 */
/*
设发声参数为默认值*/
beep_parms_t parms = {DEFAULT_FREQ, DEFAULT_LENGTH,
DEFAULT_REPS,
DEFAULT_DELAY};
/* 分析参数,可能的话更新发声参数*/
parse_command_line(argv,
&parms);
/* 打开控制台,失败则结束程序*/
if ( ( console_fd = open (
"/dev/console", O_WRONLY ) ) == -1 ) {
fprintf(stderr, "Failed to
open console.\n");
perror("open");
exit(1);
}
/*
真正开始让扬声器发声*/
for (i = 0; i < parms.reps; i++) {
/*
数字1190000从何而来,不得而知*/
int magical_fairy_number = 1190000/parms.freq;
ioctl(console_fd,
KIOCSOUND, magical_fairy_number); /* 开始发声 */
usleep(1000*parms.length);
/*等待... */
ioctl(console_fd, KIOCSOUND, 0); /* 停止发声*/
usleep(1000*parms.delay);
/* 等待... */
} /* 重复播放*/
return EXIT_SUCCESS;
}
将上面的例子
稍作扩展,用户即可以让扬声器唱歌。只要找到五线谱或简谱的音阶、音长、节拍和频率、发声时长、间隔的对应关系就可以了。我现在还记得以前在
DOS下编写出《世上只有妈妈好》时的兴奋。最后,说一些提外话,这其实是一个很简单的程序,但是我们却用了很长的篇幅,希望读者从以上的代码里能体会到
写好的程序的一些方法,或许最重要的是添加注释吧。一个程序的注释永远不会嫌多,即便你写的时候觉得它根本是多余,但相信我,相信曾这样告诉我们的许多优
秀的程序员:养成写很多注释的习惯.
阅读(2095) | 评论(0) | 转发(0) |