¿Cómo definir el tipo de datos 24bit en C?

Tendré que trabajar mucho con datos de audio de 24 bits. Que es solo int sin firmar pero en lugar de 32 bits, es de 24 bits. Entonces, ¿cuál es la forma más fácil de definir y trabajar con datos de 24 bits en C?

Dependiendo de los requisitos yo usaría un campo de bits para ello:

struct int24{ unsigned int data : 24; }; 

O, si una separación es más fácil, solo use 3 bytes (caracteres unsigned char ). Puede forzar el empaquetado de la estructura si no desea que se rellene.

[edit: veo que la etiqueta de C++ fue eliminada, pero la dejo aquí sin importar] Si te sientes más cómodo con C ++, puedes usar algo como lo siguiente:

 const int INT24_MAX = 8388607; class Int24 { protected: unsigned char value[3]; public: Int24(){} Int24( const Int24& val ) { *this = val; } operator int() const { /* Sign extend negative quantities */ if( value[2] & 0x80 ) { return (0xff << 24) | (value[2] << 16) | (value[1] << 8) | value[0]; } else { return (value[2] << 16) | (value[1] << 8) | value[0]; } } Int24& operator= (const Int24& input) { value[0] = input.value[0]; value[1] = input.value[1]; value[2] = input.value[2]; return *this; } Int24& operator= (const int input) { value[0] = ((unsigned char*)&input)[0]; value[1] = ((unsigned char*)&input)[1]; value[2] = ((unsigned char*)&input)[2]; return *this; } Int24 operator+ (const Int24& val) const { return Int24( (int)*this + (int)val ); } Int24 operator- (const Int24& val) const { return Int24( (int)*this - (int)val ); } Int24 operator* (const Int24& val) const { return Int24( (int)*this * (int)val ); } Int24 operator/ (const Int24& val) const { return Int24( (int)*this / (int)val ); } Int24& operator+= (const Int24& val) { *this = *this + val; return *this; } Int24& operator-= (const Int24& val) { *this = *this - val; return *this; } Int24& operator*= (const Int24& val) { *this = *this * val; return *this; } Int24& operator/= (const Int24& val) { *this = *this / val; return *this; } Int24 operator>> (const int val) const { return Int24( (int)*this >> val ); } Int24 operator<< (const int val) const { return Int24( (int)*this << val ); } operator bool() const { return (int)*this != 0; } bool operator! () const { return !((int)*this); } Int24 operator- () { return Int24( -(int)*this ); } bool operator== (const Int24& val) const { return (int)*this == (int)val; } bool operator!= (const Int24& val) const { return (int)*this != (int)val; } bool operator>= (const Int24& val) const { return (int)*this >= (int)val; } bool operator<= (const Int24& val) const { return (int)*this <= (int)val; } /* Define all operations you need below.. */ 

La forma limpia y portátil, asumiendo que sus muestras son poco endian y sin firma:

 static inline uint32_t getsample(uint8_t *buf, size_t pos) { return buf[3*pos] + 256UL*buf[3*pos+1] + 65536UL*buf[3*pos+2]; } static inline void putsample(uint8_t *buf, size_t pos, uint32_t sample) { buf[3*pos]=sample; buf[3*pos+1]=sample/256; buf[3*pos+2]=sample/65536; } 

Arreglarlo para que funcione con valores firmados es un poco más de trabajo, especialmente si desea mantenerlo portátil. Tenga en cuenta que el rendimiento puede ser mucho mejor si convierte una ventana completa de muestras a un formato más sano antes de procesarlo, y luego vuelva a convertir cuando haya terminado.

Algo a lo largo de estas líneas:

 struct Uint24 { unsigned char bits[3]; // assuming char is 8 bits Uint24() : bits() {} Uint24(unsigned val) { *this = val; } Uint24& operator=(unsigned val) { // store as little-endian bits[2] = val >> 16 & 0xff; bits[1] = val >> 8 & 0xff; bits[0] = val & 0xff; return *this; } unsigned as_unsigned() const { return bits[0] | bits[1] << 8 | bits[2] << 16; } }; 

Utilice un tipo de datos que sea lo suficientemente grande como para contener 24 bits de datos. Son int32_t o uint32_t ambos definidos en stdint.h

Está trabajando con datos de audio, por lo que desea que la adición funcione (la necesita para mezclar). Además, tener algunos 8 bits adicionales disponibles no es algo malo, ya que le brinda aproximadamente 24 dB de rango dynamic adicional, que necesitará mezclar varias fonts que aprovechen al máximo el rango dynamic de 24 bits.

Tenga en cuenta que solo estaba preguntando sobre un tipo de datos para usar en muestras de 24 bits. Si va a leer un flujo de paquetes de muestra de 24 bits muy comprimido, tendrá que dividirlo. Primero debes saber si la secuencia es big endian o low endian . Luego puedes usar algo como esto para reorganizar la secuencia en muestras:

 uint32_t bitstream_BE_to_sample(uint8_t bits[3]) { return (bits[0] << 16) | (bits[1] << 8) | (bits[2]); } uint32_t bitstream_LE_to_sample(uint8_t bits[3]) { return (bits[2] << 16) | (bits[1] << 8) | (bits[0]); } uint8_t *bitstream; uint32_t *samples; for(;;) { *samples = bitstream_XX_to_sample(bitstream); samples++; bistream += 3; if(end_of_bitstream()) break; } 

Podrías hacer algo como esto;

 union u32to24 { unsigned int i; char c[3]; }; 

en su código manipule los valores usando u32to24.i por ejemplo

 u32to24 val = //... val.i += foo val.i -= bar // etc 

a continuación, la salida de los caracteres para obtener los 24 bits

 fprintf("%c%c%c",val.c[0],val.c[1],val.c[2]); 

Proviso: esto es lo primero que me viene a la cabeza, por lo que puede haber errores y puede haber mejores formas de hacerlo.

Este trabajo para mí:

 typedef unsigned char UInt24[3]; 

La forma en que lo hago para manejar tanto enteros de 24 bits firmados como sin firmar es solo mediante una sum / resta antes de empaquetar y después de desempaquetar, así:

 void int24_write(uint8_t* bytes, int32_t val) { // Add to make the value positive. val += (1 << 23); // Make sure the value is in an acceptable range of // [-2^23, +2^23). assert(val >= 0 && val < (1 << 24)); // Pack the data from 32-bit to 24-bit. bytes[0] = (uint8_t)(val & 0xff); bytes[1] = (uint8_t)((val >> 8) & 0xff); bytes[2] = (uint8_t)(val >> 16); } int32_t int24_read(const uint8_t* bytes) { // Unpack the data from 24-bit to 32-bit. return (bytes[0] | (bytes[1] << 8) | (bytes[2] << 16)) - (1 << 23); } 

No estoy seguro de la velocidad óptima, pero es simple y sin sucursales. Puede empaquetar y descomprimir enteros en el rango: [-2^23, +2^23) . Si alguien tiene sugerencias sobre cómo hacerlo de manera más eficiente, sería todo oídos.

Tiendo a usarlo de una manera donde los datos a empacar son bastante pequeños, tal vez solo 3 o 4 enteros en promedio (muchos arreglos pequeños comprimidos de 32 bits a 24 bits o más pequeños), y típicamente solo tenemos 3 o 4 enteros para empaquetar / desempaquetar a la vez con un patrón de acceso aleatorio para recuperar estos pequeños arreglos de 24 bits. Dicho esto, tiendo a tener varios paquetes para empaquetar / desempaquetar a la vez, pero nuevamente solo un número pequeño como 3 o 4, no docenas o cientos o más.

Si solo necesitas sin firmar, entonces simplemente:

 void uint24_write(uint8_t* bytes, uint32_t val) { // Make sure the value is in an acceptable range of // [0, +2^24). assert(val >= 0 && val < (1 << 24)); // Pack the data from 32-bit to 24-bit. bytes[0] = (uint8_t)(val & 0xff); bytes[1] = (uint8_t)((val >> 8) & 0xff); bytes[2] = (uint8_t)(val >> 16); } uint32_t uint24_read(const uint8_t* bytes) { // Unpack the data from 24-bit to 32-bit. return (bytes[0] | (bytes[1] << 8) | (bytes[2] << 16)); } 

En ese caso, los enteros sin signo pueden estar en el rango, [0, +2^24) .