본문 바로가기

IT/Tizen

[Tizen] 타이젠 개발, Audio Input으로 녹음하기


안녕하세요, 타이젠 개발자 윤진입니다.


이번 그리고 다음 포스팅에 걸쳐 Audio I/O에 관해 다룰 예정입니다.

Audio I/O는 사운드를 raw 데이터(PCM)로 녹음하거나 재생할 때 사용합니다.

raw 데이터(PCM)란 압축하거나 다른 형태로 인코딩하지 않은 데이터를 의미하지요.



Audio input과 관련된 18개의 API가 있는데요,

위의 API를 사용하기 위해서는 microphone feature를 추가해주어야 합니다.

http://tizen.org/feature/microphone

앱의 manifest 파일에 위의 feature를 추가해주세요.

(참고 : https://developer.tizen.org/development/tools/native-tools/manifest-text-editor#feature)



Audio를 녹음하기 위해서는 우선 device instance를 만들어야 합니다.

device instance는 audio_in_create()으로 만들 수 있지요.

위의 함수를 사용하기 위해서는 privilege가 필요하니 manifest에서 권한도 추가해주셔야 돼요.

http://tizen.org/privilege/recorder


인자는 총 네 개로 세 개는 in-parameter이고 마지막 하나는 out-parameter입니다.

(타이젠은 기본적으로 out-parameter를 활용하여 인스턴스를 넘겨주는 패턴을 사용합니다.) 

sample rate, channel, type(depth)을 in-parameter로 넘겨주면,

out-parameter로 input 값이 넘어오는데 이 input 값으로 audio_in 함수 전체를 통제하게 됩니다.

input 값으로 audio_in 계열의 함수를 사용한 후,

audio_in_destroy()로 메모리를 정리해주면 됩니다.


참고로 audio_in_create_loopback()은 출력장치로 나가는 소리를 캡쳐하는 기능입니다.

- 스피커로 나가는 출력데이터를 audio_in_create_loopback()로 가져와서,

- audio_out_create()로 나가는 루틴에 대입하여 echo를 제거할 때 사용할 수 있습니다.

하지만, 이 함수를 사용하는 것은 좀 더 따져봐야할 것으로 보입니다.

Tizen의 다음 버전에서 이 기능을 지원할지는 확실치가 않아서요.

(참고로 이 부분은 멀티미디어 전문가인 '신' 책임연구원님(실명언급해도 될까요?)의 도움을 받았습니다)



audio_in_create() 직후에 audio_in_ignore_session()을 사용할 수 있습니다.

함수 설명을 보면,

"Ignores session for input"이라고 되어 있는데요(너무 단촐하네요).

다른 세션의 동작과 상관없이 녹음을 진행하기 위해 공개된 API입니다.

- 곧, 녹음을 시작할 때 타 세션을 interrupt 하지 않고,

- 타 세션이 시작하더라도 interrupt 받지 않습니다.

하지만, 기본적으로 세션관리는 Sound session manager에서 하고 있습니다.

따라서 ignore session을 이용하여 인위적으로 조절하지 않는게 좋겠네요.


int audio_io_test(int length, int num, int ch)
{
    int ret, size, i;
    audio_in_h input;
    if ((ret = audio_in_create(44100, ch_table[ch] ,AUDIO_SAMPLE_TYPE_S16_LE, &input)) == AUDIO_IO_ERROR_NONE) {
        ret = audio_in_ignore_session(input);
        if (ret != 0) {
            printf ("ERROR, set session mix\n");
            audio_in_destroy(input);
            return 0;
        }

        ret = audio_in_prepare(input);
        if (ret != 0) {
            printf ("ERROR, prepare\n");
            audio_in_destroy(input);
            return 0;
        }

        FILE* fp = fopen (DUMP_FILE, "wb+");

        if ((ret = audio_in_get_buffer_size(input, &size)) == AUDIO_IO_ERROR_NONE) {
            size = length;
            char *buffer = alloca(size);

            for (i=0; i<num; i++) {
                printf ("### loop = %d ============== \n", i);
                if ((ret = audio_in_read(input, (void*)buffer, size)) > AUDIO_IO_ERROR_NONE) {
                    fwrite (buffer, size, sizeof(char), fp);
                    printf ("PASS, size=%d, ret=%d\n", size, ret);
                }
                else {
                    printf ("FAIL, size=%d, ret=%d\n", size, ret);
                }
            }
        }

        fclose (fp);

        audio_in_destroy(input);
    }

    play_file (DUMP_FILE, length*num, ch);

    return 1;
}


위의 함수를 통해 audio_in 루틴을 전체적으로 파악할 수 있습니다.

- audio_in_create()로 디바이스 인스턴스를 하나 만들고,

- audio_in_ignore_session()으로 타 세션이 방해하지 못하도록 설정하고,

- audio_in_prepare()로 장치에서 버퍼링을 시작합니다.

- audio_in_get_buffer_size()로 시스템에서 넘겨주는 (권장) 버퍼 사이즈를 확인합니다.

  여기 있는 버퍼사이즈를 확인하여 audio_in_read()에서 버퍼를 사용합니다.

- audio_in_read()로 버퍼에 있는 데이터를 읽어 옵니다.

  synchronous하게 동작하는 API이기 때문에 데이터를 읽는 동안 블록됩니다.

- audio_in_destroy()로 최종적으로 디바이스 인스턴스를 정리합니다.


synchronous하게 동작하는 루틴이 보았다면,

이번에는 asynchronous하게 동작하는 루틴을 볼 차례입니다.


int audio_io_async_test(void)
{
    char *buffer = NULL;
    audio_in_h input;
    FILE* fp_w = NULL;
    int ret, size;
    int i = 0;
   
    ret = audio_in_create(44100, AUDIO_CHANNEL_STEREO , AUDIO_SAMPLE_TYPE_S16_LE, &input);
    if(ret != AUDIO_IO_ERROR_NONE) {
        printf ("audio_in_create_ex failed. \n");
        return 0;
    }
   
    ret = audio_in_set_stream_cb(input, _audio_io_stream_read_cb, NULL);
    if(ret != AUDIO_IO_ERROR_NONE) {
        printf ("audio_in_set_stream_cb failed. \n");
        return 0;
    }
   
    fp_w = fopen("/tmp/pcm_w.raw", "w");
    if (!fp_w) {
        printf ("cannot open a file\n");
        audio_in_destroy(input);
        return 0;
    }
   
    ret = audio_in_prepare(input);
    if (ret != 0) {
        printf ("audio_in_prepare failed.\n");
        audio_in_destroy(input);
        return 0;
    } else {
        ret = audio_in_get_buffer_size(input, &size);
        if(ret != AUDIO_IO_ERROR_NONE) {
            printf ("audio_in_get_buffer_size failed.\n");
            return 0;
        }
        else {
            printf("size(%d)\n", size);
            buffer = alloca(size);
        }
    }
   
    if(buffer == NULL) {
        printf("buffer is null\n");
        return 0;
    }
   
    printf ("loop start\n");
    for (i = 0; i < 10; i++) {
        printf ("-------- %d -------\n",i);
        usleep (1000000);
    }
   
    printf ("audio_in_unprepare\n");
    audio_in_unprepare(input);
    printf ("audio_in_destroy\n");
    audio_in_destroy(input);
   
    fclose(fp_w);
   
    return 0;
}


stream과 관련된 루틴에서는 asynchronous하게 동작하도록 콜백함수를 만들어 줘야하는데요,

콜백함수를 등록하는 루틴을 보고 콜백함수 자체를 살펴보겠습니다.

- audio_in_create()로 다비이스 인스턴스를 하나 만들어둡니다.

- audio_in_set_stream_cb()으로 asynchronous하게 이벤트를 받아 처리할 함수를 등록합니다.

- audio_in_prepare()로 디바이스에서 버퍼링을 시작하고,

- audio_in_unprepare()로 녹음을 중지합니다.

- audio_in_destroy()로 디바이스 인스턴스로 할당받은 메모리를 정리합니다.


자, 이번에는 콜백함수의 루틴을 살펴보도록 하겠습니다.


static void _audio_io_stream_read_cb (audio_in_h handle, size_t nbytes, void *userdata)
{
    const void * buffer = NULL;
       
    if (nbytes > 0) {
        audio_in_peek(handle, &buffer, &nbytes);
        if (fp_w) {
            fwrite(buffer, sizeof(char), nbytes, fp_w);
        }
        audio_in_drop (handle);
    }
}


콜백함수에서는 audio_in_peek()와 audio_in_drop() 함수를 사용할 수 있습니다.

위의 함수는 오직 콜백함수 내에서만 동작하지요.

audio_in_peek()로 버퍼링된 데이터를 buffer 인자로 받아와서 사용할 수 있습니다.

여기서는 버퍼를 fwrite를 사용하여 파일에 기록하였습니다.

그리고 나서 audio_in_drop()으로 버퍼를 정리해주었지요.


이상과 같이 audio_in 계열의 함수를 살펴보았습니다.

audio_in과 같이 raw 데이터를 다루는 함수는 기본지식이 없으면 다루기 힘듭니다.

이 기회에 학부때 중간고사용으로 공부하고 바로 까먹은 것들을 들춰봐야겠습니다.

다음 포스팅에서는 audio_out 계열의 함수를 훑어볼께요~


그럼 좋은 하루 보내세요~

끝_


* References

https://developer.tizen.org/dev-guide/2.4.0/org.tizen.native.mobile.apireference/group__CAPI__MEDIA__AUDIO__IN__MODULE.html

https://en.wikipedia.org/wiki/Pulse-code_modulation

https://en.wikipedia.org/wiki/Echo_suppression_and_cancellation

https://developer.tizen.org/development/tools/native-tools/manifest-text-editor#feature

git://review.tizen.org/framework/api/audio-io