Frage

Ich bin Programmierung in C ++. Ich muß Schwimmer (Normalisieren auf [-1.0,1.0]).

eine 24-Bit-Ganzzahl mit Vorzeichen (in einem 3-Byte-Array gespeichert ist) konvertieren

Die Plattform ist MSVC ++ auf x86 (die das Eingabemittel little-endian).

Ich habe versucht, diese:

float convert(const unsigned char* src)
{
    int i = src[2];
    i = (i << 8) | src[1];
    i = (i << 8) | src[0];

    const float Q = 2.0 / ((1 << 24) - 1.0);

    return (i + 0.5) * Q;
}

Ich bin nicht ganz sicher, aber es scheint, dass die Ergebnisse, die ich von diesem Code immer falsch sind. So ist mein Code falsch und wenn ja, warum?

War es hilfreich?

Lösung

Sie sind nicht unterzeichnen die 24 Bits in eine ganze Zahl erstreckt; die oberen Bits werden immer Null. Dieser Code funktioniert, egal was Ihre int Größe ist:

if (i & 0x800000)
    i |= ~0xffffff;

Edit: Problem 2 ist Ihre Skalierung konstant. In einfachen Worten, mögen Sie durch das neue Maximum und dividieren durch das alte Maximum zu multiplizieren, unter der Annahme, dass 0 bleibt bei 0,0 nach der Konvertierung.

const float Q = 1.0 / 0x7fffff;

Schließlich, warum fügen Sie 0,5 in der letzten Konvertierung? Ich könnte verstehen, wenn Sie rund um einen ganzzahligen Wert versucht wurden, aber Sie gehen in die andere Richtung.

Edit 2: Die Quelle, die Sie für Ihre Entscheidungen eine sehr detaillierte Begründung zu hat hinweisen. Nicht so, wie würde ich gewählt habe, aber vollkommen vertretbar dennoch. Mein Rat für den Multiplikator hält nach wie vor, aber das Maximum ist anders, weil der 0,5 zusätzlichen Faktor:

const float Q = 1.0 / (0x7fffff + 0.5);

Da die positiven und negativen Größen das gleiche nach der Zugabe sind, sollte dies in beide Richtungen skalieren richtig.

Andere Tipps

Since you are using a char array, it does not necessarily follow that the input is little endian by virtue of being x86; the char array makes the byte order architecture independent.

Your code is somewhat over complicated. A simple solution is to shift the 24 bit data to scale it to a 32bit value (so that the machine's natural signed arithmetic will work), and then use a simple ratio of the result with the maximum possible value (which is INT_MAX less 256 because of the vacant lower 8 bits).

#include <limits.h>

float convert(const unsigned char* src)
{
    int i = src[2] << 24 | src[1] << 16 | src[0] << 8 ;
    return i / (float)(INT_MAX - 256) ;
}

Test code:

unsigned char* makeS24( unsigned int i, unsigned char* s24 )
{
    s24[2] = (unsigned char)(i >> 16) ;
    s24[1] = (unsigned char)((i >> 8) & 0xff);
    s24[0] = (unsigned char)(i & 0xff);
    return s24 ;
}

#include <iostream>

int main()
{
    unsigned char s24[3] ;
    volatile int x = INT_MIN / 2 ;

    std::cout << convert( makeS24( 0x800000, s24 )) << std::endl ;  // -1.0
    std::cout << convert( makeS24( 0x7fffff, s24 )) << std::endl ;  //  1.0
    std::cout << convert( makeS24( 0, s24 )) << std::endl ;         //  0.0
    std::cout << convert( makeS24( 0xc00000, s24 )) << std::endl ;  // -0.5
    std::cout << convert( makeS24( 0x400000, s24 )) << std::endl ;  //  0.5

}

Since it's not symmetrical, this is probably the best compromise.

Maps -((2^23)-1) to -1.0 and ((2^23)-1) to 1.0.

(Note: this is the same conversion style used by 24 bit WAV files)

float convert( const unsigned char* src )
{
    int i = ( ( src[ 2 ] << 24 ) | ( src[ 1 ] << 16 ) | ( src[ 0 ] << 8 ) ) >> 8;
    return ( ( float ) i ) / 8388607.0;
}

The solution that works for me:

/**
 * Convert 24 byte that are saved into a char* and represent a float
 * in little endian format to a C float number.
 */
float convert(const unsigned char* src)
{
    float num_float;
    // concatenate the chars (short integers) and
    // save them to a long int
    long int num_integer = (
            ((src[2] & 0xFF) << 16) | 
            ((src[1] & 0xFF) << 8) | 
            (src[0] & 0xFF)
        ) & 0xFFFFFFFF;

    // copy the bits from the long int variable
    // to the float.
    memcpy(&num_float, &num_integer, 4);

    return num_float;
}

Works for me:

float convert(const char* stream)
{
    int fromStream = 
        (0x00 << 24) + 
        (stream[2] << 16) + 
        (stream[1] << 8) + 
         stream[0];

    return (float)fromStream;
}

Looks like you're treating it as an 24-bit unsigned integer. If the most significant bit is 1, you need to make i negative by setting the remaining 8 bits to 1 as well.

I'm not sure if it's good programming practice, but this seems to work (at least with g++ on 32-bit Linux, haven't tried it on anything else yet) and is certainly more elegant than extracting byte-by-byte from a char array, especially if it's not really a char array but rather a stream (in my case, it's a file stream) that you read from (if it is a char array, you can use memcpy instead of istream::read).

Just load the 24-bit variable into the less significant 3 bytes of a signed 32-bit (signed long). Then shift the long variable one byte to the left, so that the sign bit appears where it's meant to. Finally, just normalize the 32-bit variable, and you're all set.

union _24bit_LE{
  char access;
  signed long _long;
}_24bit_LE_buf;

float getnormalized24bitsample(){
  std::ifstream::read(&_24bit_LE_buf.access+1, 3);
  return (_24bit_LE_buf._long<<8) / (0x7fffffff + .5);
}

(Strangely, it doesn't seem to work when you just read into the 3 more significant bytes right away).

EDIT: it turns out this method seems to have some problems I don't fully understand yet. Better don't use it for the time being.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top