[세컨드브레인 개발기] 6. 소리가 줄어드는 녹음.
메뉴바 회의 녹음기부터 봅니다. 여덟 개로 시작해 열한 개가 된 파이프라인에, 첫 번째로 붙은 것이 이 녹음기예요. 회의가 시작되면 클릭하고, 끝나면 또 클릭하는, 그게 전부인 앱입니다.
5분마다 도는 동기화나 10분마다 도는 전사와 달리, 이건 손가락이 눌러야 시작되는 트리거 방식입니다. 트리거로 산다는 점은 끝까지 그대로였는데, 소리를 받는 방식만 이상하게 무너졌습니다. 열 초를 녹음했는데 2.73초짜리 파일이 나오는, 소리가 줄어드는 실패였어요. 무엇이 줄었는지, 하나씩 보겠습니다.
그달 중반에 붙은 세 개는 성격이 저마다 달랐습니다. 매일 새벽 세 시에 도는 정리 작업, 매주 월요일 아홉 시의 논문 리캡, 그리고 버튼이 눌릴 때 도는 녹음기. 셋 중에서 가장 손에 가까운 곳에 있는 것이 이 녹음기였습니다.
이 녹음기가 왜 필요한지는 지난 편에서 이미 설명했어요. 회의에서 나는 소리가 유입의 한 갈래이고, 그 소리를 파일로 만드는 일은 마이크에 닿을 수 있는 맥에서만 가능합니다. 파일이 떨어지면 10분짜리 전사가 받고, 15분짜리 정리가 받아서 회의록 폴더에 넣습니다.
그러니 녹음기는 파이프라인의 가장 위에 서서, 뒤의 모든 작업이 기다리는 첫 관문입니다. 여기 앉힌 프로그램은 rumps라는 메뉴바 프레임워크로 짰고, launchd가 로그인과 함께 띄워요. 메뉴바에 아이콘이 하나 생기고, 클릭하면 녹음이 시작되고, 다시 클릭하면 끝납니다. 만들어진 파일은 녹음 시각이 그대로 이름이 되는 wav로, 회의록 폴더의 audio 아래에 쌓입니다. 첫 녹음에서는 마이크 권한을 묻는 창이 하나 떴고, 그다음부터는 조용했습니다.
소리가 줄어든다

첫 버전은 ffmpeg에 맡겼습니다. avfoundation이라는, 맥의 소리 장치를 캡처하는 입력 방식이었는데, 명령 하나로 마이크에서 파일까지 가는 가장 간단한 선택이었어요. 지금 보면, 그 간단함이 실패의 절반이었다고 생각합니다.
ffmpeg -f avfoundation -i 마이크 -ar 16000 회의.wav
간단했습니다. 그리고 그 간단함이 첫날 밤에 무너졌어요. 열 초쯤 녹음하고 파일을 보니 2.73초였는데, 재생해 보면 목소리가 빠르게 돌아가면서 지지직거리는 소리가 섞여 나오고, 마치 녹음이 저 혼자 앞으로 달려간 것 같았습니다.
열 초가 2.73초가 되려면 소리의 70%가 넘게 사라져야 하는데, 길이가 줄어든 게 아니라 통째로 사라진 조각이 있다는 걸 알게 됐어요. 샘플이 대량으로 누락된 것입니다. 재생을 몇 번 반복해도 결과는 같았습니다.
누락은 어디서나 같은 정도가 아니었는데, launchd가 띄운 환경에서 특히 심했습니다. 같은 스크립트인데, 어디서 태어났느냐에 따라 샘플이 사라지는 양이 달라졌어요. 그런데 이 녹음기는 평소에 launchd가 띄운 상태로 삽니다. 로그인과 함께 태어나는, 그 상태가 기본값이었습니다. 가장 자주 쓰는 조건이, 가장 심한 증상을 보여준 셈이에요. 기록에는 '특히 launchd 환경에서 심함'이라는 한 줄만 남아 있고, 왜 그런지는 적지 않았습니다.
이 실패에서 얻은 건 하나입니다. 같은 코드라도 누가 띄우느냐가 결과를 바꾼다는 것.

리샘플이 누락을 키웠다
원인으로 지목된 것 중 하나가 -ar 16000이라는 옵션이었습니다. 이걸 인라인으로 붙이면 캡처와 리샘플을 한 명령에서 동시에 처리하게 되는데, 그 조합이 누락을 키운다는 것만 기록에 남아 있어요. 실시간 캡처에서 처리 한 번이 밀리면 그 프레임은 다시 채워지지 않으니 할 일이 둘일 때 밀릴 일도 둘이 된다는 게 내 짐작이지만, 원인을 끝까지 파보지는 않았습니다.
그래서 전략을 바꿨습니다. ffmpeg를 통째로 빼고, 소리를 받는 일을 PortAudio로 넘겼어요. 파이썬에서는 sounddevice라는 이름으로 쓰는 라이브러리인데, InputStream이라는 객체가 마이크에서 들어오는 샘플을 콜백으로 흘려보내고, 그 콜백에서 받은 그대로 wave 모듈로 16k 모노 파일을 직접 기록합니다. 한 줄로 줄어든 명령은, 그만큼 많은 일을 대신하고 있었습니다.
sounddevice.InputStream(samplerate=16000, channels=1, callback=버퍼를_wav에_쓰기)
받기와 쓰기가 같은 프로세스 안에서, 버퍼가 쌓이는 족족 처리됩니다. 중간에 끼어드는 프로그램이 없어요. 콜백은 소리가 도착할 때마다 불리는 함수이고, 쓰는 일은 소리와 같은 시각에 일어납니다. 통역을 거치지 말고, 소리를 받는 쪽이 직접 받으라는 식의 해결이었죠. 16k를 그 자리에서 바로 요청하니, 리샘플이 끼어들 자리도 없었습니다.
같은 마이크, 같은 16k였는데 받는 길만 달라졌고, 그날 기록에 남은 건 '전체 체인 검증 완료'라는 한 줄입니다. 고친 뒤의 길이를 다시 재서 적어두지는 않았어요. ffmpeg은 명령 하나로 모든 걸 해주는 도구지만, 그 대가를 녹음이 치렀습니다. 처음부터 이렇게 했더라면 어땠을까 싶을 정도로, 해결책 자체는 단순했습니다. 그다음부터 녹음기는 이 방법을 씁니다.

알림이 죽어 있었다
같은 날 고친 게 하나 더 있었습니다. 알림이었어요. 고치는 데는 한 줄이면 충분한, 그런 작은 부분이었습니다. 그 알림이, 이 녹음기의 마지막 고비였습니다.
녹음을 끝내면 메뉴바 앱이 알림을 하나 띄우는데, 처음엔 rumps가 주는 notification 함수를 쓰고 있었습니다. 그런데 이 함수는 앱 번들로 묶인 프로그램에서만 제대로 동작해요. 이 녹음기는 그냥 파이썬 스크립트라 번들이 아니었고, 알림을 부르는 순간 CFBundleIdentifier 오류를 내고 죽었습니다.
죽는 것 자체는 그럴 수 있습니다. 알림 하나가 죽는 걸로 끝났다면 그냥 고치면 됐을 텐데, 문제는 죽은 위치였어요. 알림은 녹음을 끝내는 stop 로직 안에 있었고, 그 뒤에는 전사 트리거가 기다리고 있었습니다. 알림이 죽는 순간, 그 뒤의 전사도 같이 죽었습니다. 녹음을 끝내도 전사가 시작되지 않는, 반쪽짜리 동작이었던 것이죠.
고장이 소리 없이 지나가는 건 자동화에서 가장 무서운 일입니다. 지금 생각해도 웃긴 게, 이 크래시는 소리와는 아무 상관이 없었습니다. 샘플이 사라지는 문제와 전사가 시작되지 않는 문제가 같은 교훈 블록에 나란히 적혀 있는데, 어느 쪽을 먼저 알아챘는지는 적어두지 않았어요.
알림은 osascript의 display notification으로 바꿨습니다. 맥의 알림 센터에 그대로 뜨는 시스템 명령으로, 번들 여부와 상관없이 동작합니다. 셸 명령이라 파이썬에서 그냥 불러 쓰면 되고 알림이 죽을 일이 없어졌어요. 몇 줄짜리 교체였지만, 전사 트리거가 다시 살아나는 건 이 한 줄에서 시작됐습니다. 이제 녹음을 끝내면 알림이 뜨고, 10분 안에 전사가 받고, 15분 안에 정리가 붙습니다.
뚜껑을 닫으면
녹음기가 가진 또 하나의 트리거는 뚜껑이었습니다. NSWorkspaceWillSleep이라는, 맥이 잠들기 직전에 알려주는 시그널에 저장을 걸었어요. 그 타이밍이, 저장의 마지막 기회였습니다.

회의가 끝나고 노트북 뚜껑을 닫으면, 그 순간 녹음이 확정됩니다. 뚜껑이 닫히는 순간은 곧 회의가 끝난 순간이라, 저장 시점으로도 자연스러웠습니다. 저장은 잠들기 직전에 끝나고, 잠든 뒤에는 할 일이 없었어요.
회의가 길어지면 멈추는 버튼은 잘 잊히는데, 자리에서 일어나 뚜껑을 안 닫고 나가는 일은 거의 없습니다. 기억에 맡긴 버튼과 달리 습관이 저절로 하는 동작을 트리거로 쓰는 것, 그게 이 녹음기에서 가장 쓸모있던 설계였습니다.
만든 지 하루 만에, 녹음기는 세 번 고쳐졌습니다. 하나는 소리를, 하나는 옵션을, 하나는 알림을. 셋 다 같은 밤에 일어났어요. 그날 밤 기록에는 '전체 체인 검증 완료'라는 짧은 한 줄이 남았습니다. 그 한 줄에, 그 밤의 전부가 들어 있었습니다.
메뉴앱의 녹음에서 시작해 전사와 정리를 거쳐 회의록 폴더까지, 한 번에 흐르는 것이 확인됐습니다. 회의가 끝나면 뚜껑을 닫는 것만으로 파일이 볼트로 흘러 들어가고, 뒤의 작업들이 순서대로 깨어납니다. 버튼 하나와 뚜껑 하나가, 하루의 회의를 브레인으로 옮겼어요.
기록에는 'ffmpeg avfoundation 캡처는 사용 금지'라는 한 줄도 함께 남겼는데, 나중에 같은 실패를 반복하지 않으려고 결론을 문서에 박아둔 것입니다. 회의는 더 이상 손으로 챙길 일이 아니었습니다. 이제는 뚜껑을 닫으면, 뒤는 기계의 몫이에요. 열한 개가 된 파이프라인의 첫 관문은, 이렇게 해서 제자리를 찾았습니다. 전사는 전사대로 또 다른 싸움을 준비하고 있었는데, 그 이야기는 다음 편에서.
'공부 > 인공지능' 카테고리의 다른 글
| [세컨드브레인 개발기] 8. 단체 메일은 조용히. (0) | 2026.08.06 |
|---|---|
| [세컨드브레인 개발기] 7. 전사가 깨진 이유. (0) | 2026.08.05 |
| [세컨드브레인 개발기] 5. 끊기지 않는 유입. (0) | 2026.08.03 |
| [세컨드브레인 개발기] 4. 위키가 영어로 쓰여 있었다 — 4,382개 중 482개 (0) | 2026.08.02 |
| [세컨드브레인 개발기] 3. 20k 문서를 실제로 읽어들이기 — 로더 지옥 (0) | 2026.08.02 |