在C中

在C中

我知道如何按响铃echo -ne '\a'(或者,如果您不介意依赖外部命令,那就更好了tput bel:)。然而,它实际上所做的是将一些特殊代码发送到输出;然后,如果该输出恰好是一个终端,终端解释代码并响铃。

我的问题是我想从后台脚本发出警告噪音,任何终端都不会读取其输出。我可能会播放一些和谐的.wav文件paplay或类似的文件,但我更喜欢那种简单、刺耳的蜂鸣声。我不想依赖外部程序。

那么,问题:

  1. 终端发出的铃声在某种程度上是否特殊(例如使用专用硬件),或者它是像其他声音文件一样的声音文件吗?
  2. 如何再现那个声音?

答案1

研究完程序源码后beep,我想出了一个敲响警钟的最小 C 程序。

它使用 PC 扬声器,Linux 内核将其作为通常命名的 evdev 设备/dev/input/by-path/platform-pcspkr-event-spkr(指向实际位置的符号链接)提供。

有一个事件可以打开扬声器(IE以给定频率开始产生声音),以及另一个关闭扬声器的事件(IE停止声音)。我们只需发送第一个事件,然后休眠所需的持续时间,然后发送第二个事件。

这需要对设备进行写访问,可以通过 udev 规则、程序的 setuid 位或通过以 root 身份运行程序来设置,如beep.而且,这无法控制音量。不知何故,xset程序(来自 X 服务器套件)和终端仿真器没有这些限制。

在C中

#include <linux/input.h>  // struct input_event
#include <fcntl.h>  // open
#include <unistd.h>  // write, close
#include <time.h>  // nanosleep

/* frequency (Hz): */
static unsigned int const default_frequency = 440;
/* duration (ms): */
static unsigned int const default_duration = 200;
/* file path of PC speaker: */
static char const * const default_device = "/dev/input/by-path/platform-pcspkr-event-spkr";

void start_beep(int fd, int freq)
{
    struct input_event e = { 0 };
    e.type = EV_SND;
    e.code = SND_TONE;
    e.value = freq;
    write(fd, &e, sizeof(e));
}

void stop_beep(int fd)
{
    start_beep(fd, 0);
}

void sleep_ms(unsigned int ms)
{
    struct timespec duration =
        { .tv_sec  = ms / 1000U,
          .tv_nsec = (long)(ms % 1000UL * 1000UL * 1000UL) };
    nanosleep(&duration, NULL);
}

int main(void)
{
    int fd = open(default_device, O_WRONLY);
    start_beep(fd, default_frequency);
    sleep_ms(default_duration);
    stop_beep(fd);
    close(fd);
    return 0;
}

(为了简洁和清楚起见,上面的源代码跳过了所有安全检查(检查文件是否存在、检查是否open成功、检查文件描述符是否为字符设备、检查频率和持续时间是否为允许范围内的整数、检查是否write成功,检查是否nanosleep成功……)。在实际应用中,应该解决这个问题。)

在 shell 脚本中

上面的C程序可以用来观察事件的形状;然后,可以从 shell 脚本编写相同的事件。不幸的是,二进制格式很可能是特定于平台的。在我的带有 Linux 内核 5.2 的 x86-64 机器上,声音事件具有以下结构:

  • 16 字节:零
  • 2 个字节,小尾数:0x12(type值为 的字段EV_SND
  • 2 个字节,小尾数:0x2(code值为 的字段SND_TONE
  • 4 个字节,little-endian:以 Hz 为单位的频率,或 0 停止声音(字段value

这个二进制结构可以使用 Perl 的函数编写pack。也可以输出二进制整数使用纯 Bash,但这更详细。

# frequency (Hz):
default_frequency=440
# duration (ms):
default_duration=200
# file path of PC speaker:
default_device='/dev/input/by-path/platform-pcspkr-event-spkr'

function start_beep()
{
    declare -i freq="$1"
    perl -e 'print pack("qqssl", 0, 0, 0x12, 2, '$freq')'
}

function stop_beep()
{
    start_beep 0
}

# USAGE: beep [FREQUENCY [DURATION [DEVICE]]]
function beep()
{
    declare -i freq="${1-$default_frequency}"
    declare -i dur="${2-$default_duration}"
    declare dev="${3-$default_device}"
    # convert milliseconds to seconds
    declare dur_sec=$( printf '%u.%03u' $(( dur / 1000 )) $(( dur % 1000 )) )
    # write the sound events
    {
        start_beep $freq
        sleep $dur_sec
        stop_beep
    } >> "$dev"
}

答案2

如果您的脚本以 root 身份运行,您可以简单地执行以下操作:

echo -ne '\a' >/dev/console

相关内容