본문 바로가기

IT/Tizen

[Tizen] 타이젠 개발, Audio Out으로 사운드 출력하기


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


지난 포스팅에서는 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