golomb 코드 읽어 내기

이상문
5 min readMay 16, 2023

H.264나 HEVC 스트림은 golomb 방식으로 인코딩이 되어 있다. 전공 분야가 아니어서 관심의 대상은 아니었는데, 관련 코드를 보다가 알고리즘을 정리해두고 이후에 다시 회상할 수 있도록 정리한다.

이 글에서는 인코딩하는 방식에 대해서는 관심의 대상이 아니다. H.264나 HEVC으로 압축된 스트림을 어떻게 복호화하는가에 중점을 두고자 한다.

이 코드를 읽어낼 때 가장 먼저 주목해야 할 것은 0의 개수이다. 0이 몇 개가 나오느냐에 따라 디코딩할 코드의 범위가 결정된다.

golomb U 방식

예를 들어, 0b00100000 이라는 1바이트 값이 있다고 하자. 이 경우 가장 앞의 두 개의 비트에 해당하는 0이 나오기 때문에 1 이후 2개의 비트까지가 디코딩할 비트 스트림의 범위가 된다. 즉, 5비트가 디코딩할 대상이 된다는 의미다.

이제 디코딩된 값을 찾으려면 계산이 필요한데, 1을 기준의 좌측의 0의 개수를 m 이라고 하고, 우측의 값을 b라고 한다면, 결과값은 (2^m — 1) + b와 같이 된다. 즉, 앞에 예를 든 1바이트 값에서 m은 2가 되고, b 또한 0이기 때문에 (2²-1)+0 = 3 이 결과값이다.

0b00100000 에서 디코딩 후 남은 끝의 3비트의 0은 다음 코드로 연결이 되어서 사용될 것이다.

하나 더 해보면 0b00101000 이라는 값은 (2²-1)+1 = 4 라는 값을 얻을 수 있다.

golomb S 방식

golomb U 방식을 이해한다면 golomb S 방식은 식은 죽 먹기다. 이 방식은 음의 값도 표현하기 위해서 사용하는 방식이다. golomb U 방식으로 얻은 결과값에 마지막 비트가 있는 경우는 결과값에 1을 더해서 우측으로 1비트 쉬프트 연산, 즉 2로 나누고, 없는 경우는 1비트 쉬프트 연산을 하되 음의 부호를 붙여주면 된다.

앞에 예를 들었던 0b00100000 에 대해서 이 방식을 적용하면 3은 마지막 비트가 1이기 때문에 양의 값을 가지고 1을 더한 4에서 1비트를 쉬프트해서 최종 결과값은 2가 된다.

0b00101000 에 대해서도 적용해보면 golomb U 방식으로 4라는 값을 얻을 수 있고, 마지막 비트는 0이므로 음의 값을 가지고 1비트 쉬프트하면 2이기 때문에 최종 결과값은 -2가 된다.

코드로 구현할 수 있는가?

물론 앞의 알고리즘을 잘 이해하고 작성하면 구현이 가능할 것이다. 그러나, 이런 복잡한 작업은 이미 뛰어난 연구자가 구현을 해 놓았다.

링크의 소스 코드의 BitstreamReader 클래스를 이용하면 쉽게 golomb 디코딩 결과를 얻을 수 있다.

TEST_CASE( "0b00100000에 대해 golombU를 읽으면 3 이어야 한다.",
"[BitstreamReader]" ) {
uint8_t testVal = 0b00100000;//0x20;
BitstreamReader reader(&testVal, 1);
REQUIRE(reader.getGolombU() == 3);
}

TEST_CASE( "0b00010000에 대해 golombU를 읽으면 7 이어야 한다.",
"[BitstreamReader]" ) {
uint8_t testVal = 0b00010000;//0x10;
BitstreamReader reader(&testVal, 1);
REQUIRE(reader.getGolombU() == 7);
}

TEST_CASE( "0b00110000에 대해 golombU를 읽으면 5 이어야 한다.",
"[BitstreamReader]" ) {
uint8_t testVal = 0b00110000;//0x30;
BitstreamReader reader(&testVal, 1);
REQUIRE(reader.getGolombU() == 5);
}

TEST_CASE( "0b00110000에 대해 golombS를 읽으면 3 이어야 한다.",
"[BitstreamReader]" ) {
uint8_t testVal = 0b00110000;//0x30;
BitstreamReader reader(&testVal, 1);
REQUIRE(reader.getGolombS() == 3);
}

TEST_CASE( "0b00101000에 대해 golombS를 읽으면 -2 이어야 한다.",
"[BitstreamReader]" ) {
uint8_t testVal = 0b00101000;//0x30;
BitstreamReader reader(&testVal, 1);
REQUIRE(reader.getGolombS() == -2);
}

몇 가지 값에 대해 catch2를 이용해서 테스트한 코드이다. BitstreamReader 사용하는 방법은 위의 코드로 직관적으로 파악할 수 있을 것이다.

마무리

H.264나 HEVC 스트림에 대해 적용되어 있는 golomb 코드를 디코딩하는 방법을 정리했다. 실제로 활용도는 크지 않으나 이후에 하나 더 게시하려고 하는 글에서 필수 기능이기 때문에 선제적으로 정리할 필요가 있었다.

--

--

이상문

software developer working mainly in field of streaming, using C++, javascript