web-dev-qa-db-fra.com

Comment convertir des échantillons pcm dans un tableau d'octets en nombres à virgule flottante compris entre -1,0 et 1,0 et inversement?

L'algorithme de rééchantillonnage que j'utilise attend float tableau contenant des échantillons d'entrée dans la plage - 1,0 à 1,. Les données audio sont 16 bits PCM avec échantillonnage 22khz.

Je veux sous-échantillonner l'audio de 22 kHz à 8 kHz, comment représenter les échantillons dans le tableau d'octets sous forme de nombres à virgule flottante > = -1 et <= 1 et revenir au tableau d'octets?

25
Raneez Ahmed

Vous posez deux questions:

  1. Comment sous-échantillonner de 22 kHz à 8 kHz?

  2. Comment convertir de float [-1,1] en 16 bits int et retour?

Notez que la question a été mise à jour pour indiquer que le n ° 1 est pris en charge ailleurs, mais je laisserai cette partie de ma réponse au cas où cela aiderait quelqu'un d'autre.

1. Comment sous-échantillonner de 22 kHz à 8 kHz?

Un intervenant a laissé entendre que cela peut être résolu avec la FFT. C'est incorrect (Une étape dans le rééchantillonnage est le filtrage. Je mentionne pourquoi ne pas utiliser la FFT pour filtrer ici, au cas où vous seriez intéressé: http://blog.bjornroche.com/2012/08/when-to -not-use-fft.html ).

Un très bon moyen de rééchantillonner un signal est d'utiliser un filtre polyphasé . Cependant, cela est assez complexe, même pour une personne expérimentée dans le traitement du signal. Vous avez plusieurs autres options:

  • utiliser une bibliothèque qui implémente un rééchantillonnage de haute qualité, comme libsamplerate
  • faire quelque chose de rapide et de sale

Il semble que vous ayez déjà opté pour la première approche, ce qui est génial.

Une solution rapide et sale ne sonnera pas aussi bien, mais puisque vous descendez à 8 kHz, je suppose que la qualité du son n'est pas votre première priorité. Une option rapide et sale consiste à:

  • Appliquez un filtre passe-bas au signal. Essayez de vous débarrasser d'autant d'audio au-dessus de 4 kHz que possible. Vous pouvez utiliser les filtres décrits ici (même si idéalement vous voulez quelque chose de beaucoup plus raide que ces filtres, ils sont au moins meilleurs que rien).
  • sélectionnez chaque 2,75ème échantillon dans le signal d'origine pour produire le nouveau signal rééchantillonné. Lorsque vous avez besoin d'un échantillon non entier, utilisez l'interpolation linéaire. Si vous avez besoin d'aide pour l'interpolation linéaire, essayez ici .

Cette technique devrait être plus que suffisante pour les applications vocales. Cependant, je ne l'ai pas essayé, donc je ne suis pas sûr, donc je recommande fortement d'utiliser la bibliothèque de quelqu'un d'autre.

Si vous voulez vraiment implémenter votre propre conversion de fréquence d'échantillonnage de haute qualité, comme un filtre polyphasé, vous devez le rechercher, puis poser toutes les questions que vous avez sur https://dsp.stackexchange.com/ , pas ici.

2. Comment convertir de float [-1,1] en 16 bits int et retour?

Cela a déjà été commencé par c.fogelklou, mais permettez-moi d'embellir.

Pour commencer, la plage d'entiers 16 bits est comprise entre -32768 et 32767 (en général, l'audio 16 bits est signé). Pour convertir de int en float, procédez comme suit:

float f;
int16 i = ...;
f = ((float) i) / (float) 32768
if( f > 1 ) f = 1;
if( f < -1 ) f = -1;

Vous n'avez généralement pas besoin de faire ce "délimitation" supplémentaire (en fait, vous ne le faites pas si vous utilisez vraiment un entier 16 bits), mais il est là au cas où vous auriez des entiers> 16 bits pour une raison quelconque.

Pour reconvertir, vous procédez comme suit:

float f = ...;
int16 i;
f = f * 32768 ;
if( f > 32767 ) f = 32767;
if( f < -32768 ) f = -32768;
i = (int16) f;

Dans ce cas, il est généralement nécessaire de faire attention aux valeurs hors plage, en particulier les valeurs supérieures à 32767. Vous pourriez vous plaindre que cela introduit une certaine distorsion pour f = 1. Ce problème est vivement débattu. Pour une discussion (incomplète) à ce sujet, voir cet article de blog .

C'est plus que "assez bon pour le travail du gouvernement". En d'autres termes, cela fonctionnera bien, sauf dans le cas où vous êtes préoccupé par la qualité sonore ultime. Puisque vous allez à 8 kHz, je pense que nous avons établi que ce n'est pas le cas, donc cette réponse est très bien.

Cependant, pour être complet, je dois ajouter ceci: si vous essayez de garder les choses absolument immaculées, gardez à l'esprit que cette conversion introduit de la distorsion. Pourquoi? Parce que l'erreur lors de la conversion de float en int est corrélée avec le signal. Il s'avère que la corrélation de cette erreur est terrible et que vous pouvez réellement l'entendre, même si elle est très petite. (heureusement, il est suffisamment petit pour que des choses comme la parole et la musique à faible dynamique ne comptent pas beaucoup) Pour éliminer cette erreur, vous devez utiliser quelque chose appelé tramage dans la conversion de float en int. Encore une fois, si c'est quelque chose qui vous intéresse, recherchez-le et posez des questions pertinentes et spécifiques sur https://dsp.stackexchange.com/ , pas ici.

Vous pourriez également être intéressé par les diapositives de mon exposé sur les bases de la programmation audio numérique, qui contient une diapositive sur ce sujet, même si elle dit essentiellement la même chose (peut-être même moins que ce que je viens de dire): http : //blog.bjornroche.com/2011/11/11/slides-from-fundamentals-of-audio.html

52
Bjorn Roche

PCM 16 bits a une plage - 32768 à 32767. Donc, multipliez chacun de vos échantillons PCM par (1.0f/32768.0f) dans un nouveau tableau de flottants, et passez-le à votre rééchantillonnage.

Revenir à flotter après le rééchantillonnage, multiplier par 32768.0, saturer (couper tout ce qui est en dehors de la plage - 32768 à 32767), rond (ou trembler comme Björn l'a mentionné), puis repasser au court.

Code de test qui montre la conversion en avant et en arrière à l'aide de multiplications sans erreurs binaires:

// PcmConvertTest.cpp : Defines the entry point for the console application.
//

#include <assert.h>
#include <string.h>
#include <stdint.h>
#define SZ 65536
#define MAX(x,y) ((x)>(y)) ? (x) : (y)
#define MIN(x,y) ((x)<(y)) ? (x) : (y)
int main(int argc, char* argv[])
{
  int16_t *pIntBuf1 = new int16_t[SZ];
  int16_t *pIntBuf2 = new int16_t[SZ];
  float   *pFloatBuf = new float[SZ];

  // Create an initial short buffer for testing
  for( int i = 0; i < SZ; i++) {
    pIntBuf1[i] = (int16_t)(-32768 + i);
  }

  // Convert the buffer to floats. (before resampling)
  const float div = (1.0f/32768.0f);
  for( int i = 0; i < SZ; i++) {
    pFloatBuf[i] = div * (float)pIntBuf1[i];
  }

  // Convert back to shorts
  const float mul = (32768.0f);
  for( int i = 0; i < SZ; i++) {
    int32_t tmp = (int32_t)(mul * pFloatBuf[i]);
    tmp = MAX( tmp, -32768 ); // CLIP < 32768
    tmp = MIN( tmp, 32767 );  // CLIP > 32767
    pIntBuf2[i] = tmp;
  }

  // Check that the conversion went int16_t to float and back to int for every PCM value without any errors.
  assert( 0 == memcmp( pIntBuf1, pIntBuf2, sizeof(int16_t) * SZ) );

  delete pIntBuf1;
  delete pIntBuf2;
  delete pFloatBuf;
  return 0;
}
14
c.fogelklou