Chinaunix首页 | 论坛 | 博客
  • 博客访问: 259197
  • 博文数量: 56
  • 博客积分: 1264
  • 博客等级: 中尉
  • 技术积分: 491
  • 用 户 组: 普通用户
  • 注册时间: 2011-07-19 15:16
文章分类

全部博文(56)

文章存档

2012年(10)

2011年(46)

分类: LINUX

2011-08-07 17:33:39

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) |
给主人留下些什么吧!~~