NVIDIA Video Codec SDK의 Encoder로 생성한 h264 mp4 미디어 파일의 Player 호환성 이슈

bemong1·2023년 12월 27일
0

Nvidia Video-Codec-SDK의 NvEncoder

1. NvEncoder로 Encoding한 h264 mp4 동영상을 특정 Player에서 Seeking시 이슈 발생

  • NvEncoder로 인코딩하여 생성된 패킷을 FFmpeg로 Muxing 하여 동영상 파일을 생성하는 과정을 개발
  • Apple Quick Time Player, Kakao(Daum) Pot Player, VLC Player 및 기타 상용 Player와 PC기반 네이티브 플레이어들에서는 대체적으로 별 문제 없이 작동
  • 반면, Edge, Chrome Player 등의 웹 플레이어, Android Native 플레이어의 경우, 처음부터 재생 시, 정상적으로 작동하지만, seeking 등의 playback 행위를 진행하게 되면 이슈가 발생
    • (플레이어에 따라 증상이 조금씩 상이함) 아래와 같이 화면이 깨지거나, 화면과 시간이 멈춰서 유지 되는 현상이 발생


2. 검증 진행

  • Test 1 : NvEncoder로 인코딩 된 mp4 영상을 ts로 demux/mux후, 다시 mp4로 demux/mux 하였을 떄, 정상 동작함을 확인 (mp4 -> ts -> mp4)

    • 해당 과정은 단순히 demux/mux과정일 뿐이므로 NvEncoder 문제가 아님이 확인

  • Test 2 : NvEncoder로 인코딩 시, All-I 형태로 인코딩하여 테스트 (모든 프레임을 Intra Frame으로 인코딩)

    • 해당 과정을 통해 인코딩 된 영상의 경우 정상 동작이 확인 됨
      • 아래와 같은 All-I 영상은 정상 동작
      • "Test 2"를 통해, Intra Frame과 연관이 있음에 초점이 맞춰 짐

  • Test 3 : 앞서, NvEncoder로 인코딩 된 mp4 영상과 2차례의 demux/mux 과정을 거친 mp4 영상(mp4 -> ts -> mp4)을 비교 (이하 defect영상, normal영상으로 표현 변경)

    • 두 영상의 nal unit들이 최대한 비슷하게 구성되도록 Encoder 및 Muxing Parameter 튜닝
      • BitStream Filter를 이용해 헤더 및 패킷의 nal unit들을 비교해가며 최대한 비슷한 형태로 인코딩 및 muxing이 되도록 진행
      • 두 영상 모두 packet 및 nal unit, 기타 항목들을 모두 동일하게 세팅
    • 이상 현상 확인 : defect 영상을 Bitstream Filter로 분석하였을 떄, 모든 패킷이 key frame으로 검출되는 이상 현상이 확인 됨

      • 위 사례를 보면 2번째 프레임 패킷의 경우 실제로 P-Frame으로 인코딩 되었지만 defect 영상의 BitstreamFilter 체크 시 Key Frame으로 표기되는 문제 발견
      • 분석 결과대로라면, ffprobe를 사용하여 type을 출력하였을 경우 'IIIIIIIIII...."의 형태로 출력되어야 하나, "Ippppppp...." 형태로 정상적인 출력을 확인
      • 마찬가지로 Hex editor로 파일을 열어 Nal type을 확인했을 때도 Key Frame코드가 아닌 B, P프레임 start code(0x000001 | 0x00000001) + (0x01 | 0x21 | 0x41 | 0x61) 의 형태로 정상적인 데이터를 보여 줌
      • 해당 패킷이 key frame인 경우 ’0x00000161’이 아닌 ‘0x00000165’로 표기되어야 함
        • 모든 프레임이 key frame이라면 ‘0x00000161’은 단 하나도 존재하지 않아야 하지만 ‘0x00000061’으로 탐색 시 해당 Nal unit들이 존재함이 확인 됨
        • 위 정황으로 Non-IDR에 해당하는 nal unit은 인코딩 과정에서 정상적으로 생성되었으나, 다른 어딘가에서 key frame정보를 보관하고 있으며, 이상이 있는 플레이어들에서는 이를 활용한다는 가능성에 대해 생각해 보게 됨
  • Test 4 : mp4 container atom

    • Nal unit에서 제공하는 key frame정보 외에도, mp4 container내 atom 목록에 stss(Sync sample atom)라는 데이터가 존재하며, 해당 데이터는 Payload의 Intra Frame에 대한 정보를 제공하기 위해 존재함을 확인

    • mp4 atom 확인을 위한 python source (출처 : gpt 4.0)

      ```python
      #!/usr/bin/env python3
      #coding : utf-8
      
      import struct
      
      def read_box(f):
          size, = struct.unpack(">I", f.read(4))
          type = f.read(4).decode("utf-8")
          return size, type
      
      def parse_boxes(f, indent=0, parent_type=None):
          while True:
              try:
                  start_pos = f.tell()
                  size, type = read_box(f)
                  f.seek(start_pos + size)
      
                  indent_str = "  " * indent
      
                  print(f"{indent_str}{type} (Size: {size})")
      
                  if type in ['moov', 'trak', 'mdia', 'minf', 'stbl']:
                      f.seek(start_pos + 8)  # Move to the start of child boxes
                      parse_boxes(f, indent + 1, type)
                      f.seek(start_pos + size)  # Move to the next box's start position
      
              except struct.error:
                  break
      
      def main(filename):
          with open(filename, 'rb') as f:
              parse_boxes(f)
      
      if __name__ == "__main__":
          # main("defect_test/test_defect.mp4")
          main("defect_test/test_normal.mp4")
      ```
    • 실행 결과 예시

      • Normal 영상에서는 이 ‘stss’항목이 존재함이 확인되었으나, defect 영상에서는 ’stss’가 존재하지 않음을 확인
      • Normal 영상에서 확인 된 stss 및 해당 데이터 크기(180 bytes)
      • 추가로, 해당 내용을 Hex editor로 확인하여 존재함을 확인
        • stss (0x73747373)와 Payload의 사이즈를 표기하는 전방 4byte(0x000000B4)가 존재함을 확인, ‘xB4’ = 180bytes이므로 해당 stss가 python에서 필터링한 부분과 일치함을 확인
        • stss 이 후, 작성된 172 Bytes의 Payload 데이터는 I-Frame의 위치 정보를 4Bytes씩 작성 되어 있음. 해당 데이터의 경우 후방 8Bytes(0x0000000000000029)를 통해 41개의 I-Frame에 대한 정보를 표기하고 있음을 알 수 있음. 해당 영상은 실제 1001개 Frame, 25 Gop로 인코딩 되었으며 이를 통해 역산해보면 41개 I-Frame로 구성이 맞음

      • 좀 더 확실한 검증을 위해 normal 영상에서는 해당 stss 항목을 제거하여 확인
        • 결과 : 이상현상 확인(key frame으로 검출되면 안됨)
      • defect 영상에는 동일한 stss를 삽입하여 BitStream Filter 헤더 내용을 출력
        • 결과 : 정상동작 확인
      • 결과적으로, stss를 제거한 normal 영상에서는 비정상적인 Key Frame 검출이 확인되었으며, defect 영상에 normal 영상에 있던 stss를 삽입한 경우 정상적으로 출력 됨을 확인

3. 해결

  • NvEncoder로 인코딩 된 Payload Data를 FFmpeg로 Muxing하는 과정에서 Frame Type를 강제로 삽입시켜주는 방식으로 해결
    • 실제 NvEncoder로 Encoding된 Payload 내에 I, B, P가 명확하게 명시 된 Nal Unit이 존재함이 확인되었고 해당 내용이 Muxing 시 반영이 되어야 하지만, 무슨 이유에서인지 전혀 반영이 되지 않아 해당 시퀀스를 계산하여 강제로 삽입하는 방식으로 진행
      • ffmpeg에서 패킷을 먹싱하는 과정에서 mp4 atom내 stss정보가 생성되어야 하지만 생성되지 않음
    • NvEncoder로 인코딩 한 Payload 내에서는 IDR과 Non-IDR 구별이 쉽지가 않아, 설정한 Gop 수치를 통해 IDR과 Non-IDR 카운트를 판단하여 Packet에 직접 내용을 수정하여 삽입하는 방법으로 진행
      ```cpp
      ...
      // Constraint GOP 사용시에만 가능
      if (m_nFrameNumber % m_nGop == 0)
          packet->flags |= AV_PKT_FLAG_KEY;
      else
          packet->flags &= ~AV_PKT_FLAG_KEY;
      ...
      ```

4. 결론

  • h.264 데이터를 Packetizing하여 nal unit 정보만을 활용해 수행되는 Player도 존재하며, 반면 mp4 atom 정보에 더 의존성을 가진 Player도 존재. 이런 다양한 Player들의 입맛을 맞추기 위해 본 작업이 진행되었음
  • NvEncoder로 Encoding하는 경우(+ffmpeg로 Demuxing 하는 경우) mp4 atom내 stss정보가 포함되지 않는 것으로 파악되어 해당 이슈에 친화적이지 않음이 확인되었으며, mp4 atom 정보에 더 의존성을 가진 플레이어 재생 시 문제가 발생할 수 있어 이를 위해 별도의 작업이 필요함
profile
개발자

0개의 댓글