How to define 24bit data type in C?
I will have to work a lot with 24-bit audio data. Which is just unsigned int but instead of 32bit, it is 24bit. So what is the easier way to define and work with 24bit da开发者_运维百科ta in C?
Depending on the requirements I'd either use a bitfield for it:
struct int24{
unsigned int data : 24;
};
Or, if a separation is easier, just use 3 bytes (unsigned char
s). You can force the struct to be packed if you don't want it to be padded.
[edit: I see the C++
tag was removed, but I'm leaving it here regardless]
If you're more comfortable with C++, you can use something like the following:
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.. */
The clean and portable way, assuming your samples are little endian and unsigned:
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;
}
Fixing it up to work for signed values it a little more work, especially if you want to keep it portable. Note that performance might be much better if you convert a whole window of samples to a saner format before processing it, then convert back when you're done.
Use a data type that's large enough to hold 24 bits of data. That are int32_t
or uint32_t
both defined in stdint.h
You're working with audio data, so you want addition working (you need it for mixing). Also having some additional 8 bits available is not a bad thing, as it gives you about 24dB of extra dynamic range, which you'll need mixing several sources that make full use of the 24bit dynamic range.
Note that you were just asking about a datatype to use for 24 bit samples. If you're going to read a tighly packed stream of 24bit sample frames, you'll have to split that one up. First you must know if the stream is big endian or low endian. Then you can use something like this to rearrange the stream into samples:
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;
}
Something along these lines:
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;
}
};
You could do something like this;
union u32to24 {
unsigned int i;
char c[3];
};
in your code maniplate the values using u32to24.i
for example
u32to24 val = //...
val.i += foo
val.i -= bar
// etc
then output the chars to get the 24 bits
fprintf("%c%c%c",val.c[0],val.c[1],val.c[2]);
Proviso: this is the first thing to pop into my head thus there may be errors and there may well be better ways of doing this.
精彩评论