데보션앱 소개페이지 바로가기
로그인 선택

신고하기

CLOSE
신고사유 (대표 사유 1개)
상세내용 (선택)
0/200
  • 신고한 게시글은 더 이상 보이지 않습니다.
  • 이용약관과 운영정책에 따라 신고사유에 해당하는지 검토 후 조치됩니다.
  • 허위 신고인 경우, 신고자의 서비스 이용이 제한될 수 있으니 유의하시어 신중하게 신고해 주세요.
(이 회원이 작성한 모든 댓글과 커뮤니티 게시물이 보이지 않고, 알림도 오지 않습니다.)

미리보기

커뮤니티

      1,234

      badge 23.06.15

      글 등록

      카테고리를 선택해주세요.

      DEVOTEE를 활성화 시키면
      지금 작성한 커뮤니티 글에 대해 1개의 댓글을 달아줍니다.

      버튼을 누르면 글 수정 시 ChatGPT가 작성한 댓글이 수정됩니다.

      임시저장함에 저장되었습니다. 저장일시 : 2022.5.17 14:29:08

      임시저장함

      제목을 선택하시면 이어서 작성이 가능하며,
      최대 20건까지 저장합니다.
      컨텐츠 유형, 제목, 저장일시, 삭제로 이뤄진 임시저장 목록
      컨텐츠 유형 제목 저장일 삭제

      데보션 블로그 게재 요청

      CLOSE
      • *
      • *

      본인인증

      효율적인 데보션 서비스 이용 및
      고객님의 소중한 개인정보보호를 위해
      본인인증을 진행해주세요. 본인인증 미 진행 시 로그인이 제한됩니다.
      본인인증 실패

      본인인증 로그인에 실패하였습니다.
      회원이 아니시거나 본인인증 등록이
      완료되지 않은 사용자입니다.

      회원정보 연결

      MPEG2-TS Seek시 HEVC Decoder에서 화면 깨짐 오류 수정 내용

      fromDK 21.07.12
      3,204 9 0

      FFmpeg에 HEVC Decoder버그가 있어서 수정한 내용으로 짧은(?) 글을 남김니다.

      MPEG2-TS 컨테이너파일에 HEVC/H.265 코덱으로 인코딩 된 컨텐츠를 FFmpeg으로 Seek 수행할 때 첫 화면이 깨지는 현상이 가끔 발생합니다. MP4(ISOBMFF)이나, mkv등 다른 컨테이너에서는 발생하지 않고, MPEG2-TS컨테이너의 MPEG2video, MP4visual, H.264/AVC 등 다른 비디오 코덱인 경우도 화면 깨짐이 발생하지 않고 오직 MPEG2-TS, HEVC 코덱 조합에서만 화면 깨짐이 발생하고 있습니다.

       

      MPEG2-TS

      우선 MPEG2-TS에 대해 설명하면, MPEG2-TS는 디지털 방송용으로 만들어진 포멧으로 모든 데이터를 188bytes단위로 패킷을 전송하게 되어 있습니다. RF에 멀티미디어 데이터를 전송할 목적으로 스트림의 중간 부분부터 수신하더라도 재생이 가능한 점이 특징입니다. 그래서 TV채널을 돌리는 경우 일정시간(약 1~2초)이 지나면 화면을 볼 수 있습니다. (TV채널을 변경할 때 빨리 화면이 나오게 하는 기술도 있는데 시간이 되면 추후 소개하도록 하겠습니다.)

      그러다 보니, 다른 파일기반 컨테이터 포멧들(MP4, MKV 등)과는 달리 영상의 특정 위치부터 재생하는 Random Access할 수 있는 시간 정보들을 포함하고 있지 않습니다. 즉, 다른 컨테이너 포멧은 Seek을 수행하는 경우 컨테이너 헤더에 있는 목차정보에서 시간 정보를 파싱하여 Seek Time으로 이동하여 재생이 가능합니다만, MPEG-2 TS는 별도로 목차정보가 모여 있지 않기 때문에 Seek를 수행하는 경우 188bytes 단위의 패킷을 일일이 파싱하여 해당 패킷의 시간정보를 확인하고 찾아야 하는 번거러움이 있습니다.

      예를 들면, 선생님이 “오늘은 챕터 3. 미분방정식부터 공부할 차례에요”라고 하면 학생들은 책의 앞부분의 목차를 보고 “챕터 3”의 페이지를 확인하고 해당 페이지로 Seek를 수행합니다. 일반적인 파일 기반의 컨테이너들은 이러한 목차 정보를 파일의 앞또는 뒷부분에 포함하고 있습니다. 그러나, 방송용(Broadcast)으로 만들어진 MPEG2-TS는 차례정보가 없기 때문에 학생들이 일일이 한페이지씩 넘기면서 찾게 됩니다. 물론 똑똑한 학생은 바이너리 서치와 같이 책은 반으로 나뉘어 분할 정복으로 시도하겠지요 ^^

      실제로 FFmpeg에서 MPEG2-TS컨테이너를 Seek를 수행하는 경우 바이너리 서치 방법으로 수행합니다. 컨텐츠의 첫 패킷의 Time Stamp와 마지막 패킷의 Time Stamp를 확인하고, 대략 중간 Byte 위치의 패킷의 Time Stamp를 확인 후, Seek Time과 비교 후, 다음 분할 검색 위치를 결정하는 방법을 사용합니다. Seek Time을 찾을 때 까지 계속 반복하여 분할 검색 합니다. 다른 컨테이너들은 목차 정보에 Random Access 포인트 정보를 표시 되어 있지만, MPEG2-TS의 경우 Decoder에서 스트림 중간부터 전달해도 재생이 될것이라는 가정이 있기 때문에 Seek Time 정보만 비교하고 Decoder로 전달하게 됩니다.

       

       

      HEVC Picture Type

      다음으로 HEVC/H.265 코덱은 이전 코덱인 H.264/AVC에서 달리진 부분이 Random Access에 대한 정보가 조금 더 자세히 표현할 수 있도록 추가되었습니다. 기존 코덱들에 존재하던 Key Frame, IDR(In Decoder Refresh)이외에 CRA(Clean Random Access), BLA((Broken Link Access)등의 Nal Unit이 새로 생겨나게 되었습니다. 기존 코덱들은 스트림의 중간부터 디코더에 입력하는 경우 Key Frame 또는 IDR이 나오기 전에는 디코딩을 하지 않도록 되어 있습니다. 일반적으로 이전 프레임을 참도하도록 되어 있어서 디코딩을 하더라도 영상이 깨져버립니다.

      HEVC에 추가된 CRA나 BLA같은 경우 이와 같이 Broken Link의 경우에도 트랜스 코딩 없이 스트림을 잘라서 병합할 수 있도록 정보를 줄 수 있도록 추가되었습니다. 자꾸 글이 길어지네요.

       

      아래의 Picture Type그림을 참고 바랍니다.

      RASL(Random Access Skipped Leading) RADL(Random Access Decodable Leading)

       

       

      Bug Fix

      자, 이제 배경설명은 그만하고, FFmpeg의 HEVC디코더 버그관련 이야기를 하면 FFmpeg의 MPEG2-TS Demuxer는 Seek시 Seek Time의 패킷을 Decoder로 전달하나 Key나 IDR부터 전송하지는 않도록 구현되어 있고, FFmpeg의 일반적인 디코더들은 Key Frame이나 IDR이 아닌 프레임부터 들어오면 Skip을 하도록 구현되어 있습니다만, HEVC디코더의 경우 BLA, CRA의 Broken Link의 경우 디코딩은 진행 하되 출력(디스플레이) 되지 않도록 구현이 되어 있었습니다. 그런데 IDR, CRA, BLA가 아닌 일반 픽쳐타입인 TRAIL부터 입력 되는 경우 깨진 상태로 출력이 되는 버그가 발생하였습니다. 

      아래와 같이 HEVC 디코더에서 CRA, BLA가 아닌 픽처에서 Reference프레임을 못찾은 경우,

      ret = 1로(FFmpeg에서는 오류는 음수값으로 리턴되도록 되어 있으나, 암묵적으로 ret = 1인 경우 무시할 만한 오류로 그냥 Skip하도록 동작함) 리턴하여 해당 프레임을 스킵하도록 수정하였습니다.

       

      diff --git a/ffmpeg-4.3.2/libavcodec/hevc_refs.c b/ffmpeg-4.3.2/libavcodec/hevc_refs.c
      index 4f6d985..5ebb9ba 100644
      --- a/ffmpeg-4.3.2/libavcodec/hevc_refs.c
      +++ b/ffmpeg-4.3.2/libavcodec/hevc_refs.c
      @@ -427,6 +427,10 @@ static int add_candidate_ref(HEVCContext *s, RefPicList *list,
               return AVERROR_INVALIDDATA;
      
           if (!ref) {
      +        if (s->nal_unit_type != HEVC_NAL_CRA_NUT && !IS_BLA(s)) {
      +            av_log(s->avctx, AV_LOG_ERROR, "Skip broken link\n");
      +            return 1;
      +        }
               ref = generate_missing_ref(s, poc);
               if (!ref)
                   return AVERROR(ENOMEM);
      @@ -478,7 +482,7 @@ int ff_hevc_frame_rps(HEVCContext *s)
                   list = ST_CURR_AFT;
      
               ret = add_candidate_ref(s, &rps[list], poc, HEVC_FRAME_FLAG_SHORT_REF, 1);
      -        if (ret < 0)
      +        if (ret < 0 || ret == 1)
                   goto fail;
           }
      
      diff --git a/ffmpeg-4.3.2/libavcodec/hevcdec.c b/ffmpeg-4.3.2/libavcodec/hevcdec.c
      index 1eaeaf7..50a7c89 100644
      --- a/ffmpeg-4.3.2/libavcodec/hevcdec.c
      +++ b/ffmpeg-4.3.2/libavcodec/hevcdec.c
      @@ -2828,6 +2828,9 @@ static int hevc_frame_start(HEVCContext *s)
               goto fail;
      
           ret = ff_hevc_frame_rps(s);
      +    if (ret == 1) {
      +        goto fail;
      +    }
           if (ret < 0) {
               av_log(s->avctx, AV_LOG_ERROR, "Error constructing the frame RPS.\n");
               goto fail;
      @@ -2979,6 +2982,9 @@ static int decode_nal_unit(HEVCContext *s, const H2645NAL *nal)
      
                   s->overlap ++;
                   ret = hevc_frame_start(s);
      +            if (ret == 1) {
      +                goto fail;
      +            }
                   if (ret < 0)
                       return ret;
               } else if (!s->ref) {
      

       

      코드 수정은 몇 줄 안되는데 잡담이 너무 기네요. 시간 나는대로 이전에 FFmpeg 패치한 내용을 계속 블로그에 남기도록 하겠습니다.

      댓글 0

      DEVOTEE를 활성화 시키면
      지금 작성한 댓글에 AI가 댓글을 달아줍니다.

      fromDK 님의 최신 블로그

      더보기
      동영상 기고하기