4/10/2014

Audio-ALSA APP Interface (정리가 필요)

1. ALSA APP Interface 

ALSA의 Application에서 주로 사용하는 Interface는 다음과 같다.
  1. PCM interface:  PCM을 open하여 capture와 playback을 조절한다. 
  2. Timer interface: Timer를 제공하여, PCM Interface와 함게 event를 제공하다.
  3. Mixer interface:  Volume 조절 및 PCM의 설정 제공한다. 

Real-Time Audio Kernel ( Latency 관련부분)
  http://elinux.org/images/8/82/Elc2011_lorriaux.pdf
  http://free-electrons.com/doc/embedded_linux_audio.pdf


1.1 ALSA PCM Buffer 구성 및 설명

아래의 그림은 기본적인 2Ch인 Stereo 방식의 Buffer 구성이며 기본구성 



Buffer는 일반적으로 Stereo 로 사용할 경우 1 Frame: 2 CH * 2byte로 구성이 되지만, 각 PCM의 설정에 따라 변경된다. 
이  기본 Frame은 기본 한구성이 있는데 다음과 같다. 

  • frame_size (기본 2 Ch 설정)
ALSA의 기본단위는 Frame이며 아래와 같이 계산된다. 

    1 Frame :  Number of CH * Number of Sample 
    (Ex:  2CH * 1 Sample: 2 * 2byte = 4byte) 

그리고, period 로 frame을 묶어 주기적으로 Timer interrupt를 사용하여 전송하며, 이 전체사이즈를 buffer라고 한다. 
ALSA는 위와 같이 playback or capture를  위와 같이 buffer size 만큼의 delay 가 발생하며,즉 latency 발생된다.  

  • period_size 
주기적으로 Frame을 묶어 보내는 전송하는 Frame Size를 이 buffer의 크기를 조절하면 , PMIC와 같이 사용하는 AP에는 Power Consumption 영향을 미치며
Buffer를 크기를 크게하면, Power consumption 약간 증가한다 그래서 적절히 다운시켜 사용해야 하는 것으로 보인다. 

struct snd_pcm_hardware 구조체관련내용 
  http://egloos.zum.com/furmuwon/v/11008641
ALSA buffer overflow(XRUN)
  https://ffmpeg.org/pipermail/ffmpeg-user/2013-March/014090.html

  •  buffer_size 
편히 상, Buffer Frame이라고 부르겠으며, 상위 그림의 전체 Buffer size이다. Period Frame들이 모여서 한 Block이 된 Frame을 말한다.
설정은  buffer_size   = period_size x period_count, 주로 Application에서 사용한다.
이 크기는 playback 시 delay가 되는 latency를 의미한다.


1.2  Threshold 설정 과 XRun 

Threshold 설정은 주로 보면 Buffer Xrun을 조절하기 위해서 사용되어지며, 관련설정들의 의미를 정확하게 알아두자 

  • start_threshold
audio stream, data들이 들어왔을 경우, start point, 시작지점 설정을 말하며, underrun과 관련이 있다.
(The start threshold parameter is used to determine the start point in the stream.)

설정방법
  if available frames >= threadhold, it is possible to play or capture 
    i.e: period_count * period_size
    snd_pcm_sw_params_set_start_threshold

start threadhold 인 경우,이설정값이 지난 후에, 동작가능하다.


  • stop_threshold
overrun(buffer의 overflow)을 체크하기 위한 level이라고 생각하면 되며. 이 값을 넘으면,자동적으로 stop이 된다. 
(the stop threshold parameter is used to automatically stop the running stream when the available samples crosses this boundary for checking underrun or overun.)

설정방법
  if available frames >= threadhold , pcm is automatically stoping the running system.
          for playback, capture threadhold > buffer size with xrun
          for playback, capture threadhold = buffer size without xrun
          
          if (avail >= runtime->stop_threshold)  //pcm_lib.c 
           xrun(substream);
           
    i.e: period_count * period_size   snd_pcm_sw_params_set_stop_threshold
start or stop threshold를 보면 start point와 stop point를 정확히 설정하는 것이다.
threshold라는 것이 문턱 or 설정한 level이라고 생각하면되겠다.

이는 xrun과 밀접한 관련이 있으며 audio가 주로 멈추는 현상은 이 xrun 현상이며, 
이부분은 period_size 과 stop_Threadhold 값을 잘 조절해서 맞추어 가며 재 설정해 간다


  • silence threshold
playback 시 미리 silent sample을 채워 이를 재생을 하는 것이다.
(sample's number filled with silence ahead of the current application pointer for playback.)
i.e: 0 
  snd_pcm_sw_params_set_silence_threshold

  • XRUN 
overrun 과 underrun을 말하며, 두 device의 속도차이로 인하여 buffer empty or overflow를 말한다.
이는 두 device간의 read 와 write 간의 속도차로 발생하여, buffer 부족하면, underrun 이라고 하고,
buffer가 넘치면 이를 overrun이라고 한다.

자주 발생하는 XRUN 현상
  1. playback일 경우, empty buffer로 인하여, underrun 발생 하여 재생불능 상태 발생
  2. capture 일 경우, buffer가 데이타 넘쳐 overrun 발생하여, 더이상 녹음 불능 발생  

XRUN Debug
  http://www.alsa-project.org/main/index.php/XRUN_Debug

  • Sound buffers and Data Transfer 
ALSA의 buffer는 Application buffer와 Hardware buffer가 있으며, 이곳에서는 Hardware buffer의 구성은 제외하겠다. Application Buffer Size 는 buffer_size이며, 이는


  • 기본예제 및 Buffer의 기본개념 XRUN 설명
  http://www.linuxjournal.com/node/6735/print
  http://www.alsa-project.org/main/index.php/FramesPeriods


1.3  Kernel PCM Driver 관련설정 

  • Kernel driver에서 PCM에 관한 설정 
Kernel Driver에서 제공되는 기능으로  Driver에서 Audio의 기능을 설정을 하여, Application의 설정을 제한을 한다.

$ vi include/sound/pcm.h      //   DM368 
struct snd_pcm_hardware {
        unsigned int info;              /* SNDRV_PCM_INFO_* */
        u64 formats;                    /* SNDRV_PCM_FMTBIT_* */
        unsigned int rates;             /* SNDRV_PCM_RATE_* */
        unsigned int rate_min;          /* min rate */
        unsigned int rate_max;          /* max rate */
        unsigned int channels_min;      /* min channels */
        unsigned int channels_max;      /* max channels */
        size_t buffer_bytes_max;        /* max buffer size */
        size_t period_bytes_min;        /* min period size */
        size_t period_bytes_max;        /* max period size */
        unsigned int periods_min;       /* min # of periods */
        unsigned int periods_max;       /* max # of periods */
        size_t fifo_size;               /* fifo size in bytes */
};

$ vi ./sound/soc/davinci/davinci-pcm.c  //DM368 HW Setting . PCM Driver 

static struct snd_pcm_hardware pcm_hardware_capture = {
        .info = (SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER |
                 SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID |
                 SNDRV_PCM_INFO_PAUSE),
        .formats = (SNDRV_PCM_FMTBIT_S16_LE),
        .rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |
                  SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_32000 |
                  SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 |
                  SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000 |
                  SNDRV_PCM_RATE_KNOT),
        .rate_min = 8000,
        .rate_max = 96000,
        .channels_min = 2,
        .channels_max = 2,
        .buffer_bytes_max = 128 * 1024,     // max buffer_size  =128 x 1024 = 131072
        .period_bytes_min = 32,               // min  period_size = 32 
        .period_bytes_max = 8 * 1024,       // max period_size  = 8x 1024 =  8192
        .periods_min = 16,                       //  min period_count 
        .periods_max = 255,                    //   max period_count
        .fifo_size = 0,
};
static struct snd_pcm_hardware pcm_hardware_playback = {
        .info = (SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER |
                 SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID |
                 SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME),
        .formats = (SNDRV_PCM_FMTBIT_S16_LE),
        .rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |
                  SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_32000 |
                  SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 |
                  SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000 |
                  SNDRV_PCM_RATE_KNOT),
        .rate_min = 8000,
        .rate_max = 96000,
        .channels_min = 2,
        .channels_max = 2,
        .buffer_bytes_max = 128 * 1024,
        .period_bytes_min = 32,
        .period_bytes_max = 8 * 1024,
        .periods_min = 16,
        .periods_max = 255,
        .fifo_size = 0,
};  

  http://www.alsa-project.org/~tiwai/writing-an-alsa-driver/ch05s05.html#pcm-interface-runtime-hw


  • HW/SW Params 확인 
관련부분들 확인 
$ vi ./include/sound/pcm.h
struct snd_pcm_runtime {
        /* -- Status -- */
        struct snd_pcm_substream *trigger_master;
        struct timespec trigger_tstamp; /* trigger timestamp */
        int overrange;
        snd_pcm_uframes_t avail_max;
        snd_pcm_uframes_t hw_ptr_base;  /* Position at buffer restart */
        snd_pcm_uframes_t hw_ptr_interrupt; /* Position at interrupt time */
        unsigned long hw_ptr_jiffies;   /* Time when hw_ptr is updated */
        unsigned long hw_ptr_buffer_jiffies; /* buffer time in jiffies */
        snd_pcm_sframes_t delay;        /* extra delay; typically FIFO size */

        /* -- HW params -- */
        snd_pcm_access_t access;        /* access mode */
        snd_pcm_format_t format;        /* SNDRV_PCM_FORMAT_* */
        snd_pcm_subformat_t subformat;  /* subformat */
        unsigned int rate;              /* rate in Hz */
        unsigned int channels;          /* channels */
        snd_pcm_uframes_t period_size;  /* period size */
        unsigned int periods;           /* periods */
        snd_pcm_uframes_t buffer_size;  /* buffer size */
        snd_pcm_uframes_t min_align;    /* Min alignment for the format */
        size_t byte_align;
        unsigned int frame_bits;
        unsigned int sample_bits;
        unsigned int info;
        unsigned int rate_num;
        unsigned int rate_den;

        /* -- SW params -- */
        int tstamp_mode;                /* mmap timestamp is updated */
        unsigned int period_step;
        snd_pcm_uframes_t start_threshold;
        snd_pcm_uframes_t stop_threshold;
        snd_pcm_uframes_t silence_threshold; /* Silence filling happens when
                                                noise is nearest than this */
        snd_pcm_uframes_t silence_size; /* Silence filling size */
        snd_pcm_uframes_t boundary;     /* pointers wrap point */

        snd_pcm_uframes_t silence_start; /* starting pointer to silence area */
        snd_pcm_uframes_t silence_filled; /* size filled with silence */

        union snd_pcm_sync_id sync;     /* hardware synchronization ID */
        ......


2. Application 의 설정속성 

Linux Kernel에서 ALSA관련 Device Interface를 제공하고, App은 이것을 이용을 DATA전송및 Control을 하게되고, Timer를 이용한다.
하지만 Embedded에서는 이 모든 기능을 다 제공하지는 않는다.
대부분 Control과 PCM과 Timer를 제공하고 다른 기능은 거의 제공하지는 않는다.

  • Control Interface (/dev/snd/controlCX)
  • Mixer Interface (/dev/snd/mixerCXDX)
  • PCM Interface (/dev/snd/pcmCXDX)
  • Raw MIDI Interface (/dev/snd/midiCXDX)
  • Sequencer Interface (/dev/snd/seq)
  • Timer Interface (/dev/snd/timer)
관련내용참조 
  http://www.alsa-project.org/alsa-doc/alsa-lib/
  http://www.alsa-project.org/main/index.php/ALSA_Library_API


2.1 PCM Device Interface

Application 에서 주요하게 다루는 Interface는 PCM이다.  (/dev/snd/pcmCxDx)
PCM Interface를 이용하여, Audio Data를 전송하여 Play하고 Capture를 하며, Block Mode와 Non Block Mode 및 XRUN 감지 및 parameter 관리등을 다룬다.

아래는 TI의  PCM device file을 보여주며, 모든 ALSA의 기능을 제공해주지는 않는다.
Embedded 에서는 아래와 같이 기본기능만을 사용한다.

  • 실제의 사용되어지는 예 
Caputre/Playback/Timer/Control 만 사용되며, App에서는 이를 이용하여 ALSA를 이용 

$ ls /dev/snd/
controlC0  pcmC0D0c   pcmC0D0p   timer

관련내용 
  http://www.alsa-project.org/alsa-doc/alsa-lib/pcm.html


2.2 Timer Interface

Timer는 ALSA를 NonBlock 모드사용할 경우 주로 사용하는 것 같다.
  http://www.alsa-project.org/alsa-doc/alsa-lib/timer.html
  http://www.alsa-project.org/alsa-doc/alsa-lib/_2test_2timer_8c-example.html#a1


2.3  HW/SW Parameters 

Kernel에서 Parameter의 기능의 제한이 되고, Application에서 이 설정의 변경이 가능하다.
이 설정을 변경을 함으로써, 성능 및 XRUN 문제의 오류를 방지하고 변경하자.

  .channels                  =  mono or stero 으로 설정 (2 or 1)
  .rate                      =  Sample rate 값 설정
  .period_size               =  P-Frame으로 설정.   (unit: frame)
  .period_count              =  P-Frame Count  
  .format                    =  PCM_FORMAT_S16_LE or PCM_FORMAT_S32_LE , 다른 값.
  .start_threshold           =  minum ,
  .silence_threshold         =  0
  .stop_threshold            =  > buffer size


default
    start_threshold   : period_count * period_size, default setting values 
    stop_threshold    : period_count * period_size, default setting values (XRUN control)
    silence_threshold : 0
   
   frame_size                =  num of channel * a sample        (i.e 2* 1 sample = 4 byte)
   buffer_size               =  period_size * period_count       (unit: frame)       
   period_bytes              =  period_size * bytes_per_frame    (unit: frame)
   bytes_per_frame           =  channels * bytes_per_sample      (unit: byte)    (4byte ,Stereo-2Ch)) 

http://www.alsa-project.org/alsa-doc/alsa-lib/_2test_2pcm_min_8c-example.html#a6


  • period_size 
주기적으로 한 Frame 크기로 데이타를 전송하는 단위를 말하며, Interrupt와 밀접한 연관이 있으며, 이 buffer를 조절하면 , 
PMIC와 같이 사용하는 것에는 Power Consumption 영향을 미치며. 크기를 크게하면, Power consumption 약간 증가한다.
그래서 적절히 다운시켜 사용해야 하는 것 같다.

아래는 Period Size를 구하는 간단한 예제이다, Interrupt Time을 아래와 같이 설정하고
     period_size  = Sample rate  x ( Interrupt Time) , unit : frame
     5292            =   44100 x (P Time 120/1000, interrupt time)  
     5760            =   48000 x (P Time 120/1000, interrupt time)
                                   
                         (ABE, Only 48KHz used. if used 44.1KHz resampling by Speex )
                          참고로,  48KHz로만 사용할것이다, 44.1KHz는 Resample이 필요할 것이다.

      1056        = 48000 x  (22/1000)


    아래의 Link는 byte단위로 계산을 했지만, 위 계산은 Frame 단위이다. 
  

  •  buffer_size
편히 상, Buffer Frame이라고 부르며, Period Frame들이 모여서 한 Block이 된 Frame을 말한다. 
설정은  buffer_size   = period_size x period_count, 주로 Application에서 사용한다.

  1.   B Time: P Time *  period_count= Latency

                    B Frame이 소비되는 시간을 말하며, 정확하게는 Latency.
                                         
                   ** Latency: Buffer Time

아래는 tinyalsa lib의 기본소스
    https://github.com/tinyalsa/tinyalsa


3. ALSA  기타 설정 항목 설명 (추후 삭제) 
  
boundary
sw_param 1937768448
avail
runtime->status->hw_ptr    : the number of frame that kernel write. 
runtime->control->appl_ptr : the number of frame that application write,  x4 ( 2byt and 2xchannel)
Playback (pcm_mmap_playback_avail/ pcm.c application)
avail = pcm->mmap_status->hw_ptr + pcm->buffer_size - pcm->mmap_control->appl_ptr;
 
Capture  (pcm_mmap_capture_avail)
int avail = pcm->mmap_status->hw_ptr - pcm->mmap_control->appl_ptr;
start threadhold
The start threshold parameter is used to determine the start point in stream.
sample's number     if available frames >= threadhold, it is possible to play or capture 
i.e: period_count * period_size
snd_pcm_sw_params_set_start_threshold


stop threadhold
the stop threshold parameter is used to automatically stop the running stream when the available samples crosses this boundary.
for checking underrun or overun.
sample's number   if available frames >= threadhold , pcm is automatically stoping the running system.
 for playback, capture threadhold > buffer size with xrun
 for playback, capture threadhold = buffer size without xrun
 
 if (avail >= runtime->stop_threshold)  //pcm_lib.c 
xrun(substream);
i.e: period_count * period_size
snd_pcm_sw_params_set_stop_threshold
  • Android HAL


  • ALSA 기본 예제