内容


工作中用声音控制计算机上命令的执行

使用开源软件和带麦克风的膝上型电脑来侦听特定的音调序列并执行命令

Comments

多年来,计算机用户都能够运行处理器密集型的语音识别应用程序来基于声音处理执行命令,但这常常需要进行大量的配置。虽然处理能力及算法中的最新进展使独立于用户的语音识别减少了错误率,但是有些时候仍然需要使用基于简单音调模式的识别系统。

最近发布的 sndpeek 程序可用来进行复杂的处理,借助它,我们只需运行一个简单的 Perl 程序来生成音调代码。另外,本文还提供 Perl 脚本以允许用户定制音调的输入及检测的环境。

要求

硬件

需要一个能够处理声音输入的系统,虽然能够生成离散音调事件的任何声源都可以满足要求,但最好还是选用功能齐全的麦克风。例如,通过麦克风向计算机吹口哨就为计算机添加了除了键盘和鼠标之外的第三种用户输入途径,但是如果配置正确,则通过输入插孔回放 MP3 播放器的输出也可以获得同样的效果。本文中的代码是在配有 900 MHz 处理器和 1GB RAM 的 IBM® ThinkPad® T42p 上开发及测试的。配置不如它的系统应当也能够轻松地使用本文中提供的代码,因为 sndpeek 是主要的资源消耗者,而它是一个极为高效的程序。

软件

需要一个有效的声音处理软件来访问麦克风硬件。虽然声音配置和故障排除超出了本文的范围,但是在 Vector Linux Live CD 上测试这段代码可能会很有用,Vector Linux Live CD 具有运行许多不同的声音硬件所必需的大多数驱动程序和组件。至少需要安装 sndpeek 程序的一半功能,因为 3-D 显示部分的代码是可选项,可以不安装。

参考资料 中,有一个指向 sndpeek Web 页面的链接。下载代码,然后在 src/sndpeek/sndpeek.cpp 文件中找到以下部分:

清单 1. 修改 sndpeek
    fprintf( stdout, "%.2f %.2f %.2f %.2f %.2f %.2f %.2f %.2f %.2f %.2f %.2f %.2f %.2f ", 
         mfcc(0), mfcc(1), mfcc(2), mfcc(3), mfcc(4), mfcc(5), mfcc(6),
         mfcc(7), mfcc(8), mfcc(9), mfcc(10), mfcc(11), mfcc(12) );
    fprintf( stdout, "\n" );

通过直接输出,确保程序的输出写在每个打印窗口的末尾。更改以上部分使之如下所示:

清单 2. 第二次修改 sndpeek
    fprintf( stdout, "%.2f %.2f %.2f %.2f %.2f %.2f %.2f %.2f %.2f %.2f %.2f %.2f %.2f ", 
         mfcc(0), mfcc(1), mfcc(2), mfcc(3), mfcc(4), mfcc(5), mfcc(6),
         mfcc(7), mfcc(8), mfcc(9), mfcc(10), mfcc(11), mfcc(12) );
    fprintf( stdout, "\n" );
    fflush(stdout);

现在运行典型的 ./configure; make; make install 命令在 Linux 上构建并安装 sndpeek。对于 Windows,确保正确地构建程序。在 Windows 上构建应用程序有许多选择,这不在本文讨论范围之内。如果需要查看 cmdWhistle 怎样工作,建议使用 Vector Linux Live CD。

接下来是为命令行窗口控制安装 xwit 应用程序。查阅 参考资料 获得 xwit 下载链接,只需安装而无需对源代码做任何更改。现在您就可以开始创建一些简单的音调了。

示例设置和配置

创建简单的音调序列

下载 源代码并找到 cmdWhistle.pl 脚本。这是 Perl 主程序,允许您创建音调序列,侦听特定音调序列及 run 命令。本文将先向您介绍 cmdWhistle.pl 程序的用户空间使用和配置,然后再说明各个功能。

用以下命令运行 cmdWhistle.pl 程序:sndpeek --print --nodisplay | perl cmdWhistle.pl -c。此命令将启动侦听麦克风的 sndpeek 程序并将输出打印到 Perl 程序。此程序运行后,请生成一些简单的口哨声 —— 稳定的单音调或具有 1-3-2 停顿的音调。注意,您必须在噪声相对较少的环境中运行此程序,因此请插入耳机并确保 CD 驱动器盘片停止旋转。如果膝上型电脑的电池着火,请在运行此程序之前先拔掉感烟探测器。

用不同的速度和音调做试验来感受 cmdWhistle 程序捕捉事件的能力。体验程序的音调检测过程的微妙对于创建重复的复杂音调序列来说很重要。第一个音调序列应当很简单:两声低音哔哔,间隔为 0.5 秒。运行 sndpeek --print --nodisplay | perl cmdWhistle.pl -c,当看到 “enter a tone sequence” 时,吹两声口哨,音调之间有 0.5 秒延迟。自动超时将在 4 秒钟(可配置)后发生,并且将打印出类似以下示例的音调序列:25.00 25.00 _#_ 0 500000 _#_ <command here> _#_ <comment here>

命令设置和音调序列检测

让我们来仔细研究该行:音调值、分隔符、时间值、分隔符、命令区域、分隔符和注释区域。下一步是要将这一行复制到 cmdWhistle.pl 程序的默认配置文件中:{$HOME}/.toneFile,也可能是 /home/<username>/.toneFile。用以上音调序列行创建 ~/.toneFile 之后,可以修改该行来运行程序。将命令区域文本更改为 /bin/echo "two low" 并将注释区域修改为描述性内容,如 25.00 25.00 _#_ 0 500000 _#_ /usr/bin/echo "two low" _#_ two low tones

现在您已经修改了配置文件来给出通知,用命令 sndpeek --print --nodisplay | perl cmdWhistle.pl 以守护进程模式运行 cmdWhistle.pl 脚本。程序将在背景中静静地侦听来自 ~/.toneFile 列表的任何事件。尝试以同样的时间间隔并以相同的音调吹两声低音调口哨,您将看到文本 “two low” 打印到屏幕上。如果需要详细查看运行中的 cmdWhistle.pl 脚本,请用命令 sndpeek --print --nodisplay | perl cmdWhistle.pl -v 以守护进程模式运行该脚本。如果系统支持图形显示,请删除 --nodisplay 选项以获得声音输入的优秀 3-D 视觉效果。

使用 xwit 进行窗口管理的示例设置

创建升高、降低和图标化序列

用来模仿键盘快捷键或鼠标移动的音调输入可通过很多方式实现。以下示例专门提供了窗口管理函数以使用户的手保持在原位,但同时窗口又能被放置到用户需要的位置。有关这种扩展的输入方法的更多用途,请参阅 “附加示例” 部分。

sndpeek --print --nodisplay | perl cmdWhistle.pl -c 命令以 “create” 模式运行 cmdWhistle.pl 程序。需要为快速窗口管理函数创建一些可以轻松重建的简单音调。建议对于 “降低” 使用低音调,对于 “升高” 使用高音调,对于 “图标化” 使用中音调。确保选用您能一直准确执行的东西。虽然可以修改控制精度的参数,但是修改时需要输入音调序列(包括音调和时间),而且要匹配有问题的音调或精确计时可能会很困难。广泛用于定调的三种方法综合了命令的灵活性和简单性,非常适合像我们这样唱不好卡拉 OK 的人。下面是带有这三个命令的样例 ~/.toneFile:

清单 3. 带有三个命令的样例 ~/.toneFile
20.00 _#_ 0 _#_ /usr/bin/xwit -pop -names rxvt _#_ raise rxvt windows
#
#
40.00 _#_ 0 _#_ /usr/bin/xwit -pop 
	-names nathan _#_ raise xterms starting with nathan@localhost
#
#
25.00 25.00 _#_ 0 500000 _#_ /usr/bin/xwit -iconify 
	-names nathan _#_ iconify nathan@localhost~

用 echo 替换 xwit 命令并在实际使用之前先练习。

可从 shell 脚本访问的 xwit - X 函数

虽然针对 X Window System 的命令行窗口控制有很多种方法,但是 xwit 是其中一种较为简单且可移植的方法,适合于许多窗口管理程序。下载并安装 xwit,然后执行 xwit -iconify 命令以确保程序运行正常(这将使当前窗口最小化)。虽然 xwit 没有 “降低” 功能,但是我们可以通过升高其他窗口来实现“降低”的目的。对于本示例,我们使用单个高音 (40.00) 来升高标题开头为 “nathan” 的窗口。设置 xterms 的标题是一种适用于典型编程类型任务的识别模式。使用单个低音 (20.00) 升高其他窗口(如果开头为 rxvt)。之间有半秒钟延迟的两个中音 (25.00) 则会使所有标题开头为 “nathan” 的窗口图标化。有关具体的示例,请参阅 参考资料

此设置的主要优点之一是能够持续在一个窗口中键入的同时还可以看见另一个窗口。这对构建子例程架构的同时还要在显著位置打开文档参考以获得 API 信息来说很有用。事实上,计算可以比键入更快 —— 因为可以同时键入和管理窗口环境。

升高和降低窗口的 Windows shell 脚本

为了在 Microsoft 操作系统中控制窗口,升高窗口的简单方法之一是使用 WshShell.AppActivate 命令。例如,如果我们需要升高 “gvim” 应用程序,则需要用以下代码创建一个名为 “gvimActivate.vbs” 的文件:

Set WshShell = WScript.CreateObject("WScript.Shell")
WshShell.AppActivate "gvim";

以上文件就绪后,我们要做的就是执行该文件,之后 “gvim” 窗口将被突出显示并被放在显著位置。如果是在 Windows 上运行,请将 ~/.toneFile 中的简单高音命令更改为 40.00 _#_ 0 _#_ gvimActivate.vbs _#_ raise gvim window

附加示例

sndpeek 程序和 cmdWhistle.pl 提供了一种可以以独特方式使用的附加用户输入机制。比如,为屏幕保护程序设置解锁代码并在接近电脑的时候吹一下口哨 —— 再无需烦人的密码键入操作。又如,吹一下口哨即可让计算机为您检查电子邮件,或为您检查移动电话的独特铃声,并在铃声响起时向您发送一封电子邮件。

cmdWhistle.pl 代码

历史和策略

快速傅立叶变换、滑动窗口和某些 Linux 音频编程可以为您提供符合您所选语言的音调识别能力。Ge Wang、Perry R. Cook 和 Ananya Misra 编写的 sndpeek 应用程序提供了一种可移植的、快速的 UNIX® 式的方法来采集 cmdWhistle.pl 检测音调事件所需的信息。简单的命令 sndpeek --print 将显示对当前声音源的实时文本分析,并提供优秀的 3D 视觉效果。sndpeek 打印出的文本分析中的第四个条目是使用 sndtools 中的 “Marsyas” 组件进行 sndpeeks 处理的 Rolloff 函数的输出。下面是来自 Rolloff.cpp 源代码的描述:

清单 4. 来自 Rolloff.cpp 源代码的描述
Compute Rolloff (a percentile point) of the input fvec. 
For example if perc_ is 0.90 then Rolloff would be the 
index where the sum of values before that index are 
equal to 90% of the total energy.

使用此值作为我们的基准 “音调”,cmdWhistle.pl 脚本将检测音调之间的各种时间间隔。

参数配置

让我们从位于 cmdWhistle.pl 顶部的计时和传感器临界参数开始:

清单 5. cmdWhistle.pl 计时和传感器临界参数
$|=1; #for non buffered standard output, useful for other programs to read 
require 'sys/syscall.ph'; # for subsecond timing

my $option = $ARGV[0] || ""; # simple option handling

my $MAX_TIMEOUT_LENGTH = 4; # maximum length in seconds of tone pattern
my $LISTEN_TIMEOUT = 2; # timeout value in seconds between tone
my $MAX_TIME_DEV = 	100000; # maximum acceptable deviation between recorded
	# pattern values and current time values
my $MAX_TONE_DEV = 2; # maximum acceptable deviation between recorded
	# pattern values and current tone values
my $MAX_AVG_TONE = 5; # maximum number of samples to be averaged

以上变量及其注释相对来说都很简单。稍后将更详细地介绍其用法和配置选项。下面是其余的全局变量及其描述。

清单 6. 其余的全局变量和描述
my @queTones = ();	# running queue of recent tones detected 
my $prevTone = 0; 	# the last solid tone detected, used for disambiguation
my $prevInterval = 0; 	# previous interval of time
my @baseTones = (); # the currently entered tone sequence
my @baseTimes = (); # the currently entered temporal values
my %toneHash = (); 	# tones, times and commands read from ~/.toneFile
my $toneCount = 0; 	# the current count of tones entered
my $startTime = ""; 	# start of a temporal block
my $currTime = ""; 	# current time in the time out loop
my $toneAge = 0; 		# for tone count synchronization
my $timeOut = 0;		# to reset the timer window

子例程

我们从 getEpochSecondsgetEpochMicroSeconds 子例程开始,它们用于提供关于时间音调模式状态的详细且精确的信息。

清单 7. getEpochSeconds 和 getEpochMicroSeconds 子例程
sub getEpochMicroSeconds {

 my $TIMEVAL_T = "LL";   # LL for microseconds
 my $timeVal = pack($TIMEVAL_T, ());

 syscall(&SYS_gettimeofday, $timeVal, 0) != -1 or die "micro seconds: $!";
 my @vals = unpack( $TIMEVAL_T, $timeVal );
 $timeVal = $vals[0] . $vals[1];
 $timeVal = substr( $timeVal, 6);

 my $padLen = 10 - length($timeVal);
 $timeVal = $timeVal . "0" x $padLen;

 return($timeVal);
}#getEpochMicroSeconds

sub getEpochSeconds {
 my $TIMEVAL_T = "LL";   # LL for microseconds
 my $start = pack($TIMEVAL_T, ());
 syscall(&SYS_gettimeofday, $start, 0) != -1 or die "seconds: $!";
 return( (unpack($TIMEVAL_T, $start))[0] );
}#getEpochSeconds

接下来是 readTones 子例程,它首先将采集 Rolloff 数据值作为 sndpeek 的输出。您可以从随后的注释部分中看到,代码首先将开发五种样例音调并一起比较以形成计算偏差的依据。如果音调数组队列取决于容量,就可以计算每个音调的偏差。如果对队列中所有单音调的比较结果超过音调偏差的最大值,则指定当前处理不生成可辨别的音调。

如果队列中所有音调的偏差小于可接受的阈值,则执行模糊检测的时间层。如果以缓慢升高的音调吹口哨,则您个人的音调与生成可识别的音调事件的可接受阈值之间会有极小的偏差。但这种不断的检测可能会导致出现问题。当偏差值超过 MAX_TONE_DEV 变量时,upDevdownDev 变量以及比较逻可用于采集连续音调变化。如果最新音调队列检查和连续音调检查都通过,就记录音调事件及其时间以供稍后打印或比较。

仔细修改这些变量将帮助调整程序来识别特定的音调样式和时间偏差。频率分析的整体刷新率由 sndpeek 程序的输出决定。所有其他参数都可以被配置为检测间隔更长的音调事件 —— 在一个时间阈值内发生多个并行音调事件,或者音调事件之间的时间间隔不同。

清单 8. 检测间隔更大的音调事件
sub readTones
{ 
 # read the Rolloff output only, 
 my(undef, undef, undef, $currentTone ) = split " ", $_[0];

 if( @queTones == $MAX_AVG_TONE )
 { 
  my $localDiff = 0;
  # check for a solid tone by comparing against the last five tones
  # perform simple time related tonal smoothing, so if the tone
  # wavers just a bit it's still counted as one tone
  for my $chkHistTone ( @queTones )
  { 
   if( abs($currentTone - $chkHistTone) > $MAX_TONE_DEV )
   { 
    $localDiff =1;
    $prevTone = 0;
   }#if deviation less than threshold
  }#for each tone

  if( $localDiff == 0 )
  { 
   # make sure the current tone is different than the previous one, this is to 
   # ensure that long duration tones are not acquired as multiple tone events
   # this step up or down will allow you to whistle continuously and pick up the 
   # steps as discrete tone events
   my $upDev  = $currentTone + $MAX_TONE_DEV;
   my $downDev = $currentTone - $MAX_TONE_DEV;
   if( $prevTone > $upDev || $prevTone < $downDev )
   { 
    my $currVal = getEpochMicroSeconds();
    my $diffInterval = abs($prevInterval - $currVal);
    if( $option ){
     print "Tone: $currentTone ## last: [$currVal] curr: [$prevInterval] ";
     print "difference is: $diffInterval\n";
    }
    if( $toneCount == 0 ){ $diffInterval = 0 }
    push @baseTones, $currentTone;
    push @baseTimes, $diffInterval;
    $toneCount++;
    $prevInterval = $currVal;
   }#if deviation in tone

   # now set the previous tone to the current tone so a continuous tone
   # is not acquired as multiple tone events
   $prevTone = $currentTone;

  }#if a solid tone has been found

  # if enough tones to create an useful queue have been added, pop one off
  shift @queTones;

 }#if enough tones to create a useful queue

 # always push more tones on the avg
 push @queTones, $currentTone;

}#readTones

创建了音调模式后,它被放在 ~/.toneFile 文件中并由以下子例程读取。

清单 9. 创建音调模式
# readToneFile reads tone sequences and commands from ~/.toneFile
# format is: tones _#_ times _#_ command _#_ comments
sub readToneFile 
{
 #give it a full path to .toneFile if on windows
 open(TONEFILE,"$ENV{HOME}/.toneFile") or die "no tone file: $!";

  while(<TONEFILE>){

   if( !/^#/ ){

    my @arrLine = split "_#_";
    $toneHash{ "$arrLine[0] $arrLine[1]" }{ tones }  = $arrLine[0];
    $toneHash{ "$arrLine[0] $arrLine[1]" }{ times }  = $arrLine[1];
    $toneHash{ "$arrLine[0] $arrLine[1]" }{ cmd }   = $arrLine[2];
    $toneHash{ "$arrLine[0] $arrLine[1]" }{ comment } = $arrLine[3];

   }#if not a comment line

  }#for each line in file

 close(TONEFILE);

}#readToneFile

readTone 采集到一种音调模式时,将把它与从 readToneFile 装入的现有音调模式相比较。compareToneSequences 子例程将在音调的计时之间以及音调值之间执行一个简单的差别检查。请注意音调值与计时的差异不复合。少量缺少计时或音调并不会导致整体匹配失败。

对于音调文件中的每个音调,构建音调和时间数组以供匹配。第一次比较是在音调的数目之间,因为比较七分音序列和二分音序列是没有意义的。对于每个音调和时间,检查值是否处于可接受的偏差参数内。音调和时间偏差的最大值是允许按照正确度而不是精度来匹配音调序列的临界值。您可以增大音调或时间偏差的最大值以使您可以在节奏计时或音调生成方面更自由。当自由设置可能会导致出现假检测结果时将调用警告和试验。例如,将音调偏差阈值增大到 5,而时间偏差阈值保持在 100000,这将使您可以在正确的时间输入与期望模式相去较远的音调并且仍能匹配模式 —— 如果只需练习计时它会很有用。

如果整个模式都匹配,则在 ~/.toneFile 中指定的命令是 run 并且如果启用了 verbose 模式,还会输出结果。下一步,如果未找到匹配,就退出子例程;或者如果找到匹配,就重设当前音调和时间记录。

清单 10. 创建音调模式
sub compareToneSequences 
{
 my $baseCount = @baseTones;
 my $countMatch = 0; # record how many tones matched

 for my $toneFromFile ( keys %toneHash )
 {
  my @confTones = split " ", $toneHash{$toneFromFile}{tones};
  my @confTimes = split " ", $toneHash{$toneFromFile}{times};

  my $confCount = @confTones;

  next unless( $confCount == $baseCount );

  # as a learning aid, the matching and non matching portions of the
  # comparison are printed out, so at least you can see what is going 
  # wrong while trying to remember your tone codes
  my $pos =0;
  my $toneMatchFail = 0;
  for( @baseTones )
  { 
   my $tonDiff = abs($confTones[$pos] - $baseTones[$pos]);
   my $tonStr = "t $pos b $baseTones[$pos] ".
          "c $confTones[$pos] \n";

   my $timeDiff = abs($confTimes[$pos] - $baseTimes[$pos]);
   my $timStr = "t $pos b $baseTimes[$pos] ".
          "c $confTimes[$pos] d $timeDiff\n";

   if( $tonDiff > $MAX_TONE_DEV )
   { 
    $toneMatchFail = 1;
    if( $option ){ print "NOTE DISSONANCE $tonStr" }
   }else
   { 
    if( $option ){ print "NOTE MATCH $tonStr" }
   }#if tone detected outside of deviation


   # if it's an exact match, increment the matching counter
   if( $timeDiff < $MAX_TIME_DEV ){
    if( $option ){ print "TIME MATCH $timStr" }
    $countMatch++;
   }else{
    if( $option ){ print "TIME DISSONANCE $timStr" }
    last;
   }# deviation check

   $pos++;

  }# for each tone to check 

  if( $countMatch == $confCount && $toneMatchFail == 0 )
  { 
   my $cmd = $toneHash{$toneFromFile}{ cmd };
   if( $option ){ print "run: $cmd\n" }
   $cmd =`$cmd`;
   if( $option ){ print "result: $cmd\n" }
   last;

  # otherwise, make the count of matches zero, in order to not reset
  }else
  { 
   $countMatch = 0;
  }

 }#for each tone in tone file

 # if the match count is zero, exit and don't reset variables so a longer
 # tone sequence can be entered and checked
 if( $countMatch == 0 ){ return() }

 # if a match occured, reset the variables so it won't match another pattern
 $toneCount = 0;
 @baseTones = ();
 @baseTimes = ();

}#compareToneSeqeunces

主程序逻辑

子例程就绪后,主程序逻辑将允许用户创建音调序列,或以守护进程模式运行来侦听音调命令和执行命令。当用户指定选项 “-c” 用于创建模式时将执行第一部分。简单的超时进程用于结束此 knock 序列。将音调之间的最大超时长度变量增大到允许音调间的停顿超过 4 秒。如果让最大超时长度保留为 4,则程序将结束并打印当前输入的音调序列。

清单 11. 超时进程
if( $option eq "-c" ){

 print "enter a tone sequence:\n";

 $startTime = getEpochSeconds(); # reset time out start

 while( my $sndPeekOutput = <STDIN> )
 { 

  $currTime = getEpochSeconds();

  # check if there has not been a tone in a while 
  if( $currTime - $startTime > $MAX_TIMEOUT_LENGTH ){

   $timeOut = 1; # exit the loop

  }else{

   # if a tone has been entered before timeout, reset timers so
   # more tones can be entered

   if( $toneCount != $toneAge ){
    $startTime = $currTime;  # reset timer for longer delay
    $toneAge = $toneCount; # synchronize tone counts
   }# if a new tone came in

  }# if timer not reached

  readTones( $sndPeekOutput );

  if( $timeOut == 1 ){ last }
 }#while stdin

 if( @baseTones ){
  print "place the following line in $ENV{HOME}/.toneFile\n\n";
  for( @baseTones ){ print "$_ " }
  print "_#_ ";
  for( @baseTimes ){ print "$_ " }
  print "_#_ (command here) _#_ <comments here>\n\n";
 }#if tones entered

主逻辑的第二部分不断地从 sndpeek --print 命令读取输出。达到超时阈值后将自动重置音调组,以区分单独的音调模式。考虑修改 LISTEN_TIMEOUT 变量以更快地达到音调输入次数,或延长超时变量以采集间隔更长的事件的音调模式。

清单 12. 修改 LISTEN_TIMEOUT
}else
{ 
 # main code loop to listen for tones and run commands
 readToneFile();
 $startTime = getEpochSeconds();

 while( my $sndPeekOutput = <STDIN> )
 { 

  $currTime = getEpochSeconds();

  if( $currTime - $startTime > $LISTEN_TIMEOUT ){

   $toneCount = 0;
   @baseTones = ();
   @baseTimes = ();
   $startTime = $currTime;
   if( $option ){ print "listen timeout - resetting tones \n" }

  }else{

   if( $toneCount != $toneAge ){
    $startTime = $currTime;  # reset timer for longer delay
    $toneAge = $toneCount;  # synchronize tone counts
   }# if a new tone came in

   compareToneSequences();

  }#if not reset timeout

  readTones( $sndPeekOutput );

 }#while stdin

}#if option set

安全问题及告诫

cmdWhistle 程序非常适于为系统提供额外的用户输入途径,这样您就可以用手继续敲击键盘和控制鼠标。如果需要系统验证,使用 cmdWhistle 时要特别谨慎。

除了侦听程序记录并模仿所选音调序列以在系统中运行命令这一明显问题之外,还有很多与音调验证相关的其他变数,这表示它在严格环境中的使用还尚未成熟。音调序列目前以两位“音符”的形式与四至九位表示延迟(以毫秒为单位)的部分一起存储在 ~/.toneFile 中。读取此“密码”文件或只简单地尝试匹配音调模式以获得对系统的访问权相对来说都很容易。通过减少毫秒值的精度可以使用单向散列,但这是有风险的,风险的估计就留给感兴趣的读者吧。


下载资源


相关主题

  • 您可以参阅本文在 developerWorks 全球站点上的 英文原文
  • 普林斯顿大学网站上的 sndpeek 程序。
  • ibiblio.org 上有 xwit 程序的镜像。
  • YouTube.comGoogle Video 上查看演示视频。
  • 访问 IBM developerWorks 的 PHP 项目资源 了解 PHP 的更多信息。
  • 访问 developerWorks 开源软件技术专区,获得广泛的 how-to 信息、工具和项目更新,帮助您使用开放源码技术进行开发,并将其与 IBM 的产品结合使用。

评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Open source, Linux
ArticleID=199674
ArticleTitle=工作中用声音控制计算机上命令的执行
publish-date=06142007