본문 바로가기

소프트웨어개발/개발잡담

C++에서 데이터를 비트단위로 읽기

제작년부터인가 HEVC(H.265)의 필요성이 짙어지면서, 우리팀에서 제공하던 인크립션 서버들도 HEVC 비디오를 암호화 할것이 요구 되었다. 아주 오래된 시스템부터 최근에 개발된 시스템까지 그 필요성이 대두 되었는데, 그중 한 시스템은 여타의 상용 HEVC parser를 도입하할 필요까지는 없이 헤더안의 몇가지 필드만 필요로 했다. 그런데 그 과정이 상당히 복잡했고, 범용 파서를 사용할 경우 헤더 전체를 다 파싱하지 않고 그 정보들만 쏙 쏙 빼내는게 불가능 했다. AVC(H.264)와 HEVC의 헤더 구조는 매우 흡사한데, 새로 추가된 몇가지 정보들이 서로 각각 옵셔널하게 물려있어서 따로 떼어낼 수가 없었다. 그래서 딱 그 기능만 수행하기 위한 Parser 를 만들어야 했다.


비디오 스트림 헤더 parser는 기본적으로 비트연산이 주를 이루기 때문에, 당연히 비트 단위로 읽어들이는 로직이 필요했다. 이에 여러가지를 고려해 보았으나, 추가적인 데이터 카피 없이, 버퍼에서 원하는만큼의 비트를 추출하여 숫자로 환산해주는 작은 코드를 찾기 어려웠다. 내가 원했던 것은, queue에서 데이터를 뽑아내듯이 그냥 원하는 비트만큼씩 쭉쭉 뽑아서 숫자를 참조하는 기능이었다. 


결국 여러가지 제약조건을 만족하는 코드를 찾지 못하고 (범용 라이브러리등의 유입은 불가능했다) 직접 간단하게 작성하였다. 며칠전 누군가가 이런류의 기능이 필요해 std::bitset 으로 구현하고있다고 하여 이 코드를 공유해드렸는데, 도움이 되었다고 하셔서 블로그에도 공유한다.


원본 코드에는 UE 값을 읽는 함수 등도 포함 되어 있지만, 검증을 제대로 한 부분이 아니라서 제외하였다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
#ifndef __BIT_READER__H_
#define __BIT_READER__H_
 
#include <iostream>
#include <stdexcept>
#include <algorithm>
#include <cmath>
 
class BitReader {
    const static unsigned char inbyte_mask[];
    public:
        BitReader(unsigned const char* buffer, std::size_t size)
            : _buffer(buffer), _size(size), _pos(0) {
        }
        BitReader() {
        }
        template<typename NewType> NewType read(std::size_t size) {
            // Read and move the position
            return readBits<NewType>(_pos, size);
        }
 
        template<typename NewType> NewType peek(std::size_t size) {
            // Read and stay in the same position
            std::size_t dummyPos = _pos;
            return readBits<NewType>(dummyPos, size);
        }
        std::size_t pos() {
            return _pos;
        }
    private:
        template<typename NewType> NewType readBits(std::size_t& pos, std::size_t size) {
            if(pos + size > _size*8) {
                throw std::runtime_error("out of range");
            }
            if(size > sizeof(NewType)*8) {
                throw std::runtime_error("Data is too big");
            }
            NewType value = 0;
            std::size_t bitToCopy = size;
            while(bitToCopy > 0) {
                std::size_t byteOffset = (pos<8)?0:(pos/8);
                std::size_t remainedBit = 8 - pos%8// 1~8
                std::size_t availableBit = std::min(remainedBit, bitToCopy);
                // Get remained bit
                unsigned char tmpData = _buffer[byteOffset]&inbyte_mask[remainedBit-1];
                // Clear unnecessary bit
                if(remainedBit > availableBit) {
                    tmpData >>= (remainedBit-availableBit);
                }
                // Copy the bits
                if(value !=0) {
                    value <<= availableBit;
                }
                value |= tmpData;
                // Adjust the size and position
                bitToCopy -= availableBit;
                pos += availableBit;
            }
            return value;
        }
 
        unsigned const char * _buffer;
        std::size_t _size;
        std::size_t _pos;
};
const unsigned char BitReader::inbyte_mask[] = {0x01,0x03,0x07,0x0f,0x1f,0x3f,0x7f,0xff};
 
#endif
 
cs



다음은 간단한 사용 방법과 exception 핸들링을 보여주는 테스트 코드이다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <iostream>
#include "BitReader.h"
 
using namespace std;
 
int main() {
    unsigned char abc[]={0x18,0x2c,0x50,0x91,0x07,0x31,0x48,0xd4};
    BitReader br(abc, 8);
 
    cout << "current bit position to read: " << br.pos() << endl;
    cout << "Get 3 : " << br.read<unsigned int>(3) << endl;
 
    cout << "current bit position to read: " << br.pos() << endl;
    cout << "Get 12 : " << br.read<unsigned int>(12) << endl;
 
    cout << "current bit position to read: " << br.pos() << endl;
    cout << "Get 7 : " << br.read<unsigned int>(7) << endl;
    cout << "current bit position to read: " << br.pos() << endl;
    cout << "Get 20 : " << br.peek<unsigned int>(20) << endl;
    cout << "current bit position to read: " << br.pos() << endl;
    cout << "Get 20 : " << br.read<unsigned int>(20) << endl;
    cout << "current bit position to read: " << br.pos() << endl;
    cout << "Get 12 : " << br.read<unsigned int>(12) << endl;
    cout << "current bit position to read: " << br.pos() << endl;
 
    // failed to read 9 bits into unsigned char (8 bits)
    try {
        cout << "Get 9 : " << br.read<unsigned char>(9) << endl;
    } catch (std::exception& e) {
        cout << "Failed to read: " << e.what() << endl;
    }
 
    // failed to read 32 bytes while only 10 bits are remaining
    try {
        cout << "Get 32 : " << br.read<unsigned int>(32) << endl;
    } catch (std::exception& e) {
        cout << "Failed to read: " << e.what() << endl;
    }
 
    cout << "Get 10: " << br.read<unsigned int>(10) << endl;
    cout << "current bit position to read: " << br.pos() << endl;
}
cs


  • 정태영 2015.03.01 14:50

    nal에는 EPB(0x00 00 03)란게 있어서 0x00 00 03 00 -> 0x00 00 00 변환을 해줄 수 있는 기능이 들어가야 정상 작동할거에요~

    • 에즈베어 곰발자 2015.03.02 10:20 신고

      좋은지적입니다. 제경우는 input단에서 그 변환을 마치고 넣어주는것으로 합의되었고 비트 리딩이 다른 용도로도 필요했기에 이코드에선 순수하게 unsigned 랑 ue만 읽도록 했고, raw 데이터를 바로 처리하는것은 지원하지 않습니다.