C에서

C에서

나는 벨을 누르는 방법을 알고 있습니다 echo -ne '\a'(또는 외부 명령에 의존하는 것이 마음에 들지 않으면 더 좋습니다 tput bel:). 그러나 실제로 수행하는 작업은 출력에 특수 코드를 보내는 것입니다.해당 출력이 터미널인 경우, 터미널은 코드와 벨소리를 해석합니다.

내 문제는 어떤 터미널에서도 출력을 읽을 수 없는 백그라운드 스크립트에서 경고 소음을 내고 싶다는 것입니다. 하모니 .wav파일 paplay같은 것을 재생할 수도 있지만 단순하고 거친 비프음이 더 마음에 듭니다. 외부 프로그램에 의존하고 싶지 않습니다.

따라서 질문은 다음과 같습니다.

  1. 터미널에서 나오는 벨소리는 어떤 면에서 특별한가요(예: 특수 하드웨어 사용) 아니면 다른 것과 같은 사운드 파일인가요?
  2. 그 소리를 어떻게 재현하나요?

답변1

프로그램 소스코드를 공부한 후beep, 알람을 울리는 최소한의 C 프로그램을 생각해 냈습니다.

이는 Linux 커널에서 일반적으로 명명된 evdev 장치 /dev/input/by-path/platform-pcspkr-event-spkr(실제 위치에 대한 기호 링크)로 제공되는 PC 스피커를 사용합니다.

스피커를 켜는 이벤트가 있습니다(주어진 주파수에서 소리를 내기 시작함) 및 스피커를 끄는 또 다른 이벤트(소리를 멈춰주세요). 첫 번째 이벤트를 보낸 다음 필요한 기간 동안 절전 모드로 전환한 다음 두 번째 이벤트를 보냅니다.

이를 위해서는 udev 규칙, 프로그램의 setuid 비트를 통해 설정하거나 에서와 같이 프로그램을 루트로 실행하여 설정할 수 있는 장치에 대한 쓰기 액세스가 필요합니다 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...) 실제 응용 프로그램에서는 이 문제를 해결해야 합니다.

쉘 스크립트에서

위의 C 프로그램을 사용하여 이벤트의 형태를 관찰할 수 있으며, 그런 다음 쉘 스크립트에서 동일한 이벤트를 작성할 수 있습니다. 불행하게도 바이너리 형식은 플랫폼에 따라 다를 가능성이 높습니다. Linux 커널 5.2가 설치된 x86-64 시스템에서 사운드 이벤트의 구조는 다음과 같습니다.

  • 16바이트: 0
  • 2바이트, 리틀 엔디안: 0x12( type값이 있는 필드 EV_SND)
  • 2바이트, 리틀 엔디안: 0x2( code값이 있는 필드 SND_TONE)
  • 4바이트, 리틀 엔디안: 주파수(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

스크립트가 루트로 실행 중인 경우 간단히 다음을 수행할 수 있습니다.

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

관련 정보