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


지난 포스팅에서는 audio in에 대해다뤘었는데요,

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

이번에는 그에 대한 짝개념으로 Audio out을 다뤄보도록 하겠습니다.



audio out 계열의 함수도 audio in 계열의 함수와 유사한 점이 많습니다.

다만 in 계열이 읽어들이는 것에 초점을 맞추었다면,

out 계열은 출력하는 데에 초점이 맞춰져있습니다.

in에서 raw 데이터로 녹음을 하면 out에서 녹음된 raw 데이터를 재생합니다.

그 외에 전체적인 함수 사용법은 in이나 out이나 유사합니다.



audio out 계열 함수를 사용하려면 우선 디바이스 인스턴스를 생성해야 합니다.

audio_out_create() 함수로 디바이스 인스턴스를 생성할 수 있는데요,

sample rate, channel, type(depth)는 audio_in_create()에서도 동일하게 사용한 인수입니다.

하지만, 거기에 출력을 위해 sound_type이 추가로 들어갔습니다.



Sound manager의 session에서 사용하는 enum 값이 아닌,

Sound manager의 volume에서 사용하는 sound_type_e를 넣어주게 되어 있는데요,

audio_out 계열은 3rd party에서 사용하도록 공개된 Sound manager 의 session 보다,

System, Ringtone, Call, Voice와 같은 좀 더 넓은 선택지가 있어 보입니다.


하지만, 실제로 모든 세션을 사용할 수 있는 것은 아닙니다.

Sound manager에서 제공하는 sound_manager_set_session_type()에서 허용하는 세션으로만 설정하는 것이 맞아보입니다.



audio_out_ignore_session()은 audio_in_ignore_session()과 유사한 함수입니다.

본 함수는 audio_out으로 사운드 데이터를 출력할 때,

다른 세션의 output은 무시할 수 있도록 지정합니다.

- 곧, 현재 세션을 출력 중에 다른 세션을 시작해도 서로 무시하고,

- 다른 세션이 출력 중에 현재 세션을 출력해도 서로 무시하게 합니다.


하지만, 사운드 세션 관리는 본 함수를 사용하여 통제하는 것보다는,

Sound session manager에게 관리를 맡기는 것이 맞아 보입니다.


void play_file(char *file, int length, int ch)
{
    audio_out_h output;
    FILE* fp = fopen (file, "r");

    char * buf = malloc (length);

    printf ("start to play [%s][%d][%d]\n", file, length, ch);
    audio_out_create(44100, ch_table[ch] ,AUDIO_SAMPLE_TYPE_S16_LE, SOUND_TYPE_MEDIA, &output);
    if (fread (buf, 1, length, fp) != length) {
        printf ("error!!!!\n");
    }

    audio_out_prepare(output);
    audio_out_write(output, buf, length);
    audio_out_unprepare(output);

    audio_out_destroy (output);

    fclose (fp);

    printf ("play done\n");
}


전체적인 동작은 위의 루틴을 보면서 설명해보겠습니다.

- audio_out_create()로 sound_type까지 확정하여 디바이스 인스턴스를 생성합니다.

- audio_out_prepare()로 디바이스 버퍼링에 데이터를 쓸 준비를 합니다.

- audio_out_write()를 이용하여 데이터를 디바이스에 출력합니다.

- audio_out_unprepare()로 디바이스 버퍼링에 디바이스 출력을 멈춥니다.

- audio_out_destroy()로 생성된 디바이스 인스턴스를 정리합니다.

synchronous하게 동작하기 때문에 audio_out_write()에서 장시간 블록됩니다.


스레드 블록을 막고 싶으면 asynchronous하게 동작하는 함수를 사용하면 됩니다.

이 부분도 예제 코드를 보면서 설명하겠습니다.


int audio_io_async_test(int mode)
{
    char *buffer = NULL;
    FILE* fp_r = NULL;

    int ret, size;
    int i = 0;

    ret = audio_out_create(44100, AUDIO_CHANNEL_STEREO , AUDIO_SAMPLE_TYPE_S16_LE, SOUND_TYPE_MEDIA, &output);
    if (ret != AUDIO_IO_ERROR_NONE) {
        printf("audio_out_create failed.\n");
        return 0;
    }

    ret = audio_out_set_stream_cb(output, _audio_io_stream_write_cb, NULL);
    if (ret != AUDIO_IO_ERROR_NONE) {
        printf("audio_out_set_stream_cb failed.\n");
        audio_out_destroy(output);
        return 0;
    }

    fp_r = fopen( "/tmp/pcm.raw", "r");
    if (!fp_r) {
        audio_out_destroy(output);
        return 0;
    }

    ret = audio_out_prepare(output);
    if (ret != 0) {
        printf("audio_out_prepare failed.\n");
        audio_out_destroy(output);
        return 0;
    }

    for (i = 0; i < 10; i++) {
        printf ("-------- %d -------\n",i);
        usleep (1000000);
    }

    audio_out_unprepare(output);
    audio_out_destroy(output);

    fclose(fp_r);

    return 0;
}


위의 루틴으로 audio_out을 asynchronous하게 진행할 수 있습니다.

- 우선, 데이터 출력을 위해 audio_out_create()로 디바이스 인스턴스를 생성합니다.

- 그리고 audio_out_set_stream_cb()으로 콜백을 등록합니다. 콜백은 장치상황에 맞춰 불려집니다.

- audio_out_prepare()로 디바이스가 출력을 시작하게 됩니다.

- audio_out_unprepare()로 디바이스가 출력을 멈춥니다.

- audio_out_destroy()로 디바이스 인스턴스를 해제합니다.


static void _audio_io_stream_write_cb(audio_out_h handle, size_t nbytes, void *userdata)
{
    char *buffer = NULL;
    int ret = 0;

    if (nbytes <= 0) {
        printf ("Error!!!! %d", nbytes);
        return;
    }

    buffer = malloc(nbytes);
    memset(buffer, 0, nbytes);
   
    ret = fread(buffer, 1, nbytes, fp_r);
    if (ret != nbytes) {
        printf("Error!!!! %d/%d", ret, nbytes);
    }  
   
    ret = audio_out_write(handle, buffer, nbytes);
    if (ret > AUDIO_IO_ERROR_NONE) {
        printf("audio write success. buffer(%p), nbytes(%d)\n", buffer, nbytes);
    }
        audio_out_drain(handle);     free(buffer);
}


위의 루틴은 audio_out_set_stream_cb()으로 등록한 콜백함수입니다.

- 콜백함수가 불리면 fread()를 이용하여 파일에서 데이터를 읽어온 후,

- audio_out_write()로 디바이스에 해당 데이터를 출력하게 됩니다.

  이 함수는 write가 끝날 때까지 블록하게 됩니다.

- audio_out_drain()은 pcm 데이터가 디바이스로 완전히 나갈때까지 블록시킵니다.


이상으로 간단하게 audio out 계열 함수를 살펴보았습니다.

audio out 코드를 보며 추가로 파봐야할 포인트를 몇 개 찾았는데요,

공유할 만한 사실이 발견되면,

이 포스팅에 지속적으로 업데이트하도록 하겠습니다.


그럼 좋은 하루 보내세요~

끝_


* References

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

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


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


이번 그리고 다음 포스팅에 걸쳐 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

+ Recent posts