2010년 4월 10일 토요일

리눅스 사운드 프로그래밍

< 음악이 생겨라 (Let There Be Music) >

 

by Shuveb Hussain

한글 번역 전정호

이 글은 한글번역판을 요약한 것입니다.

 

*** 사운드 카드 프로그래밍 ***

 

 (1) 샘플링

  • 음을 녹음할 때 음질을 선택한다.
  • 음질을 결정하는 중요한 요소 중 하나가 샘플링 빈도(sampling rate)이다.
  • 음은 시간에 따라 진동수가 바뀌는 파장일 뿐이다.
  • 음을 녹음하려면 일정 간격으로 음의 진동수를 기록한다. 이 간격을 샘플링 빈도라고 한다.
  • 샘플링 빈도가 높을수록 음질이 좋아진다. 예를 들어, CD의 샘플링 빈도는 초당 44100번, 즉 44.1kHz이다.
  • PCM(Pulse Code Modulation, 펄스 코드 변조)이라고 하는 이 작업은 ADC(Analogue to Digital Converter, 아날로그-디지털 변환기)가 한다.

 

 

 (2) 비트와 채널

  • 음을 샘플링할 때, 샘플당 일정 수의 비트를 사용하고, 이런 채널을 여러 개 사용한다. 샘플링하는 자료의 비트 개수를 샘플링 정밀도(resolution)라고 한다.
  • 가장 흔한 형식은 8비트 양수 바이트 혹은 16비트이다.
  • 채널별로 자료를 분리한다. 예를 들어, CD 음악에는 왼쪽, 오른쪽(스테레오) 두 채널이 있다.
  • 8비트 정밀도로 CD음질을 녹음할 때 초당 필요한 크기를 계산해보면 :
  •        2        x       44100      =      88200 bytes
    (채널 개수) x (샘플링 빈도) = (초당 필요한 크기)

  • 이제 이론은 충분하니 실제 프로그램을 시작하자.

 

 

 (3) 사운드 카드를 프로그래밍 하기

  • 사운드 카드를 프로그래밍하는 방법이 몇가지 있다.
  •  초기 리눅스 드라이버가 만들어지던 때 두 그룹이 있었다.
    • Open Sound System(OSS)은 많은 장치 드라이버를 작성한 그룹으로 심지어 하드웨어 사양을 공개하지 않거나 비밀유지동의(non-disclusure agreement)를 하지않은 개발자에게 사양을 공개하지 않는 사운드 카드 제조사의 이진모듈도 포함하였다. 하드웨어 제조자가 개인에게 사양을 알려주려고하지 않았기 때문에 OSS는 4Front Technologies란 회사를 만들었다.
    • ALSA(Advanced Linux Sound Architecture)는 제조사가 사양을 공개한 사운드 카드 드라이버만 작성했다. ALSA는 2.6-test 커널에 포함되어 있다.
  • ALSA는 OSS 에뮬레이션 계층을 제공하기 때문에 OSS로 작성한 프로그램은 OSS와 ALSA 시스템에서 모두 동작한다. 간단하고 대다수의 시스템에 설치되어 있기 때문에 이 글은 OSS 프로그래밍을 다룰 것이다.

 

  • 유닉스/리눅스는 모든 장치 드라이버마다 (보통 /dev 디렉토리에) 파일 시스템 인터페이스가 있다. 이 장치파일을 open, read, write, lseek, close와 같은 파일관련 시스템 호출에 사용한다. 장치파일을 장치드라이버가 요청을 기다리는 파일시스템상의 훅(hook)이라고 보면 된다.예를 들어, /dev/hda1 파일은 primary master IDE 하드디스크의 첫번째 파티션에 대한 인터페이스이다. open 시스템호출을 사용하여 파일을 열면 일반파일과 같이 파일핸드(file handle)을 얻을 수 있다. 파일핸들을 읽으면 실제로 파티션의 첫번째 섹터에 있는 자료를 읽게 된다. 또, 앞으로 lseek하면 파일포인터(file pointer)를 앞으로 이동한다. 섹터를 건너뛰으려면 섹터 크기만큼 앞으로 lseek한다. 위험하므로 하드디스크 장치 파일을 수정하지는 마라. 파티션에 값을 쓰면 파티션이 망가질 수 있다. 하드웨어 장치는 보통 root만 직접 접근할 수 있다.

 (4) 코드

  • 이제 코드로 넘어가자. 이 코드는 root가 아니어도 문제없이 실행되야 한다. 파일장치를 접근하는데 문제가 있다면 root로 su하고 chmod하여 접근 권한을 푼다. 물론 리눅스에서 작동하는 사운드카드를 사용해야 한다. 직접 코드를 입력하지 않고 여기에서 다운받을 수 있다. demo.pcm 파일도 같이 다운받아야 한다.

 

/*
 * oss.c는 사운드카드에서 raw PCM 22KHz 샘플음을 낸다
 *
 * 중요 - 프로그램을 실행하기전에 현재 디렉토리에 demo.pcm 파일이 있는지 확인하라.
 */

#include < sys/types.h >
#include < sys/stat.h >
#include < sys/soundcard.h >
#include < sys/ioctl.h >
#include < unistd.h >
#include < fcntl.h >
#include < errno.h >
#include < stdlib.h >

#define SECONDS 5 //재생 시간 (초)

int main()
{
    int fd;
    int handle = -1;
    int channels = 1;         // 0=모노 1=스테레오
    int format = AFMT_U8;
    int rate = 22000;
    unsigned char* data;

   /* 사운드카드에 해당하는 파일에 쓰기위해(write) 파일을 연다(open). DSP = Digital Signal Processor */
    if ( (handle = open("/dev/dsp",O_WRONLY)) == -1 )
    {
  perror("open /dev/dsp");
  return -1;
    }

   /* 재생하려는 음이 스테레오라고 사운드카드에 알린다. 0=모노 1=스테레오 */
    if ( ioctl(handle, SNDCTL_DSP_STEREO,&channels) == -1 )
    {
  perror("ioctl stereo");
  return errno;
    }

    /* 자료 형식을 사운드카드에게 알린다 */
    if ( ioctl(handle, SNDCTL_DSP_SETFMT,&format) == -1 )
    {
  perror("ioctl format");
  return errno;
    }

    /* DSP 재생율(playback rate), 즉 raw PCM 음의 샘플링 빈도를 지정한다. */
    if (ioctl(handle, SNDCTL_DSP_SPEED,&rate) == -1 )
    {
  perror("ioctl sample rate");
  return errno;
    }

    // 빈도 * 5 초 * 두 채널
    data = malloc(rate*SECONDS*(channels+1));

    if((fd=open("demo.pcm",O_RDONLY))==-1)
    {
  perror("open file");
  exit(-1);
    }

    /* demo 파일에 저장된 정보를 읽어서 할당한 메모리에 저장한다 */
    read(fd,data,rate*SECONDS*(channels+1));
    close(fd);

    /* 읽은 내용을 사운드카드에 쓴다(write)! 그러면 재생이 된다. */
    write(handle, data, rate*SECONDS*(channels+1));

    if (ioctl(handle, SNDCTL_DSP_SYNC) == -1)
    {
  perror("ioctl sync");
  return errno;
     }

    free(data); //좋다. 마무리.
    close(handle);

    printf("===Done===\n");
    return 0;
}


 

(5) 코드 설명

  • 이 프로그램은 raw PCM, 22KHz, stereo 자료파일을 5초간 연주한다. 프로그램은 크게 3가지 작업을 한다.
    • sound 장치 열기(open)
    • 재생을 위한 파라미터 설정
    • 장치에 자료 쓰기(write)
  • open() / write() / close() 시스템 호출은 일반 파일과 동일하다.
  • ioctl()(Input/Output ConTroL) 시스템 호출을 사용하여 재생에 사용할 파라미터를 설정한다. ioctl() 시스템 호출은 입출력 장치와 통신하거나 파라미터를 설정할 때 사용한다. 또 시스템 호출의 개수를 줄이기 위한 편법으로 사용되기도 한다. 그래서 흔히 프로그래머의 다용도칼이라고 부른다. 함수의 원형
  • int ioctl( int fd, int command, ... );

    • fd : 장치의 파일 기술자(file descriptor)
    • command : 장치에 대한 요청/명령
    • ... : command에 따라 달라진다.
  • ioctl(handle, SNDCTL_DSP_SPEED, &rate)
    • DSP 재생을 설정한다. rate는 장치 드라이버로 전달되는 실제 재생율이다.
  • 예제 프로그램에는 3가지 ioctl()이 있다.
    • 재생에 사용할 채널 개수(channels)를 설정
    • PCM 형식(format)을 설정
    • 재생율(sampling rate)을 설정
  • 이것으로 장치에 필요한 정보는 충분하고, 다음은 장치파일에 PCM 자료를 직접 쓰면(write) 재생이 된다. 마지막 ioctl()은 장치를 비우고(flush), 그 다음 PCM 자료를 저장하기 위해 할당한 메모리를 해제한다. 이게 끝이다.

 

 

(6) 음악 저장 형식

  • 방금 전에 5초짜리 22KHz 음악을 재생하기 위해 raw PCM 자료 220KB가 필요했다.
    • 22,000 x  5  x   2    = 220,000 bytes
    • (샘플   x 초 x 채널 = 데이터 크기)
    • CD 음질로 1분을 재생하기 위해 필요한 크기는 :
      • 44,100 x 60 x 2 = 5292000 bytes, 약 5 MB!
  • 음악 CD는 raw PCM 형식으로 자료를 저장한다. 그러나 컴퓨터는 음악을 효율적으로 저장하고 (인터넷으로) 전송하기 위해 음악을 압축하여 크기를 줄이고 재생시 다시 압축을 푼다. 어떤 음악이던 관계없이 샘플링률은 고정되어 있다. 아무 소리가 없는 몇 초간을 샘플링 했다고 생각해 보자. 같은 시간의 시끄러운 락 음악과 동일한 크기가 필요하다.
  • raw 음악파일은 자료반복이 매우 적기 때문에 일반적인 압축방법으로는 잘 압축되지 않는다. 음악은 다른 기술을 사용하여 압축한다. 가장 일반적인 방법은 사림이 귀로 들을 수 없는 부분을 제거하거나 음악 특성에 따라 압축한다. 음악을 좋아하는 사람들에게 가장 대중적인 형식은 분명 MP3다. 현재 MP3에는 특허문제가 걸려 있기 때문에, 오픈소스 공동체에서는 Ogg Vorbis 형식을 반긴다. 두 형식 모두 크기가 비슷하게 줄어들며 음질의 손실을 최소화한다.

 

 

 (7) Ogg Vorbis 파일을 재생하는 방법

  • 사운드 카드가 어떻게 raw 음악 자료를 재생하는지 알았으니, Ogg Vorbis 파일을 재생하는 방법을 알아보자. Ogg Vorbis 파일을 재생하려면 먼저 복호화(decode), 즉 raw PCM 자료로 변환해야 한다.
  • libvorbisfile이 이 작업을 한다. 이 라이브러리는 매우 낮은 수준을 다루지만 더 세밀한 조절이 가능한 libvorbis 라이브러리보다 높은 수준에서 변환을 한다. 이제 문제가 간단해진다 : Ogg Vorbis 파일에서 읽은 자료를 라이브러리로 넘기고, 라이브러리가 변환된 raw PCM 자료를 사운드 카드로 직접 보낸다. 소스는 여기에
  • #include < sys/types.h >
    #include < sys/stat.h >
    #include < sys/soundcard.h >
    #include < sys/ioctl.h >
    #include < unistd.h >
    #include < fcntl.h >
    #include < errno.h >
    #include < stdlib.h >

    #include "vorbis/codec.h"
    #include "vorbisfile.h"

    int setup_dsp(int fd,int rate, int channels);

    char pcmout[4096]; // 변환한 PCM 자료를 저장할 4KB 버퍼
    int dev_fd;

    int main(int argc, char **argv)
    {
     OggVorbis_File vf;
     int eof=0;
     int current_section;
     FILE *infile,*outfile;

     if(argc<2)
     {
      printf("supply file arguement");
      exit(0);
     }

     if ( (dev_fd = open("/dev/dsp",O_WRONLY)) == -1 )
    {
      perror("open /dev/dsp");
      return -1;
     }

     infile=fopen(argv[1],"r");

     if(infile==NULL)
     {
      perror("fopen");
      exit(-1);
     }  

    if(ov_open(infile, &vf, NULL, 0) < 0)
    {
      fprintf(stderr,"Input does not appear to be an Ogg bitstream.");
      exit(1);
     }

     char **ptr=ov_comment(&vf,-1)->user_comments;
     vorbis_info *vi=ov_info(&vf,-1);

     while(*ptr)
     {
      fprintf(stderr,"%s",*ptr);
      ++ptr;
     }

     fprintf(stderr,"Bitstream is %d channel, %ldHz",vi->channels,vi->rate);
     fprintf(stderr,"Decoded length: %ld samples",(long)ov_pcm_total(&vf,-1));
     fprintf(stderr,"Encoded by: %s",ov_comment(&vf,-1)->vendor);

     if(setup_dsp(dev_fd,vi->rate,vi->channels-1))
     {
      printf("dsp setup error.aborting");
      exit(-1);
     }

     int count=0;

     while(!eof)
     {
          long ret=ov_read(&vf,pcmout,sizeof(pcmout),0,2,1,&current_section);
          if (ret == 0)
          {
        /* EOF */
      eof=1;
          }
          else if (ret < 0)
          {
            /* 자료에 오류가 있다.  (프로그램에) 중요하다면 보고하지만,
      이 프로그램은 하지 않는다. */
           }
          else   {
      printf("Writing %d bytes for the %d time.",ret,++count);
      write(dev_fd,pcmout,ret);
          }
     }

     ov_clear(&vf);
     fclose(infile);
     
    if (ioctl(dev_fd, SNDCTL_DSP_SYNC) == -1)
    {
          perror("ioctl sync");
      return errno;
     }

     close(dev_fd);
     fprintf(stderr,"Done.");

     return(0);
    }


    int setup_dsp(int handle,int rate, int channels)
    {
        int format;

        if ( ioctl(handle, SNDCTL_DSP_STEREO,&channels) == -1 )
        {
      perror("ioctl stereo");
      return errno;
        }

         format=AFMT_U8;

         if ( ioctl(handle, SNDCTL_DSP_SETFMT,&format) == -1 )
        {
      perror("ioctl format");
      return errno;
        }
     
        if ( ioctl(handle, SNDCTL_DSP_SPEED,&rate) == -1 )
       {
      perror("ioctl sample rate");
      return errno;
        }

         return 0;
    }

 

  • 다음과 같이 컴파일 한다.
  • gcc oggplay.c -oggplay -lvorbisfile -I/path/to/vorbis/header/files -L/path/to/vorbis/lib/files

  • 라이브러리파일과 헤더파일이 /usr/lib 이나 /usr/include 같은 일반적인 위치에 있다면 -l 과 -L 명령행 옵션이 필요없다. 모든 대중적인 배포본에 vorbisfile 라이브러리가 포함되있다. 만약 시스템에 설치가 안되있다면 여기에서 다운받을 수 있다. 또, Ogg Vorbis 사이트는 여기다. 프로그램을 컴파일하려면 (개발용) 헤더파일이 필요하다.

  • setup_dsp 함수는 dsp 장치를 열고(open), 세가지 중요한 파라미터인 채널, 형식, 재생율을 지정한다. 음악 재생을 위해 가장 중요한 세가지 파라미터를 기억하라. main 함수는 오류검사외에, 쉘 아규먼트로 준 Ogg Vorbis 파일을 열고, 파일에 저장된 설명과 파일정보를 출력한다. ov_open() 라이브러리함수가 채우는 구조체에 저장된 샘플링 빈도와 채널 정보를 setup_dsp 함수에 넘긴다. 그후 프로그램은 Ogg Vorbis 파일 내용을 변환하여 버퍼에 넣는 반복문을 실행한다. 반복문은 버퍼에 저장한 raw PCM 자료를 앞에서 설정한 dsp 장치에 쓴다. 파일이 끝날때까지 반복한 후, 마지막으로 정리하고 종료한다. 간단하지 않는가? 쉽고 직관적으로 변환해준 libvorbisfile에게 감사하다.
  • Ogg Vorbis 파일을 변환하려면 libvorbisfile을 사용하면 된다. MP3 파일을 변환하여 재생하고 싶다면 여러 라이브러리를 선택할 수 있다. 많이 사용하는 라이브러리중에 영상과 음악을 모두 변환할 수 있는 smpeg 라이브러리가 있다. 또, libmad 라이브러리도 찾았다. 나는 smpeg의 음악 변환 기능만 사용하기로 했다. 관심이 있다면 smpeg의 MPEG 영상 변환 기능도 살펴보길 바란다. 라이브러리 사용법을 설명하는 샘플 프로그램 plaympeg도 포함되있다. 영상을 보여주기위해 smpeg은 매우 사용하기 쉽고 기능이 많은 그래픽 프로그래밍 라이브러리인 SDL을 사용한다. 이외에도 MP3를 변환하는 라이브러리는 많지만, 많은 리눅스 배포본에 smpeg이 포함되있기때문에 smpeg을 선택했다. 많이 사용하는 gtv MPEG 영상플레이어도 smpeg 패키지에 포함되있다.

 

 (8) MP3 파일 연주하는 프로그램 예제

  • smpeg 라이브러리가 변환한 MP3 자료를 직접 사운드 카드로 보낸다. 직접 PCM을 다룰 필요가 없다. 소스 다운로드
  • #include < stdio.h >
    #include < stdlib.h >
    #include < string.h >
    #include < signal.h >
    #include < unistd.h >
    #include < errno.h >
    #include < sys/types.h >
    #include < sys/stat.h >
    #include < sys/ioctl.h >
    #include < sys/time.h >

    #include "smpeg.h"

    void usage(char *argv0)
    {
        printf("Hi,%s This is the normal useage.",argv0);
    }

    int main(int argc, char *argv[])
    {

        int volume;
        SMPEG *mpeg;
        SMPEG_Info info;
       
        volume = 100; //Volume level

        /* 아규먼트가 없으면 사용법만 출력한다 */
        if (argc == 1)
        {
             usage(argv[0]);
             return(0);
        }

        mpeg = SMPEG_new(argv[1], &info;, 1);

        if ( SMPEG_error(mpeg) )
        {
                fprintf(stderr, "%s: %s", argv[1], SMPEG_error(mpeg));
                SMPEG_delete(mpeg);
        }

        SMPEG_enableaudio(mpeg, 1);
       SMPEG_setvolume(mpeg, volume);

        /* 음악에 대한 정보를 출력한다 */
        if ( info.has_audio )
        {
                printf("%s: MPEG audio stream", argv[1]);
         
                if ( info.has_audio )
                    printf("Audio %s", info.audio_string);

                 if ( info.total_size )
      printf("Size: %d", info.total_size);
             

                  if ( info.total_time )
      printf("Total time: %f", info.total_time);

                   /* 재생하고 재생이 끌날때까지 기다린다 */
      SMPEG_play(mpeg);  

      while(SMPEG_status(mpeg)==SMPEG_PLAYING);

            SMPEG_delete(mpeg); //구조체 반환
       }

       return(0);
    }

  • 여기에 있는 smpeg을 설치한후 프로그램을 컴파일한다. smpeg을 설치하기위해서 미리 SDL을 설치해야 한다. 컴파일할때 헤더파일과 라이브러리파일 경로를 컴파일러에게 알려줘야 한다. 쉽게 하려고 smpeg 패키지는 smpeg-config라는 간단한 도구를 제공한다. 다음과 같이 프로그램을 컴파일한다:
  • gcc playmp3.c `smpeg-config --cflags --libs` -I/usr/include/SDL

  • smpeg-config 명령어를 감싸는 따옴표문자를 주의하라. 보통 backqoute라고 부르며, US식 키보드에서 틸데(~) 문자와 같이 있다. 쉘에서 이 문자는 안에 있는 명령어의 출력을 그 자리에 대신한다. 그냥 smpeg-config 명령만 실행해보면 내가 무슨 말을 하는지 알 것이다. -I/usr/include/SDL 명령행 옵션을 -I < 실제 SDL 경로 >로 수정하라. SDL 함수를 사용하지 않아도, smpeg.h가 내부적으로 SDL 헤더파일을 사용하므로 SDL 헤더파일 경로를 지정해야 한다. RPM 사용자는 프로그램을 컴파일하기위해 smpeg과 smpeg-devel 패키지를 설치해야 한다.
  • SMPEG_new 라이브러리함수는 필요한 내부 객체를 할당하고, MP3 파일에 대한 정보를 SMPEG_info 구조체에 채운다. 프로그램은 이 정보를 출력하고, SMPEG_play를 호출하여 재생을 시작한다. 함수는 즉시 반환되기때문에 다른 작업을 하면서 MP3를 연주하는 프로그램에 유용하다. 이 프로그램에서는 따로 할 일이 없기때문에 재생이 끝날 때까지 기다린다. SMPEG_delete는 그동안 라이브러리가 할당한 모든 메모리를 반환하고, 마친다

 

 글이 여러분의 프로그래밍 작업에 조금이나마 도움이 되길 바란다. 의견을 듣고 싶다. 잘못된 점을 알려주거나 제안을 바란다. shuveb@shuvebhussain.org로 내게 연락할 수 있다.

 

참고로 테스트를 해봤는데 PCM RAW파일로만 가능한다. ^^ 형식이 그렇네. mp3나 다른 형식의 파일을 받아서 cool edit로 변환해 주었다. 쿠울~~~ 에디터 ㄱㄱ 그리고 역시나 속도 그런거 잘 맞춰줘야 한다는 ^^

 

출처 : http://www.whiterabbitpress.com/lg/issue97tag/shuveb.html

 

출처 : 한국어 출처, 원문(영어)

2010년 4월 9일 금요일

리눅스 멀티미디어 프로그래밍

1. Linux용 멀티미디어 환경

 (1) 사운드

  •  기본적으로 리눅스에서는 커널 차원에서 사운드 드라이버를 지원한다.
  • 리눅스 지원 사운드 드라이버
    • 리눅스 커널과 함께 배포된 것(OSS/Free)
    • 상업용 드라이버 OSS
    • 무료로 배포되고 있는 ALSA
  • 커널 표준 사운드 드라이버
    • OSS/Free라고도 불리우며 4Front Technologies 사에서 OSS 개발을 하고 있는 Hanu Savolainen씨가 대부분을 개발했다. 지금도 OSS에서 무상 드라이버를 기부하여 이에 대응하는 사운드 카드는 점점 늘어가고 있다.
  • OSS(Open Sound System)
    • 미국의 4Front Technologes라는 회사가 개발하여 배포하고 있는 사운드 드라이버로 자유롭게 사용할 수 있는 것과 상업용이 있다.
  • ALSA(Advanced Linux Sound Architecture)
    • Jaroslav Kysela(체코 거주)가 메인 프로그래머로서 활동하고 있는 ALSA Project에서 제작, 배포하고 있는 자유 Linux용 사운드 드라이버, GPL에 의한 재배포가 가능함, 커널 표준 드라이버나 OSS 드라이버와의 호환성이 있어, 이들 드라이버용으로 작성된 프로그램의 대부분이 ALSA 드라이버에서 그대로 사용할 수 있다. 인스톨 과정이 좀 복잡함, 커널 2.4부터 자동으로 포함되어 있다.

  (2) 화상(Video)

  •  리눅스에서 지원하는 화상캡쳐 장치들로는 TV 수신카드와 영상 캡쳐 장치들 그리고 USB 화상 CAM 으로 나눌 수 있다.
    • Video4Linux
      • Kernel에서 지원하는 기본 모듈로 TV 수신카드를 지원하기 위해 등장.
    • Video4Linux2
      • Video4Linux 1.0 버전이 TV 수신카드를 위해 등장했기 때문에 화상 Cam에는 맞지 않기 때문에 USB Web CAM을 위해서 등장했다.
      • 화면의 확대/축소 기능 지원

2. Linux Sound 프로그래밍

 (1) 사운드 프로그래밍

  • 리눅스에서의 사운드 프로그래밍은 커널에서 제공해주는 API를 이용해서 만든다.
  • 리눅스 사운드에서 Degital Audio("/dev/dsp"), mixer("/dev/mixer"), MIDI, raw Music 그리고 synthesizer와 같은 장치들을 사용한다.
  • 기본적으로 사용되는 Device 장치들
    • /dev/dsp(Digital voice device)
      • 마이크로 사운드를 녹음하고 스피커로 사운드를 출력하기 위해 사용됨
    • /dev/mixer(mixer)
      • 사운드 볼륨을 조절할 때 사용됨, 마이크의 입력 방향 설정 가능
    • /dev/sequencer(Synthesizer)
      • 사운드로 효과를 만드는데 사용된다. 미리 녹음된 소리(악기 소리나 기타소리)를 합성해서 새로운 소리를 만든다.
    • /dev/music
      • 기본적으로는 "/dev/dsp"와 같다. 차이는 알고리즘 적으로 /dev/dsp는 8bit unsigned 선형(Linear) Encoding을 사용하고, /dev/music은 mu-law Encoding을 사용한다.
    • /dev/midi
      • MIDI 인터페이스를 이용해서 MIDI 장치들(Keyboards, synthesizers, stage props)을 연결하는데 사용된다.
  • 우리는 여기서 /dev/dsp를 이용해서 사운드를 입력 받아서 출력하는 어플을 작성

 (2) OSS API Basic

  • OSS API들은 <soundcard.h>라는 헤더파일에 정의되어 있다.
  • 위의 헤더파일은 보통 "/usr/include/sys"에 위치하고 있다.
  • OSS에서 장치의 값을 설정하고 읽어오는 방법은 ioctl() 함수를 이용한다.

 (3) Audio Programming

  • 디지털 오디오(Digital Audio)는 컴퓨터 외부로 소리를 표현하는 일반적인 방법이다.
  • 오디오 프로그래밍의 일반적인 방법
    • 오디오 장치를 open() 한다.
    • 환경을 설정한다.
    • 입출력을 위한 메모리(버퍼)를 설정한다.
    • Read/Write 작어
    • 사용이 끝난 오디오 장치를 close() 한다.
  • 우선 "/dev/dsp"를 사용하기 위해서는 open()을 이용해서 장치를 열어야 한다.

### fedora11에서는 커널에 기본적으로 /dev/dsp와 /dev/mixer가 모듈로 설치가 되어서 부팅후 모듈을 올려줘야 한다. 아래처럼 시스템 시작시 모듈로 올라가도록 해준다.

# vi /etc/rc.d/rc.local
 ...
modprobe snd-pcm-oss

 

 

 (4) 사운드 장치 열기

  • open()의 flag 값
    • O_RDONLY : 사운드 장치(보통 마이크)로부터 입력을 받을 때 사용한다.
    • O_WRONLY : 사운드 장치(뽀통 스피커)로 사운드를 출력할 때 사용한다.
    • O_RDWR : 사운드 장치에서 사운드를 입력과 출력을 동시에 할 경우 사용된다.
    • if( fd = open("/dev/dsp", O_WRONLY, 0) ) == -1 ){
          perror("OSS : error opening device\n");
          return -1;
      }

  • 사운드 장치를 사용할 수 없을 경우 -1을 반환한다. 만약 다른 프로그램에서 사운드 장치를 사용하고 있을 경우 "EBUSY"라고 에러값을 설정한다.

 

 

 (5) 샘플링 환경 설정하기

  • 사운드 장치를 open한 후에는 사운드 입출력을 하기 전에 기본적인 설정이 필요하다. 사운드 음질에 영향을 미치는 다음의 기본적인 세가지 parameter가 존재한다.
    • Sample format(number of bits)
    • Channel의 수(mono or stereo)
    • Sample Rate(spee)
  • 위의 설정은 순서대로 해야한다. 아래의 설정을 위의 설정보다 먼저하면 설정이 되지 않는다.

 

 

 (6) 오디오 포맷 설저하기

  • Sample format은 음질에 영향을 미치는 중요한 변수이다.
  • Sample format은 여러가지가 있지만 여기서 AFMF_S16_LE를 사용하겠다. AFMF_S16_LE는 Signed 16Bit Little Endian을 뜻한다. 이 포맷은 일반적인 PC의 사운드 카드에서 사용되고 있다.
  • 사운드 카드를 설정하기 위해서는 앞에서 설명한 ioctl()에 매개변수로 SNDCTL_DSP_SETFMT를 사용한다.
    format = AFMF_S16_LE;
    if( ioctl(fd, SNDCTL_DSP_SETFMT, &format) == -1 ){
        perror("SOUND_PCM_SETFMT");
        return -1;
    }

 

 

 (7) 채널의 수 설정하기

  • 현재 사용되는 대부분의 오디오 장치들은 Stereo모드를 지원한다. 기본적으로 설정되는 모드는 Mono이다.
  • ioctl()에 SNDCTL_DSP_CHANNELS을 사용해서 채널의 수를 선택할 수 있다.
  • Mono의 경우는 1이고, Stereo는 2이다.
  • stereo = 2; // 1 : mono, 2 : stereo
    if( ioctl(fd, SNDCTL_DSP_CHANNELS, &stereo) == -1 ){
        perror("SOUND_PCM_CHANNELS");
        return -1;
    }

 

 

 (8) Sampling Rate 설정하기

  • Sampleing Rate는 소리의 질을 결정하는 파라미터이다.
  • OSS API는 1Hz에서 2GHz의 대역을 지원한다. 기본적으로 지원되는 Sampling rate는 8kHz이다.
  • 전화기(음성)에서 기본적으로 사용하는 것이 8kHz이다. CD의 경우 44.1kHz, DVD의 경우 96kHz의 값을 가지고 있다.
  • 우리는 음성을 이용해서 녹음/재생할 것이므로 8kHz를 이용할 것이다.
  • ioctl()에 SNDCTL_DSP_SPEED를 이용하면 Sampling Rate를 선택할 수 있다.
  • rate = 8000;
    if( ioctl(fd, SNDCTL_DSP_SPEED, &rate) == -1 ){
        perror("SOUND_PCM_SPEED");
        return -1;
    }

 

 

 (9) Full Duplex 설정하기

  • 많은 장치들이 half duplex이다. Half duplex의 경우 Recording이나 Playing이 동시에 되지 않는다. 오직 한 번에 한 가지의 일을 할 수 있다.
  • 물론 다중 채널이 지원되는 경우 Recording과 Playing을 각각 다른 채널을 이용해서 하면 된다.
  • 단일 채널에 Full Duplex를 지원하기 위해서는 사운드 장치를 설치하기에 앞서서 Full Duplex로 설정해아 한다. 설정은 다음과 같다.
  • if( ioctl(fd, SNDCTL_DSP_SETDUPLEX, 0) == -1){
        perror("SOUND_PCM_SETDUPLEX");
        return -1;
    }

 

 

 (10) 사운드 입/출력

  • 일반적으로 리눅스에서는 장치를 파일의 개념으로 사용한다.
  • 사운드를 입력받기 위해서는 파일을 읽는 것과 마찬가지로 read()를 사용한다.
  • if( status = read(fd, buf, count) ) == -1 ){
        perror(SOUND_READ_ERROR");
        return -1;
    }

  • 마친가지로 사운드를 출력하기 위해서는 write()를 사용한다.
  • if( status = write(fd, buf, count) ) == -1 ){
        perror("SOUND_WRITE_ERROR");
        return -1;
    }

  • 더 자세한 사항을 알고 싶으면 OSS API 문서를 참조하라.

 

 

 (11) 동시에 여러가지 일하기

  • 여러가지 일을 동시에 실행시키기 위해서는 thread나 멀티  proecess를 사용해야 한다. 이것들은 설정이 복잡하다. 그래서 여기서는 기본적으로 Qt에서 제공하는 QTimer를 사용하도록 한다.(자세한 내용은 출처를 참고한다.)

 

3. Video4Linx 프로그래밍

  • Video4Linux는 리눅스에서 비디오 장치들을 제어하고 사용하기 위한 API들의 모임이다.
  • 비디오 장치들로는 튜너를 가지고 있는 비디오 카드나 PC Web CAM과 같은 장치들을 들 수 있다. 튜너를 가지고 있는 장치들을 제어해서 TV나 AM/FM 라디오, TeleText등의 방송을 시청할 수 있다.
  • Video4Linux는 커널에 기본적으로 포함되어 있으므로 따로 설치할 필요가 없다.
  • 이 Video4Linux를 이용해서 X윈도우 상에서 TV를 볼 수 있는 프로그램으로는 xawtv나 xwintv 등이 있다.
  • 이 곳에서는 Philips Web Cam을 이용해서 프로그램할 것이다. 튜너를 가지고 있는 디바이스(BT848)도 거의 같은 방식으로 동작하기 때문에 프로그램을 적용하는데 무리가 없을 것으로 생각이 든다.
  • 비디오 화면을 캡쳐하는 순서(드라이버에 따라서는 mmap 등 일부의 조작이 그 기능이 없는 경우 malloc으로 메모리 버퍼를 잡을 수도 있다.)
    • Video4Linux 장비의 정보(사양)을 얻기
      • capture 카드에 관한 정보를 얻는다. 프로그램 내에서 capture 카드를 하나로 고정하고 있다면, 하지 않아도 된다.
    • 사용 가능한 채널 얻기
      • capture 카드로 사용 가능한 채널(영상 소스)의 종류를 취득한다. 이것도 위와 마찬가지로 프로그램 내에서 capture 카드를 하나로 고정하고 있다면, 하지 않아도 된다.
    • Capture할 채널의 설정
      • 앞에서 가져온 정보를 이용해서 capture 하는 채널을 선택한다. 동시에, 영상 신호의 포맷 종류(NTSC, PAL, SECAM 등)도 선택한다.
    • 파라미터 설정
      • 밝음(Brightness), 대비(Contrast), 휴(Hue) 등의 파라미터 설정한다.
    • 메모리 공간 할당
      • malloc이나 mmap의 시스템 콜을 이용해서 캡쳐할 영상이 저장될 메모리 기억공간(buffer)을 확보한다.
    • Capture 시작하기
      • 앞에서 확보한 메모리 공간에 capture가 되도록 한다.
    • Capture 종료까지 기다리기
      • 그 밖에 하는 것이 없으면, capture 처리가 종료될 때까지 기다린다.
    • 화성처리
      • capture한 버퍼 내의 영상 데이터를 원하는 목적으로 처리한다. 이 과정에서 화면에 영상을 표시할 수 있다.
  • 더 자세한 내용은 출처를 참고한다.

 

 

출처 : 리눅스 멀티미디어 프로그래밍

전역변수에 왜 static을 사용했을까?

  • 전역변수에서의 static은 해당하는 파일 내에서만 사용한다는 의미를 가지고 있다.
  • 여러 개의 파일로 개발할 때에 유용한다.
  • a.c라는 파일과 b.c라는 파일에 똑같이 전역변수를 정의할 경우, c.c라는 파일에서 해당하는 전역변수를 사용하려 할 때 어느 쪽을 사용해야 할지 헷갈리게 된다. 아마도 링크 시에 에러가 날 것이다.
  • 전역변수를 static으로 정의하면 그 파일 내에서만 사용할 수 있으므로 외부에서는 변수에 접근을 못한다. 이로 인해 디버깅의 노력을 줄일 수 있다.
  • 완전히 외부 모듈에 공개할 것만 일반적인 전역변수로 잡고 나머지 모듈 내에서 사용할 것은 모두 static으로 선언하면 static 변수를 누가 바꿨는지 추적하기 위해 모든 소스를 뒤질 필요가 없다.

출처 : 네이버 지식in

inline 함수

< inline 함수 >

- 매크로 함수와 그냥 함수의 중간적인 성격을 띈 함수이다.

 

* 사용 이유

  • 일반적인 함수를 사용하면 필요할 때마다 호출하여 사용하므로 실행파일의 크기를 줄일 수 있으나 제어권의 이동이 심하므로 실행 속도가 느려진다.
  • 매크로 함수는 컴파일시 전개되어 치환되므로 제어권 이동이 발생하지 않으므로 속도가 빠르지만 데이터형 지정을 할 수가 없고 실행파일의 용량이 커지게 된다.
  • 인라인 함수는 컴파일시 통째로 매크로처럼 전개되어 속도면에서 큰 이득을 얻는다. 그리고 데이터형 체크를 할 수 있다. 용량이 커지는 단점이 있긴 하지만 간단한 작업을 하는 함수의 경우에 이용하면 매우 편리하다.

* 장점

  • function call이 생략되는 형태로 실행되기 때문에 function call에 의한 여러가지 오버헤드(CPU register 백업, stack 관리 등)를 생략하기 때문에 간단하면서 자주 사용되는 함수에는 더 빠른 효율성을 갖게 된다.

*단점

  • 지나친 inline 함수의 사용은 실행파일이 커지게 하고, 이 때문에 페이지 폴트를 야기하는 원인이 되어 퍼포먼스가 떨어지게 되기도 한다.

* 매크로 함수와 인라인 함수의 차이점

  • 안정성 측면에서 inline 함수가 앞선다.

* inline 함수의 제약조건

  • inline 함수 내에서는 루프문(do while, while, for), switch, goto문을 사용할 수 없다.
  • inline 함수 호출시 호출되기 전에 먼저 inline 함수가 정의되어 있어야 한다.
  • inline 함수 내에서 재귀호출을 할 수 없다.
  • inline 함수는 한 수식 내에서 두 번이상 호출될 수 없다.
  • 함수 포인터로 inline 함수의 주소를 취할 수 없다.
  • inline 함수는 호출방식이 아니라 치환전개방식이기 때문이다.

 

출처

2010년 4월 3일 토요일

pthread 사용하기

< pthread 사용하기 >

1. 기본 쓰레드 함수

(1) pthreard_create
  • 쓰레드 생성을 위해 사용(pthread_t)
  • 첫번째 인자는 쓰레드 식별자
  • 세번째 인자는 쓰레드 실행시 호출되는 함수
  • 네번째 인자는 그 함수의 인자
  • 리턴값은 쓰레드 ID
  • 실행된 쓰레드에 대해서는 pthread_join 등의 함수를 이용해서 쓰레드 종료 때까지 기다려줘야 한다. pthread_join은 일종의 fork의 wait와 비슷하게 동작하며, 쓰레드 자원을 해제시켜 준다.
  • ex) thr_id = pthread_create(&p_thread[1], NULL, t_function, (void *)&a);
(2) pthread_join
  • 첫번째 인자는 기다릴(join)할 쓰레드 식별자
  • 두번째 인자는 쓰레드의 return값
  • 두번째 인자가 NULL이 아닐 경우 해당 포인터로 쓰레드 return값을 받아올 수 있다.
  • ex) pthread_join(p_thread, (void *)&status);

(3) pthread_detach
  • detach는 "떼어내다"라는 뜻을 가지며, main 쓰레드에서 pthread_create를 이용해 생성된 쓰레드를 분리시킨다.
  • 이 함수는 식별번호(첫번째 인자)인 쓰레드를 detach시키는데, detach 되었을 경우, 해당(detach)된 쓰레드가 종료될 경우 pthread_join을 호출하지 않더라도 즉시 모든 자원이 해제(free) 된다.
  • detach를 하지 않을 경우, 쓰레드 함수가 종료되더라도 자원이 해제되지 않는다.
  • pthread_create 호출 후 detach할 수 도 있고, pthread_create 호출시에 쓰레드가 detach 되도록 할 수도 있다.(pthread_attr_setdetachstate 참조)
  • ex) pthread_detach(p_thread);

(4) pthread_exit
  • 현재 실행 중인 쓰레드를 종료시키고자 할 때 사용한다.
  • pthread_cleanup_push가 정의되어 있다면, pthread_exit가 호출될 경우 cleanup handler가 호출된다. 보통 이 cleanup handler는 메모리를 정리하는 등의 일을 하게 된다.
  • ex) pthread_exit(0);

(5) pthread_cleanup_push
  • cleanup handler를 인스톨하기 위해서 사용된다.
  • pthread_exit가 호출되어서 쓰레드가 종료될 때 pthread_cleanup_push에 의해서 인스톨된 함수가 호출된다.
  • 첫번째 인자가 쓰레드가 종료될 때 호출되는 함수이다.
  • 두번째 인자는 이 때의 아규먼트이다.
  • cleanup handler는 주로 자원을 돌려주거나, mutex 잠금등의 해제를 위한 용도로 사용된다. 만약 mutex 영역에서 pthread_exit가 호출되어 버릴 경우, 다른 쓰레드에서 영원히 block 될 수 있기 때문이다.
  • 또, malloc 으로 할당받은 메모리, 열린 파일지정자를 닫기 위해서도 사용한다.
  • ex) phread_cleanup_push(cleanup, (void *)&a);

(6) pthread_cleanup_pop
  • pthread_cleanup_push와 함께 사용되며, cleanup handler를 제거하기 위해서 사용된다.
  • pthread_cleanup_push와 pthread_cleanup_pop은 반드시 같은 함수 내의 같은 레벨의 블럭에서 한 쌍으로 사용해야 한다.

(7) pthread_self
  • pthread_self를 호출하는 현재 쓰레드의 쓰레드 식별자를 리턴한다.
  • ex) ptread_t id;        id = pthread_self();

2. 쓰레드 동기화 함수

(1) pthread_mutex_init
(2) pthread_mutex_distroy
(3) pthread_mutex_lock
(4) pthread_mutex_unlock
(5) pthread_cond_init
(6) pthread_cond_signal
(7) pthread_cond_boradcast
(8) pthread_cond_wait
(9) pthread_cond_timewait
(10) pthread_cond_destroy
(11) 예제 코드

3. Thread Attribute 함수

(1) pthread_attr_init
(2) pthread_attr_distroy
(3) pthread_attr_getscope
(4) pthread_attr_setscope

4. 쓰레드 시그널 관련

(1) pthread_sigmask
(2) pthread_kill
(3) sigwait

5. 쓰레드 취소
(1) pthread_cancel
(2) pthread_setcancelstate
(3) pthread_getcancelstate



getch(), getchar(), getche() 가 필요한 이유

< getch(), getchar(), getche() 가 필요한 이유 >

* 공통 기능
  • 키보드로부터 문자 하나를 입력 받아 리턴한다.
  • 키보드에서 문자가 눌러질 때까지 무한대로 대기한다.
  • 키보드 입력시까지 대기시키는 목적으로 많이 사용된다.
* getch()
  • 에코 기능이 없다.
  • 입력받은 문자를 리턴한다.
  • 확장키 코드가 입력되었을 때는 0을 리턴한다. 이 경우 getch를 한번 더 호출하면 확장키 코드를 얻을 수 있다.
* getchar()
  • getch() 함수와 비슷하다. 유닉스에서 사용가능
* getche()
  • getch() 함수와 같되 에코 기능이 있다.

### int fflush(FILE * stream);
  • 스트림에 할당되어 있는 버퍼를 비운다.
  • 버퍼를 비운다는 의미는 입력, 출력에 따라 의미가 다르다.
    • 출력시 : 출력 대기중에 임시적으로 버퍼에 있던 데이터들이 파일에 기록된다.
    • 입력시 : 버퍼에 있던 데이터를 모두 지운다. 그리고 다시 새로운 데이터를 입력 받게 된다.
  • 버퍼를 비운다는 말은 버퍼에 들어있는 데이터를 없앤다는 의미이다.
  • fflush 실행 후에도 스트림은 여전히 개방된 상태로 남는다.
  • 성공시 0을 리턴, 에러 발생시 EOF 리턴

### linux에는 getch() 함수가 없기 때문에 함수를 만들어 사용해야 한다.

#include <termios.h>
#include <unistd.h>

int mygetch(void)
{
    struct termios oldt, newt;
    int ch;

    tcgetattr( STDIN_FILENO, &oldt );

    newt = oldt;
    newt.c_lflag &= ~( ICANON | ECHO );
    tcsetattr( STDIN_FILENO, TCSANOW, &newt );

    ch = getchar();
    tcsetattr( STDIN_FILENO, TCSANOW, &oldt );

    return ch;
}





2010년 4월 2일 금요일

NFS 설정

< Mini2440 NFS 설정 >

1. NSF setting
#get /etc/exports
/opt/FriendlyARM/MINI2440/root_qtopia *(rw,sync,no_root_squash)


2. firewall disable
# lokkit => disabled


3. start and stop nfs server
# /etc/init.d/nfs start
#mount -t nfs localhost:/opt/FriendlyARM/MINI2440/root_qtopia /mnt/
# /etc/init.d/nfs stop


3.1 in nfs server in gui
# serviceconf
 nfs => enable


4. booting system via nfs
 1) Nand flash로 모드 전환
 2) 전원 케이블, 시리얼 케이블 네트워크 케이블 연결
 3) 터미널 오픈
 4) 타겟 보드 power on
 5) vivi shell에서
   Supervivi> param set linux_cmd_line "console=ttySAC0 root=/dev/nfs nfsroot=192.168.1.70:192.168.1.111:192.168.1.111:255.255.255.0:MINI2440.arm9.net:eth0:off"

 6) vivi shell에서 boot 타이핑