2021.07.13 - [projects.] - RemoteCraft 01 - 계획 00
RemoteCraft 01 - 계획 00
한번도 안해본 기술은 자료를 잘 모아야 계획 및 설계를 하기가 쉽다... 행복회로 돌리는 중.. N유저 N게임 인스턴스 지원 간편한 저장 및 불러오기 지원 입출력 딜레이 최소화 멀티탭(2인용 게임
hellojai.tistory.com
Libretro 구동 테스트가 성공적으로 완료 되고 '큰' 그림에 대해 간략하게 썼던 계획을 전폭적으로 수정 및 보완 하는 시간을 가졌다.
그림이 나오는 것 만으로도 만족 할 순 없으니..!
세세한 컴포넌트에 대해 이야기 하기전에 개발하면서 기본 뼈대에 살을 붙히다 보니.. 자연스레 공부해야 할 것, 알아보아야 할 것, 추가해야 할 것이 늘었고 그중 이미지 데이터를 webRTC까지 전달하는 일련의 과정에 대해 이야기 해보고싶다!
기본적으로 게임 플레이를 위한 영상 + 소리 + 사용자 상호작용을 위해 남은 일들을 잠깐 정리 하자면...
- Libretro RetroPad Abstraction의 이해
- 비디오 오디오 인코딩
- 비디오 오디오 데이터 파이프라인
- Server-to-Peer WebRTC 클라이언트 구축
- 기본 상호작용(webRTC 연결, 게임 시작, 종료, 저장, 불러오기)을 위한 REST API 서버
- UI - html, javascript
WebRTC에서 필수적으로 필요한 부분들이 무엇인지 찾아보고 '게임을 외부로 연결할때 필요한 게 무엇일까?'를 고민하면서 디테일을 조금 더 붙혀서 그린 설계도다. Libretro인스턴스(게임 인스턴스) 감싸고 있는 Room 이라는 인스턴스를 하나 더 생성했다. 이 Room에는 게임인스턴스와 게임 인스턴스의 AV출력을 받고 user input의 통로가 될 WebRTC 세션을 관리하며 게임인스턴스와 외부와의 연결고리 역할을 해줄 객체이다.
WebRTC와 호환되는 브라우저가 지원하는 비디오 코덱은 기본적으로 VP8or9 + H.264. 오디오는 Opus라고 한다. 방 인스턴스 내부에 게임인스턴스에서 출력될 비디오 오디오 데이터를 인코딩하여 WebRTC 세션으로 relay해줄 media(video + audio) relayer도 추가해주었다.
WebRTC 컴포넌트, 인코더 컴포넌트, 룸 컴포넌트, Libretro 커포넌트 등 모듈단위로만 개발하고 테스트 하다보니 이들의 연결은 어떻게 구현할 것인가는.. 그림만 그려보고 깊게 생각해보지 못한 것 같다. 이 기회에 Go Channel 대해서도 공부해보고... 프로젝트 구조에 대해서도 고민해보는 시간을 가져야겠다고 생각함.
Go Channel
Go에서 언어차원으로 제공하는 Channel은 정말 강력한 것 같다. 개인적으로 이미지와 오디오데이터를 게임 인스턴스로부터 webRTC까지 전달하는 파이프라인을 만들때 Channel을 이용하여 의식의 흐름(?)대로 작성 할 수 있었다.(어떤 방식으로 하는 것이 더 리소스 상 효율적일지는 Go와 concurrency에 대해 더 많은 공부가 필요 할 것 같다.) 데이터를 외부에 쓰거나 읽어오거나 하는 기능(라이브러리 불러오기, 게임 저장, 게임 불러오기)들은 mutex를 이용하여 그 메모리를 점유 하는 것이 당연하게 느껴져 sync.mutex를 채용하였고 데이터를 여러 컴포넌트가 이어받는 형태의 구조는 Channel을 통해 흘러 보내주는 방식을 채용했다.
1. 채널을 통해 데이터를 send, 넣은 데이터는 꼭 consume 한다.
2. 채널은 sender 측에서 닫는다.
프로젝트에서 유용하게 활용한 패턴은:
- Pipeline Pattern
- Fan-in & Fan-out 패턴
이다.
다음 예제는 Libretro코어에서 프레임을 생성하는 로직부터 webRTC가 이미지를 클라이언트에 전송하기까지의 과정의 코드다. 에뮬레이터로부터 룸인스턴스, 룸인스턴스로부터 비디오인코더로 채널을 이어받는 Pipeline Pattern, 그리고 비디오 인코더로부터 여러개의 webRTC의 스트리밍 고루틴으로 이미지를 뿌려주는 Fan-out 패턴을 볼 수 있다. 객체를 생성 및 에러처리는 제외하고 코어 로직 위주로만 작성해 보았다.
// libretro API - 비디오가 새로고침 될때마다 실행되는 함수
func coreVideoRefresh(data unsafe.Pointer, width C.unsigned, height C.unsigned, pitch C.size_t) {
...
// imData를 outputImg에 렌더링하는 함수
image.Render(
pixelFormatConverterFn,
image.ScaleNearestNeighbour,
int(width), int(height), packedWidth, int(video.bpp),
imgData,
outputImg,
)
// outputImg에 그려진 이미지를 채널 전송
GlobalEmulator.imageChannel <- GameFrame{Image: outputImg}
}
func NewRoom(roomID string, game GameMetadata, cfg worker.Config) *Room {
// ... 방생성
// 게임실행 고루틴 생성
go func(game GameMetadata, roomID string) {
//... libretro-core에 필요한 정보 수집
// GlobalEmulator 인스턴스 생성 및 비디오, 오디오 채널을 반환 받음.
// 채널을 아래 인코더로 전송하는 streamVideo()함수에서 리시버로 이용함.
retroEmu, imageChannel, audioChannel := core.Init(roomID, inputChannel, store, libretroConfig)
room.imageChannel = imageChannel
room.emulator = retroEmu
room.audioChannel = audioChannel
// 비디오 스트리밍 고루틴 생성
go room.streamVideo(encoderW, encoderH, cfg.Encoder.Video)
room.emulator.StartGame()
}(game, roomID)
return room
}
func (r *Room) streamVideo(width int, height int, videoConf encoderConfig.Video) {
//... streamVideo에 필요한 특정 encoder 확인 및 초기화
videoRelay, encoderInput, encoderOutput := encoder.NewVideoRelayer(enc, width, height)
// 비디오 인코더 시작!
go videoRelay.Start()
defer videoRelay.Stop()
// 인코더의 아웃풋 채널에서 나오는 이미지를 webRTC의 프레임채널(이미지채널)로 전송하는 고루틴
go func() {
// ... recovery로직
// Fan-out 패턴 channel에서 나오는 이미지를 여러개의 채널에 모두 전송
for encodedImage := range encoderOutput {
for session := range r.rtcSessions {
session.FrameChannel <- rtc.WebFrame{Data: encodedImage.Image, Timestamp: encodedImage.timestamp}
}
}
}
// Room의 이미지채널을 통해 들어오는 이미지데이터를 인코더의 인풋채널로 전송함.
for gameImage := range r.imageChannel {
if len(encoderInput) < cap(encoderInput) {
encoderInput <- encoder.InFrame{Image: gameImage.Image, Timestamp: gameImage.Timestamp}
}
}
}
Do not communicate by sharing memory; instead, share memory by communicating. - Effective Go
'remote-craft' 카테고리의 다른 글
RemoteCraft 05 - WebRTC Pion! (0) | 2021.07.23 |
---|---|
RemoteCraft 04 - WebRTC 소개 (0) | 2021.07.23 |
RemoteCraft 02 - Libretro 00 (0) | 2021.07.19 |
RemoteCraft 01 - 계획 00 (0) | 2021.07.13 |
RemoteCraft - 00 시작 (0) | 2021.07.13 |