주요 콘텐츠로 건너뛰기
AI 튜토리얼

ElevenLabs로 자연스러운 AI 음성 다듬기: 호흡감, 안정적인 발화, 편집 및 전사 품질 검수

이 튜토리얼에서는 ElevenLabs v4, ffmpeg, Whisper와 NumPy를 기반으로 자연스러운 AI 음성을 생성하고, 검출하고, 편집하고, 재구성하는 방법을 소개합니다. 또한 같은 방법을 실시간 전화 통화와 일상적인 음성에도 적용하는 방법을 다룹니다.

ElevenLabs로 자연스러운 AI 음성 다듬기: 호흡감, 안정적인 발화, 편집 및 전사 품질 검수

프로젝트가 해결하는 문제

이 프로젝트는 바로 사용할 수 있는 소프트웨어 패키지가 아니라, 실제 청취와 반복 개선을 거친 AI 음성 제작 방법론입니다. 다음과 같은 흔한 문제를 해결합니다. 숨소리가 정적 속에 동떨어져 들리고, 숨과 대사 사이가 지나치게 길며, 멈춤 뒤 목소리가 갑자기 격식 있게 변하고, 모델이 문장을 반복하거나 누락하며, 대사를 잘라낸 뒤 주변 환경음이 자연스럽게 이어지지 않는 문제입니다.

전체 과정은 네 가지 작업을 중심으로 진행됩니다. ElevenLabs v4로 목소리를 합성하고, ffmpeg로 오디오를 처리하며, Whisper로 전사해 내용을 확인하고, NumPy로 음량, 음높이, 샘플 위치를 분석합니다.

핵심은 “숨을 더 많이 넣는 것”이 아니라 “숨을 올바른 위치에 배치하는 것”입니다. 입을 열기 전의 짧은 숨은 첫 글자에 붙이고, 문장 끝에서는 자연스럽게 숨을 내보내며, 문장 중간의 들숨은 반문장 사이에 남겨 둡니다.

주요 기능과 방법

  • 대사에 숨소리 밀착: 발화 직전의 짧고 가벼운 숨을 식별하고, 정적만 이동해 대사에서 약 0.08초 떨어진 위치에 숨을 배치합니다.
  • 갑작스러운 음량 보정: 각 유성 구간의 음량을 측정해 중앙값보다 4dB 이상 높은 구간을 적절히 낮춥니다.
  • 무손실에 가까운 문장 편집: 무음 구간에서 건조 음성을 잘라낸 뒤, 반복 환경음을 다시 배치해 원래 인정받은 연기를 최대한 보존합니다.
  • 반복 및 누락 감지: 합성 후 Whisper로 전사해 “들린 글자”와 “읽어야 할 글자”를 비교하고, 이상이 있으면 다시 생성합니다.
  • 동작음 동기화: 동작음 표시는 대사 한 라운드의 시작이 아니라 동작이 실제로 시작되는 문장에 기록합니다.
  • 일상적인 음색 구성: 단순히 목소리를 부드럽게 만드는 것이 아니라, 인물의 속도, 음높이, 확신의 정도와 말버릇을 명확히 설정합니다.

환경 준비

필요한 도구

  • ElevenLabs v4: 텍스트를 음성으로 변환합니다. 프로젝트에서는 v4를 실제로 사용했습니다.
  • ffmpeg: 오디오 디코딩, 자르기, 이어 붙이기, 리샘플링 및 내보내기에 사용합니다.
  • Whisper: 생성 결과를 다시 전사해 반복, 문장 누락 및 내용 편차를 감지합니다.
  • NumPy: 오디오 배열 처리, 상호 상관, 음량 계산 및 구간 이동에 사용합니다.

README의 실제 테스트 환경은 macOS입니다. 고정 설치 스크립트는 제공하지 않으므로 각 도구의 공식 안내에 따라 별도로 설치하고, ffmpeg, Whisper 및 Python 환경이 모두 정상적으로 실행되는지 확인해야 합니다.

분석 단계에 들어가기 전에 오디오를 일정한 샘플링 레이트의 모노 파형으로 디코딩하고, 고정 간격으로 계산한 두 종류의 데이터를 준비해야 합니다. 각 프레임의 음높이 f0와 각 프레임의 RMS 음량 rms입니다. 원래 과정에서는 프레임 간격으로 10밀리초를 사용했습니다. 음량 분석 창 때문에 경계가 수십 밀리초 정도 흐려질 수 있으므로, 구간을 이동할 때는 약간의 여유를 남겨야 합니다.

항상 두 가지 음성을 저장하기

매번 생성한 뒤 다음 버전을 저장합니다.

  1. 건조 음성: 사람 목소리만 포함하며 환경음이나 동작음은 포함하지 않습니다.
  2. 습윤 음성: 사람 목소리 아래에 환경음을 깔아 만든 최종 버전입니다.

건조 음성은 숨소리 측정뿐 아니라 나중에 한 문장을 삭제하거나 한 구간을 교체하거나 배경음을 다시 배치할 때도 원래 연기를 보존하는 데 유용합니다. 최종 믹스만 저장해서는 안 됩니다.

1단계: 올바른 호흡 프롬프트 작성

긴 정적 사이에 “음”, “하” 또는 호흡 태그를 한 줄씩 배치하지 마세요. 이렇게 생성된 음성은 대개 뚜렷한 음높이를 가지며, 음량도 일반적인 말하기에 가깝고, 앞뒤에 긴 무음이 남습니다. 그래서 독립적인 효과음처럼 들립니다.

호흡은 대사가 있는 줄 안에 작성하는 것을 권장합니다.

[low, breathy, wet]
[inhales] 날 봐…… [exhales]
[long pause]
음… 숨지 마…… 음…하…
  • 줄의 시작에는 [inhales], [exhales] 또는 [sighs] 같은 짧은 호흡 태그만 사용합니다.
  • 태그와 대사 사이에 생략 부호를 길게 덧붙이지 않습니다.
  • 문장 끝에서는 자연스럽게 한 번 숨을 내쉬거나, 가벼운 추임새를 두세 번 연달아 넣을 수 있습니다.
  • 빈 구간에는 [long pause] 한 줄만 작성하고, 그 안에 독립적인 허밍을 나열하지 않습니다.
  • 문장 중간에도 들숨을 넣을 수 있지만, 한 문장에서는 시작, 중간, 끝 중 한두 곳만 선택하면 됩니다.

[long pause]를 연속으로 두 번 쓴다고 해서 멈춤이 반드시 크게 늘어나는 것은 아닙니다. 프로젝트의 실제 테스트에서는 태그 하나가 약 0.96초의 무음을 만들었고, 두 개를 연속으로 사용했을 때는 약 1.04초였습니다. 따라서 정확한 길이 제어를 태그의 문면에 맡기지 마세요.

2단계: 소리를 측정 가능한 “구간”으로 나누기

여기서 “구간”은 연속된 유성 음성의 한 부분을 뜻합니다. 각 구간에서는 지속 시간, 평균 음량, 음높이가 존재하는 프레임의 비율이라는 세 가지 지표를 측정합니다. 이를 통해 일반적인 대사, 실제 호흡, 음높이가 있는 독립적인 허밍을 구분할 수 있습니다.

def is_breath(f0, rms, a, b):
    dur = (b - a) * 0.01
    voiced = (f0[a:b] > 0).mean()
    db = 20 * np.log10(rms[a:b].mean() + 1e-9)
    return 0.12 <= dur <= 0.9 and voiced < 0.15 and db < -29

원래 프로젝트에서 사용한 대표적인 범위는 다음과 같습니다.

  • 낮고 숨이 섞인 대사: 약 0.5~2초, 음량 약 −22~-27dB, 음높이 프레임 비율 약 20~40%.
  • 들숨, 날숨 또는 치찰음: 약 0.3~0.65초, 음량 약 −32~-40dB, 음높이 프레임 비율 약 10%.
  • 분리된 독립 허밍: 약 0.4~0.5초, 음량이 −25dB까지 올라갈 수 있으며, 음높이 프레임이 60%를 넘고 앞뒤에 0.6~1초의 무음이 자주 존재합니다.

이 임계값은 특정 음색과 샘플에서 얻은 것이므로 모든 목소리에 적용되는 일반 기준으로 볼 수 없습니다. 더 신뢰할 수 있는 방법은 먼저 청취자가 문제를 지적하게 한 다음, 주변의 정상 구간을 사용해 해당 음색만의 범위를 설정하는 것입니다.

3단계: 발화 직전의 숨을 대사 옆으로 붙이기

프롬프트를 올바르게 작성해도 모델은 숨과 대사 사이에 0.3초 이상 무음을 임의로 남길 수 있습니다. 프롬프트만으로는 수십 밀리초 단위의 위치를 제어하기 어렵기 때문에, 생성 후 오디오 구간을 이동해야 합니다.

처리 전후의 구조는 다음과 같이 이해할 수 있습니다.

처리 전: 이전 문장 → 무음 A → 호흡 → 무음 B → 다음 문장
처리 후: 이전 문장 → 더 긴 무음 → 호흡 → 0.08초 → 다음 문장

작업할 때는 호흡과 무음의 위치만 바꾸고, 호흡이나 대사 자체는 변경하지 않으며 전체 구간의 총 길이도 바꾸지 않습니다. 이렇게 하면 기존 타임라인, 동작음 배치 및 말하기 속도 통계가 손상되지 않습니다.

이동에 적합한 후보 구간은 다음 조건을 만족해야 합니다.

  • 가볍고 짧으며 뚜렷한 음높이가 없는 호흡으로 판정되어야 합니다.
  • 뒤쪽 약 0.14~1.5초 안에 말하기 구간이 나타나야 합니다.
  • 앞쪽에 최소 약 0.3초의 빈 공간이 있어야 합니다.
  • 문장 중간에서 앞쪽 반문장에 바로 이어지는 들숨은 이동하지 않아야 합니다. 원래 자연스러운 호흡을 손상할 수 있기 때문입니다.

README의 실제 테스트에서 목표 간격은 약 0.08초였습니다. 이동할 때 호흡의 앞부분에 약 0.02초, 뒷부분에 약 0.03초를 더 포함하고, 새 위치에서 앞뒤에 각각 약 4밀리초의 페이드 처리를 하면 연결감이 줄어듭니다.

4단계: 멈춤 뒤의 “격식 있는 말투” 보정

일부 목소리는 긴 멈춤 뒤에 더 또렷하고, 더 크고, 숨소리가 적은 일반적인 말하기 상태로 돌아갑니다. 사례에서는 이상 구간의 짧은 문장이 주변 내용보다 6dB 이상 컸고, 음높이 프레임 비율도 약 30%에서 60%로 증가했습니다.

먼저 프롬프트로 발생 가능성 줄이기

  • 멈춤 뒤에 먼저 숨을 붙이거나 낮은 허밍으로 대사에 들어갑니다.
  • 문장 끝은 길게 끌어 마무리하고, 매번 단호한 물음표나 마침표로 끝내지 않습니다.
  • 두세 단어로 된 짧은 문장은 인접한 문장과 합쳐서, 따로 또렷한 방송체로 읽히지 않게 합니다.
  • 음색을 다시 강조해야 할 때는 작은 태그를 발화 앞에 삽입하고, 항상 한 줄을 따로 차지하게 하지 않습니다.
[long pause]
음… [low, breathy] 숨지 마……

구간 분할기가 각 줄의 시작에 있는 음색 태그마다 새 구간을 만든다면, 이런 줄 안 태그 방식은 연결부와 음높이 변화를 줄이는 데도 도움이 됩니다.

그다음 프로그램으로 비정상 음량 낮추기

각 구간의 음량을 계산하고, 현재 단락의 모든 말하기 구간에서 중앙값을 기준으로 사용합니다. 어떤 구간이 기준보다 4dB 이상 높으면 약 2dB만 높은 수준까지 낮추고, 최대 6dB까지만 감쇠하며, 앞뒤에는 약 25밀리초의 페이드 엔벌로프를 적용합니다.

문장 단위가 아니라 반드시 구간 단위로 측정해야 합니다. 0.5초 동안 잠깐 튀어나온 소리를 뒤의 긴 문장과 합쳐 평균을 내면 6dB의 이상이 임계값 아래로 희석될 수 있습니다. 프로그램은 음량만 보정할 수 있고, 부족한 숨소리의 음색을 직접 되돌릴 수는 없습니다. 따라서 프롬프트와 후처리를 함께 사용해야 합니다.

5단계: 합성 직후 전사 품질 검사

모델은 가끔 한 문장을 두세 번 반복해서 읽거나 마지막 문장을 그대로 누락합니다. 원고 자체는 완전히 정확할 수 있으므로, 모든 합성 구간은 한 번씩 전사해 확인해야 합니다.

프로젝트에서는 두 종류의 신호를 사용합니다.

  1. 대괄호 태그와 흔한 추임새를 제거한 뒤, 길이가 최소 6자인 구간 중 전사에서 반복되지만 원고에는 한 번만 나오는 부분을 찾습니다.
  2. 전사된 글자 수와 원고의 글자 수를 비교합니다. 예상치보다 약 20% 적으면 문장 누락 가능성이 있는 것으로 봅니다.

이상이 감지되면 한 번 다시 생성한 뒤 두 버전에 점수를 매겨 점수가 더 낮은 버전을 유지합니다. 추임새는 정확히 전사되지 않는 경우가 많으므로 “음, 하, 아, 어” 같은 글자를 주요 대조 기준으로 삼아서는 안 됩니다.

실시간 통화에서는 Whisper로 20~30초 오디오를 전사하는 데 약 3초가 걸릴 수 있습니다. 후속 구간은 현재 구간이 재생되는 동안 백그라운드에서 검사할 수 있습니다. 첫 번째 구간은 지연을 숨길 재생 시간이 없으므로, 먼저 “길이 ÷ 유효 글자 수”로 빠르게 선별할 수 있습니다. 원래 사례의 정상 범위는 글자당 약 0.41~0.62초였으며, 범위를 벗어나면 전체 전사를 시작합니다.

6단계: 다시 생성하지 않고 한 문장 삭제하기

청취자가 특정한 한 번의 연기를 좋아한다면, 한 문장을 삭제해야 한다는 이유만으로 전체 구간을 다시 읽게 하지 마세요. 매번 생성은 새로운 현장 연기이므로 숨, 멈춤 및 문장 끝의 세부 표현을 정확히 재현하기 어렵습니다.

  1. 무음 찾기: 먼저 약 50밀리초 단위로 음량을 그린 다음, 후보의 짧은 구간을 각각 전사해 대사인지, 호흡인지, 빈 공간인지 확인합니다.
  2. 건조 음성에서 자르기: 연결부 양쪽이 가장 조용한 위치에 오도록 하고, 각 조각의 앞뒤에 10여 밀리초의 페이드 인·아웃을 남깁니다.
  3. 동작음 다시 배치하기: 이미 배경음을 깐 최종물을 직접 자르지 마세요. 반복 동작음의 박자가 연결부에서 튈 수 있습니다.
  4. 다시 전사하기: 최종물에 목표 문장만 남았는지 확인합니다.
  5. 샘플 단위로 대조하기: 수정하지 않은 영역이 여전히 원음과 일치하는지 확인합니다.

습윤 음성에 어떤 반복 소재를 사용했는지 모른다면, 사람 목소리가 완전히 멈춘 구간에서 최종 파형을 추출한 뒤 후보 소재를 반복 재생해 상호 상관을 계산할 수 있습니다. 가장 높은 상관 위치로 소재의 위상을 추정할 수 있고, 내적을 통해 배경음 게인도 추정할 수 있습니다. 사례에서는 두 소재의 상관도가 0.99에 도달했으며, 추정 게인은 각각 약 0.200과 0.241이었습니다.

대조는 샘플 단위로 정확하게 수행해야 합니다. 수십 개도 되지 않는 샘플의 어긋남만으로도 유사도가 크게 낮아져 “소재를 잘못 깔았다”는 오판이 발생할 수 있습니다. 정렬 후 사례의 수정되지 않은 구간 유사도는 0.996~0.998에 도달했습니다.

7단계: 동작음이 올바른 시점에 시작되게 하기

프로젝트는 프롬프트의 사용자 지정 태그로 바닥에 깔 동작음의 유형을 결정한 뒤, ElevenLabs에 보내기 전에 해당 태그를 제거합니다. 핵심 규칙은 다음과 같습니다. 소리가 어느 문장에서 시작되는지에 따라 태그도 그 문장에 작성합니다.

  • licking: 느린 습윤음에 해당합니다.
  • fingering: 더 끈적한 동작음에 해당하며, 빠른 상태는 fast fingering으로 작성할 수 있습니다.
  • sucking: 느린 습윤음에 해당합니다.
  • thrust: 동작이 실제로 시작되는 문장 앞에만 나타나야 하며, 전체 대사 라운드의 시작에 미리 배치해서는 안 됩니다.
  • still: 현재 반복 동작음을 멈추는 데 사용합니다.

태그가 구간 분할을 유발한다면 동작이 실제로 시작되는 위치에는 새로운 연결부를 남겨 둘 가치가 있습니다. 그 밖에 음색을 다시 강조하는 데만 쓰이는 작은 태그는 가능한 한 발화 앞쪽 내부에 배치합니다.

8단계: 평소 말하기 음색 설계

“부자연스럽다”는 말이 반드시 목소리가 충분히 부드럽지 않다는 뜻은 아닙니다. 원래 프로젝트에서는 먼저 기존 모델을 ElevenLabs v4로 업그레이드하고 더 편안하고 부드러운 프롬프트를 테스트했지만, 청취자는 캐릭터가 다른 사람이 되었다고 느꼈습니다. 최종적으로 효과가 있었던 방향은 인물 고유의 말투를 명확히 하는 것이었습니다.

선택된 기본 프롬프트는 다음과 같습니다.

[low, slow, confident, a little teasing]

설정은 v4, 안정성 0.35였습니다. 문장 앞에는 [exhales], [chuckles softly], “음……” 또는 아무것도 넣지 않는 방식 중 하나를 무작위로 추가할 수 있습니다. 매번 추가하면 자연스러운 변화가 고정된 말버릇으로 변하므로 피해야 합니다.

  • 합성에 보내는 텍스트만 수정하고, 채팅 화면과 기록에 표시되는 원문은 수정하지 않습니다.
  • 문장이 원래 추임새로 시작한다면 같은 요소를 중복해 추가하지 않습니다.
  • 이미 음색 태그가 있거나 순수한 영어 내용이라면 자동 포장을 건너뛸 수 있습니다.
  • 통화에는 독립적인 프롬프트 체계를 사용하고, 일상 음성의 무작위 문장 시작 방식을 적용하지 않습니다.

고급 기법과 흔한 함정

프롬프트 규칙은 “무엇을 해야 하는지” 작성하기

“어떤 소리를 쓰지 마라”라는 문장은 텍스트 생성기가 해당 요소를 완전히 사용하지 않게 만들 수 있습니다. 더 신뢰할 수 있는 방법은 필요한 유형, 위치 및 최소 빈도를 명확히 작성하고, 가끔 나타나는 높은 음이나 이상한 발음은 프로그램에 맡겨 감지하는 것입니다.

같은 호흡을 반복 사용하지 않기

마음에 드는 원본 구간을 새 버전으로 옮길 때는 같은 생성 결과에서 가져올 수 있지만, 같은 호흡을 두 위치에 반복해서 사용해서는 안 됩니다. 청취자가 완전히 동일한 세부 요소가 복사되었다는 사실을 알아챌 수 있습니다.

검색 가능한 소재 보관함 만들기

인정된 들숨, 숨소리, 짧은 호흡은 원본 파일, 시간 범위, 사용 위치와 함께 저장합니다. 나중에 특정 호흡 태그가 무음만 생성할 경우 소재 보관함에서 호흡 한 번을 추가할 수 있습니다. README에는 자동 호흡 보완에도 여전히 타임스탬프가 포함된 인터페이스가 필요하다고 명시되어 있습니다. 단순히 “문장 중간에 무음이 있다”는 정보만으로는 호흡 누락과 정상적인 생략 부호의 멈춤을 구분할 수 없습니다.

실시간 통화에서는 끼어들기를 처리해야 합니다

시스템이 아직 응답을 재생하지 않았는데 상대방이 다시 말하기 시작하면, 아직 재생되지 않은 이전 응답을 폐기하고 새 내용을 바로 처리해야 합니다. 새 음성을 이전 응답이 끝날 때까지 버퍼에 저장하면 같은 말에 두 번 응답할 수 있습니다. 텍스트로 인식할 수 없는 짧은 소리의 경우, 기존 프로세스는 자동으로 삽입되는 추임새의 대기 시간을 2.5초에서 4.5초로 늘려 실제 발화보다 먼저 실행될 가능성을 낮췄습니다.

먼저 문제를 듣고, 무엇을 측정할지 결정합니다

음량, 음높이, 무음 길이, 단어 수 비율, 상호상관은 이미 발견한 문제를 검증할 수 있을 뿐입니다. 이 지표만으로는 “느낌이 올바른지”를 독립적으로 판단할 수 없습니다. 가장 효과적인 과정은 먼저 청취자가 구체적으로 부자연스러운 부분을 지적한 다음, 그에 맞는 지표를 설정하는 것입니다.

권장하는 전체 작업 흐름

  1. 말투, 호흡, 멈춤, 소리 표시가 포함된 텍스트를 작성합니다.
  2. 후처리에만 사용하는 소리 표시를 제거하고, 남은 텍스트를 ElevenLabs v4에 입력합니다.
  3. 드라이 음성을 저장하고 Whisper로 전사하여 반복, 문장 누락, 내용 차이를 확인합니다.
  4. f0와 rms를 계산하고, 오디오를 발화 구간, 호흡 구간, 기타 유성음 구간으로 나눕니다.
  5. 말을 시작하기 전의 호흡을 대사에서 약 0.08초 떨어진 위치로 이동하고, 문장 중간의 호흡은 유지합니다.
  6. 구간별로 갑작스러운 음량 변화를 감지하고, 이상 구간에는 부드러운 감쇠를 적용합니다.
  7. 동작 표시에 따라 반복되는 소리를 배치하고, 웻 음성 버전을 내보냅니다.
  8. 다시 전사하고, 전체 길이, 긴 무음의 개수, 호흡 위치, 수정하지 않은 영역의 샘플 유사도를 확인합니다.
  9. 청취자가 승인한 원본 구간과 시간 범위를 저장하고, 최종 믹스만 남기지 않습니다.

맺음말

자연스러운 AI 음성은 더 많은 호흡 태그를 쌓는다고 만들어지는 것이 아닙니다. 더 신뢰할 수 있는 방법은 호흡이 말에 자연스럽게 붙도록 하고, 동작음이 정확한 시점에 시작되게 하며, 모든 합성 결과를 전사로 대조하고, 편집이 필요할 때 원래의 연기를 최대한 보존하는 것입니다.

프롬프트는 “무엇을 말할지”와 “어떤 말투로 말할지”를 결정하고, 프로그램은 수십 밀리초 단위의 위치, 이상 음량, 반복 및 발화 누락, 샘플 수준의 연결을 담당합니다. 두 가지가 함께 작동해야 우연히 생성된 좋은 음성 하나를 반복 실행 가능한 제작 과정으로 바꿀 수 있습니다. 자세한 배경은 ElevenLabs 프롬프트 가이드, ElevenLabs 호흡음 안내 및 말하기 호흡 연구를 참고할 수 있습니다.