라즈베리파이 4를 서버로 사용하고, 브라우저에서 음성 입력 → LLM 처리 → 음성 출력이 한 번에 이루어지는 전체 통합 시스템을 구현해 보겠습니다.

🏗️ 시스템 아키텍처

이 시스템은 사용자가 웹 페이지의 버튼을 누르고 말하면, 서버가 이를 처리하여 다시 목소리로 응답하는 구조입니다.


1. 사전 준비 (필수 설치)

먼저 라즈베리파이 터미널에서 필요한 라이브러리를 설치합니다.

# 1. Ollama 및 모델 설치
curl -fsSL https://ollama.com/install.sh | sh
ollama run gemma3:270m  # 가벼운 모델 추천

# 2. Python 라이브러리 설치
pip install flask flask-cors faster-whisper

# 3. Piper TTS 및 한국어 모델 준비 (앞서 안내한 ~/piper 디렉토리 기준)
# piper 실행 파일과 ko_KR-kyuri-low.onnx 파일이 해당 폴더에 있어야 합니다.

2. 백엔드 코드 (app.py)

Flask를 사용하여 음성 파일을 받고 처리하는 서버를 구축합니다.

from flask import Flask, request, send_file, render_template
from flask_cors import CORS
from faster_whisper import WhisperModel
import requests
import subprocess
import os

app = Flask(__name__)
CORS(app)

# STT 모델 로드 (라즈베리파이용 tiny 모델)
stt_model = WhisperModel("tiny", device="cpu", compute_type="int8")

@app.route('/')
def index():
    return render_template('index.html')

@app.route('/voice', methods=['POST'])
def process_voice():
    # 1. 클라이언트로부터 녹음된 데이터 받기
    audio_file = request.files['audio']
    audio_path = "input.wav"
    audio_file.save(audio_path)

    # 2. STT (음성 -> 텍스트)
    segments, _ = stt_model.transcribe(audio_path, language="ko")
    user_text = "".join([s.text for s in segments])
    print(f"User: {user_text}")

    # 3. LLM (Ollama 질의)
    ollama_response = requests.post(
        "http://localhost:11434/api/generate",
        json={"model": "gemma3:270m", "prompt": user_text, "stream": False}
    )
    answer_text = ollama_response.json().get('response', "죄송해요, 이해하지 못했어요.")
    print(f"AI: {answer_text}")

    # 4. TTS (텍스트 -> 음성 파일 생성)
    # Piper 경로를 본인의 환경에 맞게 수정하세요.
    tts_cmd = f'echo "{answer_text}" | ./piper/piper --model ./piper/ko_KR-kyuri-low.onnx --output_file output.wav'
    subprocess.run(tts_cmd, shell=True)

    # 5. 생성된 음성 파일 반환
    return send_file("output.wav", mimetype="audio/wav")

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000, debug=True)

3. 프론트엔드 코드 (templates/index.html)

브라우저에서 마이크를 제어하고 서버와 통신하는 코드입니다.

<!DOCTYPE html>
<html>
<head>
    <title>RPi Voice AI</title>
</head>
<body>
    <h1>라즈베리파이 음성 비서</h1>
    <button id="recordBtn">🎤 말하기 시작</button>
    <p id="status">대기 중...</p>
    <audio id="audioPlayer" controls></audio>

    <script>
        let mediaRecorder;
        let audioChunks = [];

        document.getElementById('recordBtn').onclick = async () => {
            if (!mediaRecorder || mediaRecorder.state === "inactive") {
                const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
                mediaRecorder = new MediaRecorder(stream);
                audioChunks = [];

                mediaRecorder.ondataavailable = e => audioChunks.push(e.data);
                mediaRecorder.onstop = async () => {
                    const audioBlob = new Blob(audioChunks, { type: 'audio/wav' });
                    const formData = new FormData();
                    formData.append('audio', audioBlob);

                    document.getElementById('status').innerText = "생각 중...";

                    const response = await fetch('/voice', { method: 'POST', body: formData });
                    const blob = await response.blob();
                    const url = URL.createObjectURL(blob);

                    const player = document.getElementById('audioPlayer');
                    player.src = url;
                    player.play();
                    document.getElementById('status').innerText = "답변 중!";
                };

                mediaRecorder.start();
                document.getElementById('recordBtn').innerText = "🛑 중지 및 전송";
                document.getElementById('status').innerText = "듣고 있어요...";
            } else {
                mediaRecorder.stop();
                document.getElementById('recordBtn').innerText = "🎤 말하기 시작";
            }
        };
    </script>
</body>
</html>

4. 실행 방법

  1. 디렉토리 구조 확인:
    project/
    ├── app.py
    ├── templates/
    │   └── index.html
    └── piper/  (piper 바이너리와 모델 파일 위치)
    

```

  1. 서버 실행: python app.py
  2. 접속: 같은 네트워크에 연결된 PC나 스마트폰 브라우저에서 http://라즈베리파이IP:5000 접속.

⚠️ 주의사항

  • HTTPS: 외부 네트워크나 최신 안드로이드/iOS 브라우저에서 마이크 기능을 쓰려면 HTTPS가 필요합니다. 로컬 테스트는 localhost 또는 127.0.0.1에서 가장 잘 작동합니다.
  • 성능: 라즈베리파이 4에서 STT(tiny)와 LLM(270M)을 동시에 돌리면 답변까지 약 3~7초 정도 소요될 수 있습니다.
728x90

+ Recent posts