Skip to main content

스트리밍 STT - GRPC

본 문서는 스트리밍 STT 중에서 GRPC로 구현하는 방식에 대한 가이드를 제공합니다.

연동예제

본 문서의 예제는 로컬 오디오 파일로부터 스트리밍 음성인식을 수행하는 방법을 설명해줍니다. 마이크와 같은 스트리밍 입력 장치로 API를 이용하고 싶은 경우, 파일로 읽어오는 코드 부분을 장치 인식을 수행하는 코드로 변경함으로써 사용하실 수 있습니다. GRPC 연동을 위한 proto 파일을 확인할 수 있습니다.

모든 스트리밍 요청에는 사용 한도가 있습니다. 한도를 초과하면 오류가 발생합니다.

인증 토큰 발급

스트리밍 STT API를 사용하기 위해서는 인증 토큰 발급 가이드를 통해 토큰을 발급받아야 합니다.

DecoderConfig

NameTypeDescriptionRequired
sample_rateinteger범위: 8000 ~ 48000, 단위: HzO
encodingAudioEncoding인코딩 타입 (참고. 지원 인코딩)O
use_itnbool영어/숫자/단위 변환 사용 여부 (default: true, 참고: 영어/숫자/단위 변환)X
use_disfluency_filterbool간투어 필터기능 사용 여부 (default: false, 참고: 간투어 필터)X
use_profanity_filterbool비속어 필터기능 사용 여부 (default: false, 참고: 비속어 필터)X

StreamingRecognitionResult

{
// 스트리밍 시작 기준 문장의 발화 시점 (단위: msec)
start_at: integer
// final이 true 일 경우 문자의 발화 시간, final 이 false일 경우 0 (단위: msec)
duration: integer
// 문장의 종료 여부
is_final: bool
// 대체 텍스트, 첫번째 값이 정확도가 가장 높은 결과
alternatives: [
SpeechRecognitionAlternative {
// 문장의 텍스트
text: string
// 단어(토큰)의 정보, is_final 이 true 일 경우만 제공
words?: [
WordInfo {
// 단어(토큰)의 텍스트, `|` 로 띄어쓰기 구분
text: string
// 문장의 시작 기준 단어(토큰)의 발화 시점 (단위: msec)
start_at: integer
// 단어(토큰)의 발화 시간 (단위: msec)
duration: integer
// 단어(토큰)의 정확도 (미지원)
confidence: float
}
]
}
]
}

샘플 코드

package main

import (
"context"
"encoding/json"
"flag"
"fmt"
"io"
"log"
"net/http"
"os"
"path/filepath"
"sync"
"time"

"github.com/grpc-ecosystem/go-grpc-middleware/util/metautils"
pb "github.com/vito-ai/go-genproto/vito-openapi/stt"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/metadata"
)

const ServerHost = "grpc-openapi.vito.ai:443"
const ClientId = "YOUR_CLIENT_ID"
const ClientSecret = "YOUR_CLIENT_SECRET"

var False = false
var True = true

func main() {
flag.Usage = func() {
fmt.Fprintf(os.Stderr, "Usage: %s <AUDIOFILE>\n", filepath.Base(os.Args[0]))
fmt.Fprintf(os.Stderr, "<AUDIOFILE> must be a path to a local audio file. Audio file must be a 16-bit signed little-endian encoded with a sample rate of 16000.\n")

}
flag.Parse()
if len(flag.Args()) != 1 {
log.Fatal("Please pass path to your local audio file as a command line argument")
}
audioFile := flag.Arg(0)

data := map[string][]string{
"client_id": []string{ClientId},
"client_secret": []string{ClientSecret},
}
resp, _ := http.PostForm("https://openapi.vito.ai/v1/authenticate", data)
if resp.StatusCode != 200 {
panic("Failed to authenticate")
}

bytes, _ := io.ReadAll(resp.Body)
var result struct {
Token string `json:"access_token"`
}
json.Unmarshal(bytes, &result)

ctx := context.Background()

var dialOpts []grpc.DialOption
dialOpts = append(dialOpts, grpc.WithTransportCredentials(credentials.NewClientTLSFromCert(nil, "")))
dialOpts = append(dialOpts, grpc.WithBlock())
dialOpts = append(dialOpts, grpc.WithTimeout(10*time.Second))
conn, err := grpc.Dial(ServerHost, dialOpts...)

if err != nil {
log.Fatalf("fail to dial: %v", err)
}

defer conn.Close()

md := metadata.Pairs("authorization", fmt.Sprintf("%s %v", "bearer", result.Token))
nCtx := metautils.NiceMD(md).ToOutgoing(ctx)
client := pb.NewOnlineDecoderClient(conn)
stream, err := client.Decode(nCtx)
if err != nil {
log.Printf("Failed to create stream: %v\n", err)
log.Fatal(err)
}

// Send the initial configuration message.
if err := stream.Send(&pb.DecoderRequest{
StreamingRequest: &pb.DecoderRequest_StreamingConfig{
StreamingConfig: &pb.DecoderConfig{
SampleRate: 8000,
Encoding: pb.DecoderConfig_LINEAR16,
UseItn: &True,
UseDisfluencyFilter: &False,
UseProfanityFilter: &False,
},
},
}); err != nil {
log.Fatal(err)
}

f, err := os.Open(audioFile)
defer f.Close()
if err != nil {
log.Fatal(err)
}
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
buf := make([]byte, 1024)
readed := 0
for {
n, err := f.Read(buf)
readed += n
if n > 0 {
if err := stream.Send(&pb.DecoderRequest{
StreamingRequest: &pb.DecoderRequest_AudioContent{
AudioContent: buf[:n],
},
}); err != nil {
log.Printf("Could not send audio: %v", err)
}
}
if err == io.EOF {
// Nothing else to pipe, close the stream.
if err := stream.CloseSend(); err != nil {
log.Fatalf("Could not close stream: %v", err)
}
return
}
if err != nil {
log.Printf("Could not read from %s: %v", audioFile, err)
continue
}
}
}()

_, err = stream.Recv()
if err != nil {
log.Fatalf("failed to recv: %v", err)
}

for {
resp, err := stream.Recv()
if err == io.EOF {
break
}
if err != nil {
log.Printf("Cannot stream results: %v", err)
break
}
if err := resp.Error; err {
log.Printf("Could not recognize: %v", err)
break
}
for _, result := range resp.Results {
if result.IsFinal {
fmt.Printf("final: %v\n", result.Alternatives[0].Text)
} else {
fmt.Printf("%v\n", result.Alternatives[0].Text)
}
}
}
wg.Wait()
}

오류 코드

스트리밍 STT - GRPC 의 오류 처리는 grpc error code를 이용하여 처리합니다.

CodeDescriptionNotes
16Unauthenticated인증실패
3InvalidArgument잘못된 파라미터 요청
1Canceled사용량 초과
13Internal서버 오류

참고사항

오디오 파일을 텍스트로 변환할 경우, 스트리밍 STT API를 이용하여 처리할 수도 있지만 일반 STT 가이드 문서에서 기술된 것처럼 일반 STT API로 변환 작업을 수행하는 것이 더 편리합니다.