안녕하세요, 타이젠 개발자 윤진입니다.
이번 그리고 다음 포스팅에 걸쳐 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://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
'IT > Tizen' 카테고리의 다른 글
[Tizen] 타이젠 개발, Player API로 소리 재생해보기 (0) | 2015.12.13 |
---|---|
[Tizen] 타이젠 개발, 디바이스의 파워 컨트롤하기 (2) | 2015.12.11 |
[Tizen] 타이젠 개발, "이제는 제발 헤매지 말자" SDK 인증실패 총정리 (2) | 2015.12.09 |
[Tizen] 타이젠 개발, 앱에서 타이젠 스토어에 링크걸기 (0) | 2015.12.07 |
[Tizen] 타이젠 개발, Audio Out으로 사운드 출력하기 (0) | 2015.12.05 |
[Tizen] 타이젠 개발, Tone player로 사운드재생하기 (2) | 2015.12.01 |
[Tizen] 타이젠 개발, Sound manager로 세션을 조절하기 (0) | 2015.11.29 |
[Tizen] 타이젠 개발, Sound manager로 볼륨을 조절하기 (30) | 2015.11.28 |
[Tizen] 타이젠 개발툴 sdb 살펴보기(Z1, Z3, Gear1, Gear2) (32) | 2015.11.25 |
[Tizen] 타이젠 개발 Z1, Z3 앱 라이프사이클의 이벤트 핸들링 (0) | 2015.11.23 |