Как читать битовые целочисленные данные из двоичного файла? - PullRequest
2 голосов
/ 09 ноября 2011

У меня есть файл данных, сгенерированный аппаратно. Некоторые данные имеют ширину 4 бита, а некоторые - 12 бит. Matlab может обрабатывать эти данные, используя fread (fp, 1, 'ubit4 => uint16'). Я пытался сделать это в C ++, но, похоже, не существует простого способа. Я могу прочитать byte / int / long / long long и затем извлечь запрошенные биты. но кажется неэффективным иметь дело с сотнями мегабайтов данных.

Чтобы обобщить эту проблему, вопрос состоит в том, как читать битовое целое число (например, N от 1 до 64)? Кто-нибудь может порекомендовать хороший способ для чтения такого рода данных из файла на C ++?

Ответы [ 5 ]

3 голосов
/ 09 ноября 2011
#include <iostream>
#include <climits>
#include <stdexcept>
#include <cassert>

class bitbuffer {
    char buffer;
    char held_bits;
public:
    bitbuffer() :held_bits(0), buffer(0) {}
    unsigned long long read(unsigned char bits) { 
        unsigned long long result = 0;
        //if the buffer doesn't hold enough bits
        while (bits > held_bits) {
            //grab the all bits in the buffer
            bits -= held_bits;
            result |= ((unsigned long long)buffer) << bits;
            //reload the buffer
            if (!std::cin)
                throw std::runtime_error("");
            std::cin.get(buffer);
            held_bits = (char)std::cin.gcount() * CHAR_BIT;
        }
        //append the bits left to the end of the result
        result |= buffer >> (held_bits-bits);
        //remove those bits from the buffer
        held_bits -= bits;
        buffer &= (1ull<<held_bits)-1;
        return result;
    };
};

int main() {
    std::cout << "enter 65535: ";  
    bitbuffer reader;  //0x3535353335
    assert(reader.read(4) == 0x3);
    assert(reader.read(4) == 0x6);
    assert(reader.read(8) == 0x35);
    assert(reader.read(1) == 0x0);
    assert(reader.read(1) == 0x0);
    assert(reader.read(1) == 0x1);
    assert(reader.read(1) == 0x1);
    assert(reader.read(4) == 0x5);
    assert(reader.read(16) == 0x3335);
    assert(reader.read(8) == 0x0A);
    std::cout << "enter FFFFFFFF: ";
    assert(reader.read(64) == 0x4646464646464646);
    return 0;
}

Обратите внимание, что это читает из std::cin и выдает общую ошибку, если она не работает, но не должно быть слишком сложно настроить эти части в зависимости от ваших потребностей.

1 голос
/ 10 ноября 2011

Спасибо всем за предоставленные ответы, и все они очень полезны. Я не пытаюсь ответить на мой вопрос и получить кредит, но я чувствую, что обязан дать свой отзыв о прогрессе в этом вопросе. Все кредиты идут на вышеуказанные ответы.

Для достижения аналогичной функции в пакете matlab для чтения целых чисел bitN, я чувствую, что шаблонный класс не является правильным, поэтому я придумал несколько функций, чтобы иметь дело с <8-битными <16-битными <32-битными и <64-битными случаями и обрабатывать их отдельно . </p>

Моя идея такова: я копирую несколько байтов (от 2 до 8 байтов) в мой объект, обрабатываю эти байты и сохраняю необработанный байт для следующей обработки. Вот мой код и результаты тестирования (реализован только случай <8 бит): </p>

#include <math.h>
#include <memory.h>
typedef unsigned _int8 _uint8;
typedef unsigned _int16 _uint16;
typedef unsigned _int32 _uint32;
typedef unsigned _int64 _uint64;

class bitbuffer
{
    _uint8 *pbuf;
    _uint8 *pelem; //can be casted to int16/32/64
    _uint32 pbuf_len; //buf length in byte
    _uint32 pelem_len; //element length in byte
    union membuf
    {
        _uint64 buf64;
        _uint32 buf32;
        _uint16 buf16;
        _uint8 buf8[2];
    } tbuf;

    //bookkeeping information
    _uint8 start_bit; //
    _uint32 byte_pos; //current byte position
    _uint32 elem_pos;
public:
    bitbuffer(_uint8 *src,_uint32 src_len,_uint8 *dst,_uint32 dst_len)
    {
        pbuf=src;pelem=dst;
        pbuf_len=src_len;pelem_len=dst_len;
        start_bit=0;byte_pos=0;elem_pos=0;
    } //to define the source and destination
    void set_startbit(_uint8 bit) {start_bit=bit;}
    void set_bytepos(_uint32 pos) {byte_pos=pos;}
    void set_elempos(_uint32 pos) {elem_pos=pos;}
    void reset() {start_bit=0;byte_pos=0;elem_pos=0;} //for restart something from somewhere else
    //OUT getbits(IN a, _uint8 nbits); //get nbits from a using start and byte_pos
    _uint32 get_elem_uint8(_uint32 num_elem,_uint8 nbits) //output limit to 8/16/32/64 only
    {
        _uint32 num_read=0;
        _uint16 mask=pow(2,nbits)-1;//00000111 for example nbit=3 
        while(byte_pos<=pbuf_len-2)
        {
            //memcpy((char*)&tbuf.buf16,pbuf+byte_pos,2); //copy 2 bytes into our buffer, this may introduce redundant copy
            tbuf.buf8[1]=pbuf[byte_pos]; //for little endian machine, swap the bytes
            tbuf.buf8[0]=pbuf[byte_pos+1];
            //now we have start_bits, byte_pos, elem_pos, just finish them all
            while(start_bit<=16-nbits)
            {
                pelem[elem_pos++]=(tbuf.buf16>>(16-start_bit-nbits))&mask;//(tbuf.buf16&(mask<<(16-start_bit))
                start_bit+=nbits; //advance by nbits
                num_read++;
                if(num_read>=num_elem)
                {
                    break;
                }
            }
            //need update the start_bit and byte_pos
            byte_pos+=(start_bit/8);
            start_bit%=8;
            if(num_read>=num_elem)
            {
                break;
            }

        }
        return num_read;
    }
/*  
    _uint32 get_elem_uint16(_uint32 num_elem,_uint8 nbits) //output limit to 8/16/32/64 only
    {
        _uint32 num_read=0;
        _uint32 mask=pow(2,nbits)-1;//00000111 for example nbit=3 
        while(byte_pos<pbuf_len-4)
        {
            memcpy((char*)&tbuf.buf32,pbuf+byte_pos,4); //copy 2 bytes into our buffer, this may introduce redundant copy
            //now we have start_bits, byte_pos, elem_pos, just finish them all
            while(start_bit<=32-nbits)
            {
                pelem[elem_pos++]=(tbuf.buf32>>(32-start_bit-nbits))&mask;//(tbuf.buf16&(mask<<(16-start_bit))
                start_bit+=nbits; //advance by nbits
                num_read++;
                if(num_read>=num_elem)
                {
                    break;
                }
            }
            //need update the start_bit and byte_pos
            start_bit%=8;
            byte_pos+=(start_bit/8);
            if(num_read>=num_elem)
            {
                break;
            }

        }
        return num_read;
    }
    _uint32 get_elem_uint32(_uint32 num_elem,_uint8 nbits) //output limit to 8/16/32/64 only
    {
        _uint32 num_read=0;
        _uint64 mask=pow(2,nbits)-1;//00000111 for example nbit=3 
        while(byte_pos<pbuf_len-8)
        {
            memcpy((char*)&tbuf.buf16,pbuf+byte_pos,8); //copy 2 bytes into our buffer, this may introduce redundant copy
            //now we have start_bits, byte_pos, elem_pos, just finish them all
            while(start_bit<=64-nbits)
            {
                pelem[elem_pos++]=(tbuf.buf64>>(64-start_bit-nbits))&mask;//(tbuf.buf16&(mask<<(16-start_bit))
                start_bit+=nbits; //advance by nbits
                num_read++;
                if(num_read>=num_elem)
                {
                    break;
                }
            }
            //need update the start_bit and byte_pos
            start_bit%=8;
            byte_pos+=(start_bit/8);
            if(num_read>=num_elem)
            {
                break;
            }

        }
        return num_read;
    }

    //not work well for 64 bit!
    _uint64 get_elem_uint64(_uint32 num_elem,_uint8 nbits) //output limit to 8/16/32/64 only
    {
        _uint32 num_read=0;
        _uint64 mask=pow(2,nbits)-1;//00000111 for example nbit=3 
        while(byte_pos<pbuf_len-2)
        {
            memcpy((char*)&tbuf.buf16,pbuf+byte_pos,8); //copy 2 bytes into our buffer, this may introduce redundant copy
            //now we have start_bits, byte_pos, elem_pos, just finish them all
            while(start_bit<=16-nbits)
            {
                pelem[elem_pos++]=(tbuf.buf16>>(16-start_bit-nbits))&mask;//(tbuf.buf16&(mask<<(16-start_bit))
                start_bit+=nbits; //advance by nbits
                num_read++;
                if(num_read>=num_elem)
                {
                    break;
                }
            }
            //need update the start_bit and byte_pos
            start_bit%=8;
            byte_pos+=(start_bit/8);
            if(num_read>=num_elem)
            {
                break;
            }

        }
        return num_read;
    }*/
};

#include <iostream>
using namespace std;

int main()
{
    _uint8 *pbuf=new _uint8[10];
    _uint8 *pelem=new _uint8[80];
    for(int i=0;i<10;i++) pbuf[i]=i*11+11;
    bitbuffer vbit(pbuf,10,pelem,10);

    cout.setf(ios_base::hex,ios_base::basefield);
    cout<<"Bytes: ";
    for(i=0;i<10;i++) cout<<pbuf[i]<<" ";
    cout<<endl;
    cout<<"1 bit: ";
    int num_read=vbit.get_elem_uint8(80,1);
    for(i=0;i<num_read;i++) cout<<(int)pelem[i];
    cout<<endl;
    vbit.reset();
    cout<<"2 bit: ";
    num_read=vbit.get_elem_uint8(40,2);
    for(i=0;i<num_read;i++) cout<<(int)pelem[i]<<" ";
    cout<<endl;
    vbit.reset();
    cout<<"3 bit: ";
    num_read=vbit.get_elem_uint8(26,3);
    for(i=0;i<num_read;i++) cout<<(int)pelem[i]<<' ';
    cout<<endl;
    vbit.reset();
    cout<<"4 bit: ";
    num_read=vbit.get_elem_uint8(20,4);//get 10 bit-12 integers 
    for(i=0;i<num_read;i++) cout<<(int)pelem[i]<<" ";
    cout<<endl;
    vbit.reset();
    cout<<"5 bit: ";
    num_read=vbit.get_elem_uint8(16,5);//get 10 bit-12 integers 
    for(i=0;i<num_read;i++) cout<<(int)pelem[i]<<" ";
    cout<<endl;
    vbit.reset();
    cout<<"6 bit: ";
    num_read=vbit.get_elem_uint8(13,6);//get 10 bit-12 integers 
    for(i=0;i<num_read;i++) cout<<(int)pelem[i]<<" ";
    cout<<endl;
    vbit.reset();
    cout<<"7 bit: ";
    num_read=vbit.get_elem_uint8(11,7);//get 10 bit-12 integers 
    for(i=0;i<num_read;i++) cout<<(int)pelem[i]<<" ";
    cout<<endl;
    vbit.reset();
    cout<<"8 bit: ";
    num_read=vbit.get_elem_uint8(10,8);//get 10 bit-12 integers 
    for(i=0;i<num_read;i++) cout<<(int)pelem[i]<<" ";
    cout<<endl;
    vbit.reset();

    return 0;
}

результаты тестирования:

Bytes: b 16 21 2c 37 42 4d 58 63 6e
1 bit: 0000101100010110001000010010110000110111010000100100110101011000011000110
1101110
2 bit: 0 0 2 3 0 1 1 2 0 2 0 1 0 2 3 0 0 3 1 3 1 0 0 2 1 0 3 1 1 1 2 0 1 2 0 3 1
 2 3 2
3 bit: 0 2 6 1 3 0 4 1 1 3 0 3 3 5 0 2 2 3 2 5 4 1 4 3
4 bit: 0 b 1 6 2 1 2 c 3 7 4 2 4 d 5 8 6 3 6 e
5 bit: 1 c b 2 2 b 1 17 8 9 6 15 10 18 1b e
6 bit: 2 31 18 21 b 3 1d 2 13 15 21 23
7 bit: 5 45 44 12 61 5d 4 4d 2c 18 6d
8 bit: b 16 21 2c 37 42 4d 58 63 6e
Press any key to continue
1 голос
/ 10 ноября 2011

В моем проекте у меня те же требования чтения N битов из потока.

Исходный код доступен здесь: https://bitbucket.org/puntoexe/imebra/src/6a3d67b378c8/project_files/library/base

Или вы можете загрузить весь пакет с документацией из https://bitbucket.org/puntoexe/imebra/downloads и использовать только базовые классы. Это открытый исходный код (FreeBSD) и протестирован. Никаких других библиотек не требуется, кроме STL.

По сути, вы создаете поток, а затем подключаете к нему streamReader. Устройство чтения потоков может считывать блоки байтов или запрошенное количество битов. Несколько объектов streamReader могут быть подключены к одному потоку.

В настоящее время классы используются для чтения файлов JPEG или файлов медицинских изображений.

Работает на нескольких операционных системах (включая iOS), на машинах с большим и низким порядком байтов.

Пример:

#include "../../library/imebra/include/imebra.h"

// Open the file containing the dicom dataset
ptr<puntoexe::stream> inputStream(new puntoexe::stream);
inputStream->openFile(argv[1], std::ios_base::in);

// Connect a stream reader to the dicom stream. Several stream reader
//  can share the same stream
ptr<puntoexe::streamReader> reader(new streamReader(inputStream));
0 голосов
/ 10 ноября 2011

Ниже приведен пример того, как получить диапазон битов из переменной.

Пример имитирует, что некоторые двоичные данные были прочитаны из файла и сохранены в vector<unsigned char>.

Данные копируются (шаблон функции извлечения) из вектора в переменную. После этого функция get_bits возвращает запрошенные биты в новую переменную. Все просто!

#include <vector>

using namespace std;

template<typename T>
T extract(const vector<unsigned char> &v, int pos)
{
  T value;
  memcpy(&value, &v[pos], sizeof(T));
  return value;
}

template<typename IN, typename OUT>
OUT get_bits(IN value, int first_bit, int last_bit)
{
  value = (value >> first_bit);
  double the_mask = pow(2.0,(1 + last_bit - first_bit)) - 1;
  OUT result = value & static_cast<IN>(the_mask);
  return result;
}

int main()
{
  vector<unsigned char> v;
  //Simulate that we have read a binary file.
  //Add some binary data to v.
  v.push_back(255);
  v.push_back(1);
  //0x01 0xff
  short a = extract<short>(v,0);

  //Now get the bits from the extracted variable.
  char b = get_bits<short,char>(a,8,8);
  short c = get_bits<short,short>(a,2,5);
  int d = get_bits<short,int>(a,0,7);

  return 0;
}

Это простой пример без проверки ошибок.

Вы можете использовать шаблон функции извлечения для получения данных, начиная с любой позиции в векторе. Этот вектор имеет только 2 элемента, а размер короткого замыкания составляет 2 байта, поэтому аргумент pos для функции извлечения равен 0.

Удачи!

0 голосов
/ 09 ноября 2011

Ваш вопрос не очень конкретный, поэтому я могу порекомендовать только некоторые общие идеи.

Возможно, вы захотите прочитать файл кусками, например 4096 байт за раз (это типичный размер страницы)- хотя большие куски тоже должны быть хорошими (может быть даже 64 кБ или 512 кБ, просто поэкспериментируйте).Как только вы получили чанк, обработайте его из памяти.

Чтобы быть верным, мы должны сгенерировать чанк памяти как массив целевого числа.Например, для 4-байтовых целых чисел мы могли бы сделать это:

#include <cstdint>
#include <memory>
#include <cstdio>

uint32_t buf[1024];

typedef std::unique_ptr<std::FILE, int (*)(std::FILE *)> unique_file_ptr;

static unique_file_ptr make_file(const char * filename, const char * flags)
{
  std::FILE * const fp = std::fopen(filename, flags);
  return unique_file_ptr(fp ? fp : nullptr, std::fclose);
}

int main()
{
  auto fp = make_file("thedata.bin", "rb");

  if (!fp) return 1;

  while (true)
  {
    if (4096 != std::fread(reinterpret_cast<char*>(buf), 4096, fp.get())) break;
    // process buf[0] up to buf[1023]
  }
}

Я выбрал C-библиотеку fopen / fread вместо C ++ iostreams по соображениям производительности;Я не могу утверждать, что это решение основано на личном опыте.(Если у вас старый компилятор, вам может понадобиться заголовок <stdint.h>, и, возможно, у вас нет unique_ptr, и в этом случае вы можете просто использовать std::FILE* и std::fopen вручную.)

Альтернатива глобальному buf, вы также можете сделать std::vector<uint32_t>, изменить его размер до чего-то достаточно большого и прочитать непосредственно в его буфер данных (&buf[0] или buf.data()).

Если вынеобходимо прочитать целые числа длиной не 2, 4, 8 или 16 байт, вам придется читать в массив символов и извлекать числа вручную с помощью алгебраических операций (например, buf[pos] + (buf[pos + 1] << 8) + (buf[pos + 2] << 16) для 3-байтового целого числа).Если ваша упаковка даже не выровнена по байтам, вам придется приложить еще больше усилий.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...