(꼼수 가득한) 생애 최초 앱 개발기

Hyunsoo Lim·2022년 6월 15일
2

앱 개발이 모죠?

때는 2021년 6월.

코틀린으로 안드로이드 어플 개발을 하라는, 아니 정확히는 개발 중이던 걸 이어 받아 프로젝트를 끝내라는 특명이 떨어졌다.

연유는 이랬다.

사용자와 상담을 진행하는 어플과 관리 페이지를 만드는 프로젝트인데, 앱 개발 및 PM을 담당하시던 분이 회사 사정상 파견을 나가게 된 것.

시작하기 전부터 여러가지 문제가 있었는데,

일단 담당하시던 분이 애초에 앱 개발자가 아니었고, 거의 다 끝낸 걸 이어 받아 화면만 좀 만지면 된다고 전해들었는데 받아서 까보니 진척률이 20~30% 정도 밖에 되질 않았고, 마감은 얼마 남지 않았고, (으어...)

더 큰 문제는 내가 자바도, 코틀린도, 심지어 외길 아이폰 인생이라 안드로이드도 전혀 모르는 인간이었단 거..

하지만 이 참에 새로운 거 공부한다 생각하고 열심히 해보겠다고 했다. 까라면 까야지

오히려 좋아. 가보자고!

에러가 터진다

거창하게 사용자와 상담이라 했지만 결국 사용자의 대답에 따라 분기가 나뉘는, 상담 시나리오를 읽어주는 앱을 개발하는 게 목표였다.

단순히 TTS(Text-To-Speech)와 STT(Speech-To-Text)를 순환시키면 될 것 같지만, 몇 가지 추가 및 제약 사항 하에 목표를 이뤄야 했다.

  1. 특정 라이브러리 - 모 통신사의 TTS, STT 라이브러리 - 를 이용해야 함.
  2. 사용자의 응답을 녹음해야 함.
  3. 영상 녹화를 해야 함.

해당 라이브러리 기본 동작 방식은 마치 아이폰의 Siri처럼 a)사용자의 요청 b)질문(라이브러리) c)답변(사용자)... 이렇게 a), b), c)가 순서대로 일어나는 것이었다.

우리는 원래 작동하게 설계된 a-b-c 방식이 아니라 b)라이브러리의 질문으로 시작을 하고, c)사용자의 답변 뒤에, b)재질문이 나올 수도 있고, b')상담 내용이 들어갈 수도 있고, 그 둘이 콤보(b+b')가 될 수도 있는 등 시작 방식도 다르고, 대화의 분기도 '상담'의 느낌이 나게 커스텀을 해야만 했다.

모 통신사의 담당자에게 문의했지만 우리가 원하는 방식대로 쓸 수 없다는 답변을 얻은 상황. 하지만 앞서 앱 개발을 시작한 분이 어떻게든 꼼수를 찾아 b 방식으로 동작을 시작하는 데까지 뼈대가 갖춰져 있었다.

그 뼈대 위에서 어찌어찌 1,2,3이 되긴 되게끔 개발을 1차 마무리 했으나...시나리오를 실행하다보면 에러가 랜덤으로 터지곤 했다.

눈 빠지는 디버깅 끝에 에러 조건 하나 발견해서(했다고 생각해서) 처리하니까 괜찮았던 저쪽에서 에러 터지고, 저쪽 막으니까 저짝 터지고, 저짝 막으니까, 이짝 요짝 죠짝 다 터지ㄴ다...ㅠㅠ

에러가 터진다~ (샤라랄라 랄랄라~)

정말이지 걷잡을 수가 없었다.

결국 데모용 제품을 납품까지 한 상황에서 기존 설계를 싹 갈아엎기로 결정했다.

꼼수 1. STT - TTS 분리

에러의 근본 원인은 모 통신사의 TTS와 STT가 따로 동작하지 않고 한 몸처럼 동작하는 데 있었다.

STT와 TTS 사이엔 텍스트를 인식(판단)하는 과정도 녹아져있는데, 그 모든 과정이 라이브러리 내부의 라이프사이클로 취급되었고, 그 사이에 어거지로 끼어든 게 문제가 되었던 것이다.

예를 들어 정해둔 대답(텍스트)에 따라 강제로 분기를 타게 만들었는데, STT를 못 했을 때도 혹은 잘 했을 때도, 라이브러리에 내장된 '잘 모르겠슴돠?' 가 갑자기 튀어나오거나, 앱이 갑자기 프리징되는 식이었다. (제일 무서운 건 그 모두가 랜덤이었다..)

동시성이 문제인가 싶어 코루틴이란 것도 급하게 찾아보고 며칠동안 별의별 걸 다 시도해보다가, 문득 이런 생각이 들었다.

모 통신사의 라이브러리란 티가 나는 "기계 음성이 들리는" TTS는 그대로 쓰고, STT는 안드로이드에 내장된 걸 쓰게끔 분리시키면 되지 않을까? 그러면 1을 그대로 만족시키는 척 하며 에러의 근본 원인을 없앨 수 있을 거 같은데?

안드로이드 내장 라이브러리!

이쯤 고생하고 나니 안드로이드 STT 구현 자체는 어렵지 않은 수준이 되어, 빠르게 테스트를 해보니 사용자 음성 인식 정확도도 훨씬 좋아졌다.

부푼 마음을 안고 통신사 TTS와 합치는데...

단독 테스트 땐 잘 되던 STT에서 음성 인식 자체가 되질 않는다ㅠㅠ

왜 되던 게 안되니..ㅠㅠ

꼼수 2. 분리한 TTS 온앤오프

충돌이 나면 분리가 답이라 생각해 둘 사이의 액티비티를 나눠봤는데 안 된다.

아무리봐도 모 통신사의 라이브러리가 음성 인식용 마이크를 선점해 다른 라이브러리에서 마이크를 못 쓰게 된 것 같았다.

그러면 어차피 STT - TTS 둘을 분리한 거, 하나가 동작할 때 나머지를 아예 꺼버리면 되지 않을까?

백그라운드에서 돌리다 꺼버리자!

찾아보니 Activity와는 다른 성격인 service 라는 녀석이 있었고, 원하는 대로 동작시킬 수 있을 것 같았다.
검색과 테스트를 거듭하여 모 통신사의 TTS를 서비스로 구현해 안드로이드 STT와 합쳤더니...

된다!!!

시나리오 끝까지 단 한 번의 에러도 없이 TTS와 STT가 서로 내용을 주고 받으며 진행이 이뤄졌다. 이전과 달리 어쩌다 한 번이 아니라, 반복적으로!

  1. 특정 라이브러리 - 모 통신사의 TTS, STT 라이브러리 - 를 이용해야 함.
  2. 사용자의 응답을 녹음해야 함.
  3. 영상 녹화를 해야 함.

    나 녀석 잘했어!

가장 중요한 1번을 해결했으니, 이제 원래 잘 되던 음성 녹음 기능만 다시 넣기만 하면 되...는...데...
녹음 기능을 추가하자마자 귀신 같이 또 STT에서 음성 인식이 안 된다.

왜 되던 게 안되니 2..ㅠㅠ

꼼수 3. 구글 클라우드 STT로 교체

음성인식이 안 되는 걸 보면, 역시나 마이크가 문제인 거 같다.

하지만 이상한 게 a)모 통신사 라이브러리에서 녹음을 할 땐 문제가 없었는데, b)안드로이드 STT 와 함께 쓰니 문제가 난다는 것.

a)의 이유를 찾아 b)에 적용하면 될 것 같은데, 도저히 이유를 못 찾겠다. 결국 다른 해결책을 찾아 헤매고 또 헤매야했다.

오랜 구글링 끝에 안드로이드 STT와 음성녹음을 동시에 할 수는 없다는 답과(이유는 못 찾음), 구글 클라우드 API의 STT를 쓰면 가능할 거란 답을 동시에 얻을 수 있었다.

구글 클라우드 STT!

구글에서 제공하는 샘플 코드를 보니 STT와 녹음이 동시에 가능할 것 같았다.
바로 구글 클라우드 API 계정을 만들어 위의 샘플 앱을 실행해보니, STT와 음성 녹음이 동시에 된다!!

이제 구글 클라우드 Speech 샘플코드와 앱을 합치기만 하면 되는데...
기존 라이브러리와 충돌이...ㅠㅠ

...ㅠㅠ

눈 빠질듯한 디버깅 끝에 충돌 나는 라이브러리를 찾아 주석 달고 설치를 했더니, 이번엔 라이브러리는 다 설치가 되는데 앱 구현에서 필요로 하는 클래스 하나가 설치가 안 된다...

샘플앱의 라이브러리를 이식하려고 했더니 샘플앱은 gradle이 아니라 proto라는 폴더에 위치한 protobuf 형식의 파일을 이용해 필수 라이브러리를 설치하는 거 같은데..

...

꼼수 4. 앱을 분리한 뒤에 데이터만 합해볼까?

또 궁리에 궁리를 하다하다 떠오른 아이디어.

앱 내부에서 STT-TTS를 분리해 재미를 봤는데, 이번엔 문제 생기는 파트를 아예 다른 앱으로 분리하고 앱끼리 연결하면 안 될까?

다 분리햇!

찾아보니 앱과 앱끼리 연결하는 AIDL이란 게 있다고 해 앱 A와 앱 B가 데이터를 주고 받는 샘플을 구현해봤는데 그건 성공!

그런데 STT 앱을 따로 만들어놓고 보니 일반적인 데이터가 아닌, 음성 인풋을 기존 앱(앱 A)에서 STT 앱(앱 B)에 전달하는 방법이 떠오르질 않는다ㅠㅠ

안 될 걸 진작 알았어야지..ㅠㅠ

꼼수 5. 접근성 서비스!!!

결국 근본 원인을 파악해야할 것 같아 다시금 검색에 검색을 거듭하다 공식 페이지에서 원인을 찾았다.

두 개 이상의 앱이 동시에 오디오를 캡처하기를 원하는 경우, 동일한 소스에서 오디오 신호를 모든 앱으로 전달하는 데 문제가 발생할 수 있습니다.

역시 예상한대로 오디오 선점 문제인 것 같다.

해결책에 대한 힌트도 있다.(지금보니 내가 찾았던 당시보다 설명이 훨씬 상세하고 명료해졌다. 당시엔 읽어봐도 무슨 말인지 알기 어려운 문장이었고 내용도 짧았는데..)

이 규칙에는 한 가지 예외가 있는데, 권한이 있는 앱(에: Google 어시스턴트 또는 접근성 서비스)이 android.permission.CAPTURE_AUDIO_HOTWORD 권한을 가지고 오디오 소스 유형 HOTWORD를 사용하는 경우입니다. 이 경우에는 다른 앱이 녹음을 시작할 수 있습니다.

이에 따르면 STT를 접근성 서비스란 걸로 구현하고, 음성 녹음을 하면 되지 않을까?

접근성 서비스!!

권한을 요청하는 과정도 넣어야하긴 했지만, 오~!!!! 된다. 2까지 확실히 클리어!

  1. 특정 라이브러리 - 모 통신사의 TTS, STT 라이브러리 - 를 이용해야 함.
  2. 사용자의 응답을 녹음해야 함.
  3. 영상 녹화를 해야 함.

됐다 됐어!!

꼼수 6. 아예 폰을 떠나 생각하기

영상 녹화 역시 1차 개발 완료 시점엔 동작을 잘 했지만, 현 시점엔 많은 게 바뀌어 제대로 동작하지 않았다.

일단 스펙이 후면 카메라 녹화에서 전면 카메라 녹화로 바뀌었는데, 심플하게 구현할 수 있었던 camera 클래스가 아니라 camera2 클래스를 사용해야만 한다는 의미가 되었다.

camera2는 카메라 렌즈 뿐 아니라 surfaceView와도 연관이 있어 UI와 연결이 되어 있어야 했다. 그런데 앞선 문제들을 해결하기 위해 액티비티(페이지로 생각하면 될 것 같다)와 프래그먼트를 여러 개로 더 분리시켜왔는데, 그러다보니 STT와 TTS 사이에 페이지 변경이 잦았고, 그때마다 영상 녹화가 끊길 수 밖에 없었다.

한 큐에 녹화되는 방법이 없는지 찾고 또 찾고 테스트를 해봤지만, 포기하려는 시점에 결국 꼼수(대안)를 떠올렸다.

한 번에 녹화가 안 된다면, 분리된 걸 하나로 합치면 될 게 아닌가? 폰에선 무거운 작업이니 서버에서 하면 될거고.

서버에서 합치면 되잖아?!

또 이쯤되니(?) 서버에서 돌아가는 프로그램 짜는 건 손쉬운 일이 되버렸다.
  1. 특정 라이브러리 - 모 통신사의 TTS, STT 라이브러리 - 를 이용해야 함.
  2. 사용자의 응답을 녹음해야 함.
  3. 영상 녹화를 해야 함.

미션 완료!!

되긴 되네?

이 프로젝트에 약 3달 정도 매달렸는데, 화면만 만지며 안드로이드 구조를 대략적으로 익히던 첫 한 달 이후로는 엄청나게 집중하고 고민하고 궁리했던 시기였다.

문제와 문제의 원인을 소거법으로 규명해가고, 해결책을 마련하기 위해 다각도에서 고민하여 실험해보고, 끝내 해결책이 떠오르지 않으면 한발짝 물러나 대안을 마련하고...

안드로이드나 코틀린을 정식으로 공부한 게 아니라, 문제에 봉착한 뒤 짜냈던 모든 아이디어가 근본적인 해결책이 아니라 꼼수처럼 느껴졌지만, 그 모든 시도가 문제의 원인을 구체적으로 드러내주거나 문제를 해결하는 데 어떤 방식으로든 도움이 되었다고 믿는다.

그리고 앞으로의 개발 인생도 깊은 고민과 시도와 실패의 순간들을 겪으며 울퉁불퉁 나아가길, 그러다 가끔은 달콤한 성공의 맛을 보길 스스로 바래본다.

ps. 최근에도 납품 받은 고객에게서 어플 잘 쓰고 있단 연락을 받고 무척 기분이 좋아졌었다.

profile
잡식형 괴발자

2개의 댓글

comment-user-thumbnail
2022년 11월 12일

개발과 관련해서 질문을 좀 드리고 싶은데
어떻게하면 연락을 할수 있을까요?

1개의 답글