Skip to content

화질 조정 기능 구현

Seungheon Han edited this page Dec 1, 2024 · 1 revision

1️⃣ 화질에 영향을 미치는 요소

화질 조정 기능을 구현하기 위해 먼저 화질에 영향을 미치는 요소에 대해서 정리해 보았습니다.

비트레이트 (Bitrate)

  • 초당 처리되는 데이터양을 의미.
  • 고정 비트레이트(CBR)와 가변 비트레이트(VBR)가 있음.
  • 비트레이트가 높을수록 품질은 좋아지지만 파일 크기가 커진다.

해상도 (Resolution)

  • 영상의 가로x세로 픽셀 수입니다
  • 보통 1920x1080(Full HD), 3840x2160(4K) 등으로 표현합니다
  • 해상도가 높을수록 더 선명하지만 처리 부하가 증가합니다

프레임 레이트 (Framerate)

  • 초당 표시되는 이미지 수
  • 보통 24fps, 30fps, 60fps
  • 프레임레이트가 높을수록 부드럽지만 데이터 양이 증가합니다.

화질은 비트레이트, 해상도, 프레임레이트가 모두 맞물려서 결정됩니다. 또한, 화질별로 해상도가 정해져있고, 적정 비트레이트와 프레임레이트가 정해져있습니다.

화질별 권장 비트레이트

image

화질별 해상도

image

2️⃣ 송출 스트림 인코딩 옵션 변경

화질 기능을 구현하기 위해 위에서 학습한 화질별 비트레이트, 해상도, 프레임레이트를 송출 클라이언트 producer에서 설정 해주어야 했습니다.

export const ENCODING_OPTIONS = [
  { maxBitrate: 750000, scaleResolutionDownBy: 2, maxFramerate: 30 },
  { maxBitrate: 2500000, scaleResolutionDownBy: 1.5, maxFramerate: 30 },
  { maxBitrate: 4000000, scaleResolutionDownBy: 1, maxFramerate: 30 },
];
const producerConfig: Record<string, unknown> = {
  track: tracks[kind],
};

if (kind === 'video') {
  producerConfig['encodings'] = ENCODING_OPTIONS;
  producerConfig['codecOptions'] = {
    videoGoogleStartBitrate: 1000,
  };
}

transport
  .current!.produce(producerConfig)
  .then(producer => setProducers(prev => new Map(prev).set(kind, producer)));
  • 방송 송출 스트림을 생성하기 위한 produce 함수를 호출 할 때 스트림 타입이 video일 경우 encoding 옵션을 설정하도록 코드를 변경해 주었습니다.
  • encoidng 옵션은 각 화질별 비트레이트와 프레임레이트를 설정한 배열입니다.
  • encoding 옵션에 배열을 담아 스트림을 송출하면 스트림을 수신하는 consumer는 3가지 인코딩 옵션 중 하나를 선택하여 스트림을 받을 수 있습니다.

3️⃣ 수신 스트림 품질 설정

서버에서는 스트림을 수신받는 consumer 생성 코드를 변경해주어야 합니다.

export const QUALITY = {
  LOW: '480p',
  MID: '720p',
  HIGH: '1080p',
};

export const QUALITY_LAYER = {
  [QUALITY.LOW]: 0,
  [QUALITY.MID]: 1,
  [QUALITY.HIGH]: 2,
};
const consumer = await transport.consume({
  producerId: producer.id,
  rtpCapabilities,
  paused: false,
  preferredLayers: {
    spatialLayer: QUALITY_LAYER[QUALITY.MID], // 0,1,2 순으로 480p,720p,1080p. 기본값은 중간인 720p로 시작.
    temporalLayer: 2, // fps: 0,1,2순으로 기본 fps의 1/4, 1/2, 1 로 들어감.
  },
});
  • consume 함수를 호출할때 preferredLayers 옵션을 추가하여 클라이언트 producer가 송출하는 비디오 인코딩 옵션 중 한가지를 선택해 받아볼 수 있도록 수정합니다.
  • 초반부터 높은 화질을 선택하여 스트림을 받을 경우 과부화가 올 수 있기 때문에 중간 화질로 받아 볼 수 있도록 설정합니다.
@SubscribeMessage('setVideoQuality')
handleVideoQuality(@MessageBody() params: SetVideoQualityDto) {
  this.sfuService.setVideoQuality(params);
}
setConsumerBitrate(params: SetVideoQualityDto) {
    const { transportId, quality } = params;

    const consumers = this.consumers.get(transportId);
    const videoConsumer = consumers.find(consumer => consumer.kind === 'video');

    videoConsumer.setPreferredLayers({
      spatialLayer: QUALITY_LAYER[quality],
      temporalLayer: 2,
    });
  }
  • 클라이언트에서 화질 변경 버튼을 클릭했을때 화질을 변경하도록 socket 이벤트를 구현했습니다.
  • consumer 객체의 setPreferredLayers 함수를 통해 3가지 인코딩 스트림중 요청받은 스트림으로 변경하도록 설정해주었습니다.
  • spatialLayer은 해상도와 관련된 옵션으로 비디오의 비트레이트와 해상도와 연관되어있으며, temporalLayer은 프레임레이트와 관련된 옵션입니다.

4️⃣ 클라이언트 측 이벤트 발생

const handleVideoQuality = (videoQuality: VideoQuality) => {
  if (!socket) return;

  socket.emit('setVideoQuality', { transportId, quality: videoQuality });
  setVideoQuality(videoQuality);
};
  • 클라이언트에서 화질 변경 클릭이벤트가 발생할때 마다 서버에 구현한 setVideoQuality 이벤트가 발생하도록 코드를 수정해 주었습니다.

5️⃣ 생각해 봐야 할 문제

현재 화질 변경 기능은 정상적으로 동작하지만, 비디오의 화질 자체가 생각했던 것과는 조금 다른 모습이 있어 이 부분을 좀 더 개선해 봐야할 필요성을 느꼈습니다.

특히, 720p와 1080p 화질 차이가 육안으로 구분하기가 힘들고, 480p 화질은 육안으로 구분은 잘 되지만 초록 색으로 비디오가 부각되는 현상이 있어 영상 인코딩 정보들과 mediasoup 인코딩 옵션들에 대해서 더 학습하고 수정해봐야 할 것 같습니다.

👥 팀 강점

🧑‍💻 개발 일지

📌 ALL

📌 FE

📌 BE

💥 트러블 슈팅

📌 FE

📌 BE

🤔 고민

📚 학습 정리

📌 김광현

📌 백지연

📌 전희선

📌 한승헌

🤝 회의록

🗒️ 데일리 스크럼

💬 팀 회고


👨‍👩‍👧‍👦 소개

🌱 문화

🔨 기술 스택

⚙️ 서비스 아키텍쳐

🚧 CI/CD

🌊 Flow

💭 6주를 보내면서

Clone this wiki locally