23.06.15
DEVOTEE를 활성화 시키면
지금 작성한 커뮤니티 글에 대해 1개의 댓글을 달아줍니다.
버튼을 누르면 글 수정 시 ChatGPT가 작성한 댓글이 수정됩니다.
| 컨텐츠 유형 | 제목 | 저장일 | 삭제 |
|---|
본인인증 로그인에 실패하였습니다.
회원이 아니시거나 본인인증 등록이
완료되지 않은 사용자입니다.
안녕하세요, SKT 신철민입니다.
이번 글에서는 Java 환경에서 실시간 음성 서비스의 품질을 높이기 위한 고민과 과정, 그리고 저희가 직접 경험한 Tyche 엔진의 주요 적용 사례를 소개합니다.
실시간 스트리밍이나 대화형 인터페이스처럼 빠른 반응성과 네이티브 수준의 정확도가 요구되는 환경에서는, 음성의 전처리(예: EPD, 저비트레이트 인코딩)를 안정적으로 구축하는 일이 쉽지만은 않습니다.
특히 JNI 설계, 아키텍처별 빌드·링크, 프레임 처리를 각각 최적화하는 과정에서 상당한 시행착오와 점검 지점들이 생기게 됩니다.
저희는 이러한 기술적 과제들을 해결하고자 Tyche 라이브러리를 활용해, 네이티브 C API 기반의 주요 음성 처리 모듈(EPD, Speex, Wakeup 등)을 Java 애플리케이션에 이식했습니다.
본문에서는 Tyche 라이브러리의 구조와 주요 기능을 중심으로, 이 라이브러리를 기반으로 Java용 네이티브 라이브러리를 OS와 아키텍처별로 어떻게 생성하는지, 단계별 빌드·적용 방법을 상세히 다룹니다.
Java 프로젝트와 연동 사례에 대해 추가 정보가 필요하시면 아래 devocean 블로그도 참고해 주세요.
https://devocean.sk.com/blog/techBoardDetail.do?ID=167433
음성 신호를 압축하기 위해 CELP(Code Excited Linear Prediction) 알고리즘을 기반으로 동작합니다.
CELP는 입력된 PCM 데이터를 프레임 단위(예: 16kHz, 10ms 등)로 나눠서, 각 프레임에 대해 음성의 선형 예측 계수와 잔차 신호(코드북 인덱스)를 추출해 정보를 최대한 줄여 저장합니다.
내부적으로 노이즈 제거(Denoising), 에코 제거(AEC/RES), 자동 이득 조절(AGC), 음성·무음 구간 판정(VAD) 등 다양한 처리 모듈을 선택적으로 사용하는데, 이 과정에서 실제 음성 통화 환경에 최적화된 품질과 저비트레이트 전송을 제공합니다.
코덱은 품질 설정(0~10 단계)과 샘플링 주파수(8/16/32kHz) 옵션을 변경해 협대역/광대역/초광대역 모두 지원하며, 실시간 처리와 파일 기반 인코딩 양쪽에 적합합니다
입력 음성 신호 스트림에서 실제 발화(음성) 구간의 시작·종료 시점을 판정하는 기술입니다.
일반적으로 프레임의 에너지, Zero-Crossing Rate, 주파수 대역 특성, VAD 결과 등을 혼합해 음성·무음 상태를 판별합니다.
프레임의 에너지: 한 프레임(짧은 구간)의 전체 샘플 값을 제곱해서 모두 더한 값으로, 음성 구간에서는 에너지가 높고 무음·잡음 구간에서는 에너지가 낮게 나타납니다. 주로 음성 유무 판정 기준으로 활용됩니다.
Zero-Crossing Rate: 신호가 0을 기준으로 위아래 교차하는 횟수를 구하는 값입니다. 무성음(노이즈·잡음)은 변화가 많아 이 값이 높고, 음성(특히 모음 등)에서는 이 값이 낮은 특징을 보입니다. 잡음·음성 구분에 사용됩니다.
주파수 대역 특성: 각 프레임의 주파수별 에너지를 분석한 값으로, 음성 구간에서는 특정 대역에 에너지가 집중됩니다. 환경별 잡음 제거나 음성 특성 분석에 활용됩니다.
VAD(Voice Activity Detection) 결과: 음성 프레임인지 아닌지를 판단하는 알고리즘 결과로, 에너지·ZCR·주파수 특성 등 다양한 지표를 혼합해 음성·무음 여부를 최종적으로 판정해줍니다
Tyche 엔진에서는 입력 PCM을 10ms 단위로 처리하면서, 각 프레임의 누적 에너지·스펙트럼 변화를 기반으로 발화 시작점과 끝점(START/END event)을 검출합니다. 외부 노이즈, 에코, 반향 등 다양한 환경 요소에 강인성을 높이기 위해 DTD(Double-Talk Detection)나 smoothing 기법을 추가 적용한 사례도 있습니다.
실서비스에서는 모델(파라미터, 환경별 학습값)을 추가로 적용해 미세조정이 가능하며, 로깅과 이벤트 출력(출력 버퍼 반환 등)으로 결과를 즉시 확인할 수 있습니다.
관련 정보는 저희 팀에 강규한 매니저님께서 전문가이시기에 관련 문의는 강규한 매니저님께 문의 부탁드립니다.
Tyche는 실시간 음성 전처리용 네이티브 엔진 라이브러리로, 빠른 지연과 안정성에 초점을 맞춘 인코딩·종단점 검출·웨이크업 감지 기능을 갖추고 있습니다.
본 프로젝트에서는 Tyche의 C API와 정적 라이브러리를 활용해, JNI를 통해 Java 환경에 손쉽게 통합하는 방식을 안내합니다.
libTycheCommon.a: OS/스레딩·플랫폼별 공통 유틸리티 제공
libTycheEpd.a: EPD(End-Point Detection) – 발화 구간의 시작/종료 검출
libTycheSpeex.a: Speex 코덱 – 저비트레이트 음성 인코딩/디코딩
libTycheWakeup.a: 키워드 기반 웨이크업(Hotword) 감지
공개 헤더: libtypes.h, libdefines.h, libexports.h, libEpdApi.h, libSpeexApi.h, libWakeupApi.h 등
EPD: 발화 시작·종료를 epdClientChannelSTART/RUN/RELEASE 시퀀스와 출력 버퍼 조회로 처
Speex: speexSTART/RUN/GetOutputData/RELEASE로 저비트레이트 음성 인코딩을 파이프라인화
Wakeup: Wakeup_Create/PutAudio/Destroy 명령으로 키워드 감지 및 이벤트/스코어 관리
지원 플랫폼/아키텍처:
Linux: amd64, aarch64
macOS: x86_64, aarch64
적용 예시
실시간 STT와 ASR(Automatic Speech Recognition) 전처리 파이프라인에서의 활용이 대표적이며,
모바일 단말이나 에이전트, 데스크톱 환경의 음성 인터랙션 서비스, 그리고 대역폭 제약이 있는 환경에서의 음성 업링크 최적화, 키워드 혹은 핫워드 기반 UX 인터페이스에도 적합합니다.
Tyche는 이미 검증된 네이티브 품질과 저지연을 제공하며, 모듈화된 API 구조 덕분에 필요한 기능만 선택적으로 연동할 수 있습니다.
Java 환경에서는 간결한 JNI 브리지를 통해 손쉽게 통합이 가능하지만, 운영 시에는 모델 파일 경로와 아키텍처, 빌드 경로의 정확한 지정이 중요합니다.
프레임 사이즈(16kHz, 16bit, 10ms)의 일관성을 항상 유지해야 하며, epdClientSetDebugOutput 옵션을 활용해 품질 점검과 디버깅을 수행할 수 있습니다.
Tyche는 네이티브 성능과 Java 생산성을 동시에 추구하는 환경에서 매우 유용합니다.
프로젝트 샘플 구조를 그대로 따라가면 Speex 인코딩, EPD·웨이크업 모듈을 빠르게 서비스 파이프라인에 적용할 수 있습니다.
본 가이드는 Tyche 엔진의 EPD, Speex, Wakeup 주요 모듈을 JNI로 Java 프로젝트에 연동해 실서비스에 적용하는 전체 절차를 단계별로 설명합니다.
실제 저장소 예시는 https://github.com/ConanShin/epd-speex-jni에서 참고할 수 있습니다.
대상 및 환경
Tyche 엔진을 실시간 음성 전처리 파이프라인에 사용하려는 Java 개발자를 위한 내용입니다.
테스트 환경은 Linux(amd64, aarch64), macOS(x86_64, aarch64)에 맞춰 설계되어 있으며, 저장소 기본 구조는 epd-speex-jni-transalator/ 폴더 기준입니다.
경로나 OS/아키텍처별 파일 배치 시 README 내 경로 오탈자를 주의하세요.
사전 준비
최소 준비사항으로 JDK(추천 17 이상), gcc 또는 clang 등 기본 빌드 툴체인이 필요합니다.
JAVA_HOME 환경 변수를 반드시 확인하고, 타겟 운영체제에 맞춰 Tyche 바이너리 디렉터리(os.arch 값에 따라 linux/amd64, linux/aarch64, macOS/x86_64, macOS/aarch64)를 올바르게 매핑해야 합니다.
특히 EPD 모델 파일(epd-speex-jni-transalator/c/endpoint_detector/skt_epd_model.raw)의 경로 유효성과 파일 존재 여부도 미리 점검해야 연동 시 런타임 오류를 예방할 수 있습니다.
JNI 헤더 생성
Java 클래스에서 JNI 네이티브 매핑용 헤더를 생성하려면 다음 명령을 사용합니다.
cd /Users/sum/workspace/02.AUTO/epd-test
javac -h . epd-jni-transalator/java/SpeexEncoder.java
javac -h . epd-jni-transalator/java/EndpointDetector.java헤더 파일은 현재 디렉터리와 build/generated/sources/headers/java/main에도 위치하므로, C 코드에서 경로를 참고 가능합니다.
C 소스 include 경로 및 빌드
각 모듈의 C 소스(speex_encoder.c, endpoint_detector.c)에서 include 구문을 프로젝트 구조 및 타겟 플랫폼에 맞게 수정합니다.
예시:Linux amd64의 경우
#include "../tyche/linux/amd64/include/libSpeexApi.h"macOS aarch64의 경우
#include "../tyche/macOS/aarch64/include/libEpdApi.h"C 파일 컴파일 및 JNI 라이브러리 생성
컴파일 예시(Linux/macOS):
Linux
gcc -I"$JAVA_HOME/include" -I"$JAVA_HOME/include/linux" -fPIC -c c/speex_encoder/speex_encoder.c -o speex_encoder.o
gcc -I"$JAVA_HOME/include" -I"$JAVA_HOME/include/linux" -fPIC -c c/endpoint_detector/endpoint_detector.c -o endpoint_detector.omacOS
gcc -I"$JAVA_HOME/include" -I"$JAVA_HOME/include/darwin" -fPIC -c c/speex_encoder/speex_encoder.c -o speex_encoder.o
gcc -I"$JAVA_HOME/include" -I"$JAVA_HOME/include/darwin" -fPIC -c c/endpoint_detector/endpoint_detector.c -o endpoint_detector.o링크 예시(Linux/macOS):
Linux
gcc -shared -fPIC -o libSpeexEncoderJNI.so speex_encoder.o -lstdc++ -L./c/tyche/linux/aarch64 -lTycheEpd -lTycheSpeex -lTycheCommon -lTycheWakeup
gcc -shared -fPIC -o libEndpointDetectorJNI.so endpoint_detector.o -lstdc++ -L./c/tyche/linux/aarch64 -lTycheEpd -lTycheSpeex -lTycheCommon -lTycheWakeupmacOS
gcc -dynamiclib -fPIC -o libSpeexEncoderJNI.dylib speex\_encoder.o -lstdc++ -L./c/tyche/macOS -lTycheEpd -lTycheSpeex -lTycheCommon -lTycheWakeup
gcc -dynamiclib -fPIC -o libEndpointDetectorJNI.dylib endpoint\_detector.o -lstdc++ -L./c/tyche/macOS -lTycheEpd -lTycheSpeex -lTycheCommon -lTycheWakeup공유 라이브러리를 해당 아키텍처별 폴더에 배치한 후 Java에서 System.load() 또는 System.loadLibrary()로 연결할 수 있습니다.
폴더 및 경로 매핑
Java 환경에서 JNI 라이브러리를 자동으로 로딩하려면
epd-speex-jni-transalator/c/speex_encoder/{os.arch}/
epd-speex-jni-transalator/c/endpoint_detector/{os.arch}/
경로에 산출물을 배치해야 합니다.
os.arch 값(aarch64, x86_64 등)을 체크하여 올바른 폴더에 libSpeexEncoderJNI.*, libEndpointDetectorJNI.* 파일을 이동하세요
운영 환경에서 Tyche 엔진을 안정적으로 서비스하기 위해서는 몇 가지 필수 항목을 꾸준히 점검해야 합니다.
아키텍처 일치: os.arch 값과 실제 바이너리 폴더명이 반드시 일치해야 하며, 아키텍처 불일치로 인한 로딩 오류를 예방하려면 빌드·배포 시점마다 재확인이 필요합니다.
모델 파일 검사: EPD 모델 파일의 경로, 파일 존재, 접근 권한을 시작 전에 모두 체크하고, stat() 등 기본 예외 처리를 통해 초기화 실패를 방지해야 합니다.
링커 오류 추적: undefined symbol 에러가 발생하는 경우, 링크 경로(-L 옵션), 라이브러리 지정 순서, 아키텍처 일치 여부를 다시 검토하면 대부분 해결됩니다.
성능 관리: EPD는 10ms 프레임 단위로 입력 데이터를 호출하는 것이 적합하며, Speex 인코더는 여러 프레임을 묶어서 전달함으로써 JNI의 경계 호출을 최소화할 수 있습니다.
로깅 옵션 활용: epdClientSetDebugOutput(0|1) 옵션을 적용하면 품질 진단, 운영 중 문제 추적에 도움이 됩니다.
UnsatisfiedLinkError: 로드 대상 경로, 파일명, 확장자, 파일 권한에 오타나 누락 부분이 없는지 꼼꼼하게 확인하세요.
Undefined symbols: 주로 잘못된 아키텍처(ARCH) 링크, 혹은 -L 옵션 누락에 의해 발생하니 빌드/링크 타이밍마다 아키텍처/경로를 재확인하면 예방할 수 있습니다.
Segfault: JNI 시그니처 불일치, 버퍼 크기, null 값 혹은 입력 객체의 미초기화 영역 등 시스템 자원 관리의 기본을 다시 한번 검토하세요.
모델 로드 실패: 절대 경로 지정, 권한, 파일명 오타 등 기본적인 환경 값을 다시 한번 체크하는 것이 중요합니다.
Tyche 엔진을 적용할 때는 헤더 파일의 include 경로와 네이티브 라이브러리 링크 구조를 OS와 아키텍처에 맞게 정확하게 설정하는 것이 중요합니다.
Java 래퍼에서는 절대경로 로딩 규칙을 따르도록 산출물을 잘 배치하면 추가적인 java.library.path 설정 없이도 정상적으로 동작할 수 있습니다.
또한 16kHz 샘플링과 10ms 프레임 기준을 유지하며, 실운영 단계에서는 로깅과 예외 처리, 아키텍처 일치 여부를 항상 점검해야 안정적인 서비스 운영이 가능합니다.
이번 글에서는 Tyche 엔진을 기반으로 Java 환경에서 Speex 인코딩과 EPD 라이브러리를 사용할수 있게 변환하고 안정적으로 구동하는 방법을 정리했습니다.
네이티브 품질과 Java 생산성을 동시에 확보하려면, OS/ARCH별 바이너리 정합성과 모델 경로, 프레임 처리 일관성이라는 3가지를 특히 엄격히 지키는 것이 핵심입니다.
본 가이드가 실시간 음성 전처리 파이프라인을 빠르게 Java 서비스에 녹이시는데 도움이 되셨길 바랍니다.
감사합니다.
DEVOTEE를 활성화 시키면
지금 작성한 댓글에 AI가 댓글을 달아줍니다.