Question

Je la programmation en C ++. Je besoin de convertir un 24 bits entier signé (tableau stocké dans un octet 3) à flotteur (normalisation à [-1.0,1.0]).

La plate-forme est MSVC ++ sur x86 (ce qui signifie que l'entrée est petit-boutiste).

J'ai essayé ceci:

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;
}

Je ne suis pas tout à fait sûr, mais il semble que les résultats que je reçois de ce code sont incorrectes. Alors, est mon mal de code et si oui, pourquoi?

Était-ce utile?

La solution

Vous n'êtes pas signer étendant les 24 bits en un nombre entier; les bits supérieurs seront toujours zéro. Ce code ne fonctionnera quel que soit votre taille de int est:

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

Modifier Problème 2 est votre constante de mise à l'échelle. En termes simples, vous voulez multiplier par le nouveau maximum et diviser par l'ancien maximale, en supposant que 0 reste à 0,0 après la conversion.

const float Q = 1.0 / 0x7fffff;

Enfin, pourquoi l'ajout de 0,5 dans la conversion finale? Je pourrais comprendre si vous essayiez de tour à une valeur entière, mais vous allez l'autre sens.

Edit 2: La source vous pointez a une raison d'être très détaillé pour votre choix. Pas la façon dont je l'aurais choisi, mais parfaitement défendable quand même. Mon conseil pour le multiplicateur tient toujours, mais le maximum est différent en raison du facteur ajouté 0,5:

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

Parce que les grandeurs positives et négatives sont les mêmes après l'addition, cela devrait échelle correctement les deux sens.

Autres conseils

Puisque vous utilisez un tableau de caractères, il ne suit pas nécessairement que l'entrée est peu endian en vertu d'être x86; le tableau de caractères rend l'architecture de l'ordre des octets indépendant.

Votre code est un peu plus compliqué. Une solution simple consiste à décaler les 24 bits de données qui lui sont échelle à une valeur de 32 bits (de sorte que l'arithmétique naturel signé volonté travail de la machine), et ensuite utiliser un simple ratio du résultat avec la valeur maximale possible (ce qui est INT_MAX moins 256 parce des vides 8 bits de poids faible).

#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) ;
}

Code d'essai:

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

}

Comme il est pas symétrique, ce qui est probablement le meilleur compromis.

Cartes -. ((2 ^ 23) -1) à -1,0 et ((2 ^ 23) -1) pour 1,0

(Note: ceci est le même style de conversion utilisé par 24 bits des fichiers WAV)

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

La solution qui fonctionne pour moi:

/**
 * 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;
}

Travaux pour moi:

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

    return (float)fromStream;
}

On dirait que vous traitez comme un entier non signé 24 bits. Si le bit le plus significatif est 1, vous devez faire négatif i en définissant les 8 bits restants à 1 ainsi.

Je ne suis pas sûr que ce soit une bonne pratique de programmation, mais cela semble fonctionner (au moins avec g ++ sur Linux 32 bits, ne l'ont pas essayé sur autre chose encore) et est certainement plus élégant que l'extraction octet par -Byte à partir d'un tableau de caractères, surtout si ce n'est pas vraiment un tableau de caractères, mais plutôt un cours d'eau (dans mon cas, il est un flux de fichiers) que vous lisez (si un tableau de caractères, vous pouvez utilisation memcpy au lieu de istream::read).

Il suffit de charger la variable 24 bits dans les moins significatifs 3 octets d'un 32 bits signé (signed long). Déplacez ensuite la variable long un octet à gauche, de sorte que le bit de signe apparaît là où il est censé. Enfin, juste Normaliser la variable 32 bits, et vous êtes tous ensemble.

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);
}

(Étrangement, il ne semble pas fonctionner lorsque vous venez de lire dans les 3 octets de plus importants tout de suite).

EDIT : il se trouve cette méthode semble avoir quelques problèmes que je ne comprends pas encore complètement. Mieux ne l'utilisez pas pour l'instant.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top