상세 컨텐츠

본문 제목

[FastAPI] (STT 기능 API 만들기)음성인식 데이터 처리 (WAV to PCM) #1

FastAPI

by grizzly 2025. 4. 16. 19:41

본문

음성인식 프로젝트에서 sound 파일이 들어올 경우

해당 데이터를 학습된 모델을 통하여 Text 데이터로 변하는 것을 Return해주는 API를 만들어야 한다.

 

이를 위해서

  1. Model 학습 시킨 후 해당 모델을 이용할 API를 Docker에 올리기
  2. 전체 단위로 볼 경우 Sound file을 받아서 Text파일로 Return해주는 API 만들기

이렇게 두 가지를 해야했다.

 

이 중 Whisper 파인튜닝을 통한 모델 학습을 먼저 하려 했으나 살짝 난항을 겪고 있어서 일단 구조를 먼저 만들기로 결심했다.

 

현재까지의 전체 디렉토리는 다음과 같다.

동작은 간단하다.

# main.py
from fastapi import FastAPI
import uvicorn
import os

from app.connectAPI.routers.wav_to_pcm import router as wav_to_pcm_router
from app.connectAPI.routers.pcm_to_text import router as pcm_to_text_router

app = FastAPI(title="WAV to Text API")

TEMP_DIRECTORY = "temp"

def start_server():
    os.makedirs(TEMP_DIRECTORY, exist_ok=True)

    import app.connectAPI.routers.wav_to_pcm as wav_to_pcm_module
    wav_to_pcm_module.TEMP_DIR = TEMP_DIRECTORY

    app.include_router(wav_to_pcm_router)
    app.include_router(pcm_to_text_router)

    return app

if __name__ == "__main__":
    app = start_server()  # 앱 초기화
    uvicorn.run(app, host="0.0.0.0", port=8000)

main.py는 다음과 같다.

전역 변수로 다루는 것은 TEMP_DIRECTORY 하나이다. 아직 저 부분에 대해서 어떻게 할 지 고민이 되어 아무 의미 없는 값으로 채워 넣었다. (메모리에 직접 접근하여 바로 처리할까 생각을 했지만, 기능 구현에 사용할 데이터가 어느 정도의 크기까지 받을 지에 대한 구체적 논의가 아직 따로 없었어서 보류 상태로 두었다)

 

그것을 제외하곤 FastAPI의 기능을 router로 등록하여 사용하는 구조만을 이용했다.

# pcm_to_text.py
# 아직 모델 개발이 전부 진행되지 않음 / 임시 코드로 대체할 생각
from fastapi import APIRouter
router = APIRouter()

async def convert_pcm_to_text(pcm_array):
    return "example data"

아직 모델 개발이 진행되지 않아서 임시 Return 값 만을 받을 수 있게 해놨다.

# struct.py
from pydantic import BaseModel
class TextResponse(BaseModel):
    text: str
    job_id: str
    processing_time: float
# wav_to_pcm.py

from fastapi import APIRouter, File, UploadFile, HTTPException
from app.connectAPI.routers.struct import TextResponse
import time
import os
import uuid
import aiofiles
import numpy as np
import wave

router = APIRouter()

TEMP_DIR = None

@router.post("/api/wav-to-text", response_model=TextResponse)
async def wav_to_text(file: UploadFile = File(...)):
    """
    WAV 파일을 받아서 텍스트로 변환하는 API
    """
    start_time = time.time()

    # 1. WAV 파일 받기
    if not file.filename.endswith('.wav'):
        raise HTTPException(status_code=400, detail="WAV 파일만 지원합니다.")

    # 고유 작업 ID 생성
    job_id = str(uuid.uuid4())

    # WAV 파일 임시 저장
    wav_path = os.path.join(TEMP_DIR, f"{job_id}.wav")
    async with aiofiles.open(wav_path, 'wb') as out_file:
        content = await file.read()
        await out_file.write(content)

    # WAV -> PCM 변환
    pcm_data = await convert_wav_to_pcm(wav_path)

    # 이 부분에 나중에 pcm_to_text 파일로 연결해야함
    text = "example data"

    os.remove(wav_path)

    processing_time = time.time() - start_time
    return TextResponse(
        text=text,
        job_id=job_id,
        processing_time=processing_time
    )


async def convert_wav_to_pcm(wav_path: str) -> np.ndarray:
    """
    WAV 파일을 PCM 데이터로 변환
    """
    try:
        with wave.open(wav_path, 'rb') as wav_file:
            # WAV 파일 정보 읽기
            n_channels = wav_file.getnchannels()
            sample_width = wav_file.getsampwidth()
            framerate = wav_file.getframerate()
            n_frames = wav_file.getnframes()

            # PCM 데이터 읽기
            pcm_data = wav_file.readframes(n_frames)

            # NumPy 배열로 변환
            if sample_width == 2:  # 16-bit
                pcm_array = np.frombuffer(pcm_data, dtype=np.int16)
            elif sample_width == 4:  # 32-bit
                pcm_array = np.frombuffer(pcm_data, dtype=np.int32)
            else:
                pcm_array = np.frombuffer(pcm_data, dtype=np.uint8)

            return pcm_array
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"WAV -> PCM 변환 오류: {str(e)}")

먼저 위의 struct.py의 경우 API 함수의 반환값을 해당 구조를 따르도록 했음. (추후 이 부분의 조작을 통하여 반환 값을 바꿀 예정)

 

UUID 부분은 동시에 여러 요청 들어와도 각 요청 고유하게 구분 가능하게하고, 비동기 처리가 가능하게 한다.

 

밑의 코드의 경우 wav_to_text로 일단 wav 확장자 형태의 파일만을 받게 하였다.

또한 일단 따로 저장 장소를 갖지 않는 형태로 구현을 진행하였으며, conert_wav_to_pcm 함수를 이용하여

wav 파일을 pcm 데이터로 변환하는 구체적인 코드를 분리하였다. 해당 방식으로 pcm 데이터를 wav_to_text 함수로 전달하는 역할을 한다.

 

각각의 역할이 살짝 달랐다.

  1. wav_to_text()
    1. 함수를 통하여 input을 받게 됨
    2. wav 확장자 형태인지 확인함
    3. 마지막 return을 함
  2. convert_wav_to_pcm()
    1. wav 파일을 pcm으로 변환하는 기능

여기까지가 router에 대한 구체적인 설명이다.

관련글 더보기