최근 거의 2주일동안 apple의 하드웨어 H264 디코더인 VDA와 씨름을 하였다. 이건 H264의 깊이있는 지식을 가지고 쓴것이 아니고 단지 VDA로 하드웨어 디코딩을 하기위해 중점을 맞춘것이니 깊이있는 지식은 넘어가고 VDA나 VideoToolbox를 사용하기위해 참고하는 정도만으로 넘어가야 할 것이다.
VDA에 관한 기술문서로 https://developer.apple.com/library/content/technotes/tn2267/_index.html 페이지가 거의 유일하며, 그나마 참조할건 http://stackoverflow.com/questions/29525000/how-to-use-videotoolbox-to-decompress-h-264-video-stream 이 글의 댓글이 거의 유일하다. (물론 VideoToolbox와 VDA는 초기화 방법이 서로 다르지만, VDA에서 지원되는 기능은 VideoToolbox에서도 동일하게 지원하는거로 보이고 avcC라는 것을 생성하지 않아도 되기때문에 초기화가 더 수월하다.)
먼저 이론적인 기본은 위 stackoverflow의 댓글로 확인하면 될것이다. 약간 첨언한다면 H264는 3바이트, 또는 4바이트짜리 Start Code라고 불리는 코드로 각각의 프레임을 구분한다. 그리고 디코딩에 필요한 NALU 타입은 7 (SPS), 8 (PPS), 5 (IDR), 1 (non-IDR) 정도로 보인다.
디코더를 초기화하기 위해서는 SPS, PPS가 필요하며, 이 정보를 바탕으로 디코딩을 한다. (영어가 된다면 https://developer.apple.com/videos/play/wwdc2014/703/ 이것이 참고가 될것이고, 프레젠테이션 자료는 Resource 탭에서 받을 수 있다.)
문제는 Start Code가 3 또는 4바이트 이 한마디를 대충 넘겨집고 생각했다가 Start Code가 4바이트로 시작했으니 앞으로도 4바이트라고 생각하고 0x00 00 00 01 만 찾아서 디코딩을 하게되었다. (그렇게 거의 일주 반동안 삽질… ㅠㅠ) 하지만 Start Code가 3바이트 또는 4바이트라는것에 주의해야한다.
NVENC로 FullHD 해상도, LowLatency 프리셋으로 인코딩한 영상은 SPS와 PPS가 처음 한번만 생성되고 키프레임에 해당하는 IDC도 첫 프레임만 생성되고 나머지는 non-IDC프레임이다. (네트워크 전송에 목적을 둔 프리셋인지 용량이 큰 키 프레임은 무조건 필요한 처음 말고는 생성 안한다.)
4바이트짜리 Start Code만 찾아서 VDA 또는 VideoToolbox에 넘기면 아래와 같은 출력 결과를 보게될것이다.
이틀 전에서야 알게된것이지만 NVENC에서 위에 적은 내용대로 프리셋을 적용하여 FullHD 영상으로 인코딩하게되면 4바이트 Start Code로 시작하는 데이터 안에 3바이트 Start Code로 시작하는 프레임이 3개가 더 있게된다. 결국 인코더에서 데이터를 받게되면 아래와같이 프레임이 출력된다.
첫 프레임: 00 00 00 01 67 (SPS) 00 00 00 01 68 (PPS) 00 00 00 01 65 (IDC) 00 00 01 65 (IDC) 00 00 01 65 (IDC) 00 00 01 65 (IDC)
두번째 프레임: 00 00 00 01 61 (non-IDC) 00 00 01 61 (non-IDC) 00 00 01 61 (non-IDC) 00 00 01 61 (non-IDC)
이후 프레임은 두번째 프래임과 동일한 구조 (물론 괄호안에 비트스트림 데이터가 존재한다.)
처음엔 인코더에서 출력하는 프레임 하나에 저렇게 3바이트짜리 Start Code로 4등분하여 출력하게 될줄은 생각도 못했다. 그리고 3바이트짜리 Start Code는 무조건 00 00 01이니까 디코더에서 구분해서 디코딩하지 않을까… 한다면 VDA나 VideoToolbox에서 그렇게 해주지않는다. 그냥 저렇게 아래가 주욱 잘려서 늘려진 영상을 출력해 줄 뿐이다.
이것때문에 ffmpeg의 H264(x264) 디코더에서 어떻게 처리하나 확인하기위해 직접 최적화 옵션을 비활성한 라이브러리를 링크걸어 디버거로 돌려보니 4바이트짜리와 3바이트짜리의 Start Code를 모조리 찾아 각각 프레임을 배열로 담아서 디코딩을 하였다.
이것을 참조하여 동일하게 4바이트와 3바이트짜리 Start Code를 찾아내어 각 프레임을 디코더에 넣었더니 마찬가지로 위와같이 늘어진 이미지 한장 출력되고 세번은 이미지 출력이 되지않았다. 고민고민하여 인코더에서 출력해준 각 프레임 내 3바이트 Start Code를 제거하고 4바이트 Start Code 프레임 하나를 디코더에 넣어주니 그래도 마찬가지. 결국 마지막으로 각 3, 4바이트 Start Code를 4바이트 NAL Unit 사이즈로 가공하여 한번에 디코더에 4개의 프래임을 넣으니 완전한 이미지가 출력되었다. 아래는 색 변환하기 귀찮아서 Y 채널 데이터만 뽑아서 열어본 화면.
이제 ffmpeg는 빼버리고 VDA로 디코딩하도록하여 출력하게 만든다음 천천히 VideoToolbox를 사용해서 디코딩 하도록 해봐야겠다.
아래는 인코더로 출력 된 스트림을 파일로 기록한것이고, 하나는 맥에서 테스트 해본 코드이다. 어떤 파일은 SPS PPS다음에 IDC 프레임이 3바이트 Start Code로 이어져있는것도있으니(x264 인코더가 그랬던거같다.) 경우에따라 적절하게 분리하고 합쳐서 디코더에 프레임 정보를 넣어야할것이다.
여기까지. 나머지는 삽질하다가, 또는 필요에의해 검색하다 이 글까지 보게 된 사람의 역량에 따라서 나보다 덜 삽질할수도, 코드를 봐도 미궁에 빠지는 사람도 있을 듯.
아… H264에 대한 지식도 없이 맥에서 스트리밍 프로그램 만들겠다고 이주일동안 매일매일 시간쪼개서 네시간씩 자면서 삽질한거 생각하니 후련하기도하고 피곤함이 몰려온다… 그래도 한번 포기해서 ffmpeg를 썼던 코드 이제 VDA로 수정하고 다음주에는 VideoToolbox로 포팅해야겠다!
(피곤해서 글 정리도 귀찮다 -_-;;)