Speex / Delphi
mardi 4 septembre 2007, par Bech ()
(Pour speex et dotnet, c’est par là)
Dur dur de trouver des infos sur le net pour utiliser le codec Speex sous Delphi
.
Ayant eu du mal, je vous propose donc d’en profiter en utilisant le fichier joint (en bas de page). Il s’agit du projet que l’on trouve sur le site de speex : http://speex.org/software/. Il manque des fichiers dans la démo d’origine, j’ai donc fait les modifications pour que tout soit fonctionnel.
Le fichier zip contient :
Speex 1.0
— libspeex.dll (speex-1.2beta2)
— speex.pas
— le projet "Speex_Delphi_Demo" que l’on trouve sur le net malheureusement sans les fichiers précédents (Cet exemple utilise uniquement les fonctions de la libspeex version 1.0.5).
Speex 1.2
— libspeex.dll (speex-1.2beta2)
— speex.pas (nouvelle version)
— le projet "Speex_Delphi_Demo" modifié.
version originale : delphi speex demo
Dernière version du fichier Speex.pas pour la libspeex >= v1.1
Ce fichier speex.pas permet d’utiliser la version 1.1 ou 1.2 de la libspeex. Elle permet donc entre autre les fonctions speex_encode_int et speex_decode_int ainsi que celles du préprocesseur tel que speex_preprocess.
Format des données pour speex
speex utilise, jusqu’a la version 1.0, un tableau de single pour traiter les données audio.
- speex_encode: function(state: PSpeexState; in_:PSingle; bits: PSpeexBits): Integer cdecl;
- speex_decode: function(state: PSpeexState; bits: PSpeexBits; out_: PSingle): Integer cdecl;
A partir de la version 1.1, on peut utiliser un tableau de type entier sur 16bits signé (SmallInt). Pour cela on fait appel aux fonctions :
- speex_encode_int: function(state: PSpeexState; in_:PSmallInt; bits: PSpeexBits): Integer cdecl;
- speex_decode_int: function(state: PSpeexState; bits: PSpeexBits; out_: PSmallInt): Integer cdecl;
Dans tout les cas, quand vous utiliser la taille d’une frame, il s’agit du nombre de valeur et non le nombre d’octet. Vous devez prendre cela en considération car sinon vous allez avoir des erreurs d’accès mémoire.
Explication : si une frame fait 160 en taille et que vous encoder en SmallInt, il vous faut deux fois plus d’octet ( sizeof(smallint) = 2 ) et 4 fois plus en Single.
Dans les exemple qui suivent, j’utilise des SmallInt donc vous verez parfois dans mon code des "encframe*2" pour obtenir une taille en octet.
Encodage avec speex
Pour commencer, en toute logique vous aurez besoin de quelques variables :
- var
- encbits: TSpeexBits;
- encstate: PSpeexState;
- encprestate: PSpeexPreprocessState;
- encframe: integer; // taille d'une frame
- buffBytes: array of byte; // tampon pour l'encodage
- smpRt: integer; // taux de capture
- q,i : Integer; // pour passer des paramètres lors de la config
Ces variables sont donc initialisées :
- // Pour charger la DLL
- Speex_Load_DLL;
- // Préparer une structure speex_bits
- speex_bits_init(@encbits);
- // Préparer un structure pour l'encodage
- encstate := speex_encoder_init(speex_lib_get_mode(SPEEX_MODEID_NB));
- q := 10; // qualité d'encodage speex
- smpRt := 8000; // Taux de notre capture audio
- speex_encoder_ctl(encstate,SPEEX_SET_SAMPLING_RATE,@smpRt);
- speex_encoder_ctl(encstate,SPEEX_SET_QUALITY,@q);
- speex_encoder_ctl(encstate,SPEEX_SET_VBR_QUALITY,@q);
- i := 1; speex_encoder_ctl(encstate, SPEEX_SET_VBR, @i); // Active le mode VBR
- i := 1; speex_encoder_ctl(encstate, SPEEX_SET_VAD, @i); // Active la détection de voix
- // Obtenir la taille d'une frame
- speex_encoder_ctl(encstate,SPEEX_GET_FRAME_SIZE,@encframe);
- // Prépare la structure du préprocesseur
- encprestate := speex_preprocess_state_init(encframe, smpRt);
- // Initialise le préprocesseur : DENOISE, AGC, VAD et DEREVERB
- i := 1; speex_preprocess_ctl(encprestate, SPEEX_PREPROCESS_SET_DENOISE, @i);
- i := 1; speex_preprocess_ctl(encprestate, SPEEX_PREPROCESS_SET_AGC, @i);
- i := 1; speex_preprocess_ctl(encprestate, SPEEX_PREPROCESS_SET_VAD, @i);
- i := 1; speex_preprocess_ctl(encprestate, SPEEX_PREPROCESS_SET_DEREVERB, @i);
On pense de suite à prévoir la libération de ces variables dans une procédure style "OnClose" ou "Destroy" (Comme cela on est tranquile après...) :
- speex_preprocess_state_destroy(encprestate);
- speex_bits_destroy(@encbits);
- speex_encoder_destroy(encstate);
Voila, maintenant je suppose que vous savez capture de l’audio et plaçer les échantillons dans un buffer du type "Array of smallint" (entier sur 16bit).
Dans ce premier exemple, buffer est donc un tableau de taille égale à une frame speex (variable encframe) :
- speex_preprocess(encprestate, @Buffer[0], nil);
- speex_encode_int(encstate, @Buffer[0], @encbits);
- // sz contient le nombe d'octet à écrire / prévoir
- sz := speex_bits_nbytes(@encbits);
- // On prépare la taille du tampon qui contiendra les données encodées
- if length(buffBytes) < sz then SetLength(buffBytes,sz);
- // sz2 contient le nombre d'octet écrit
- sz2 := speex_bits_write(@encbits,@buffBytes[0],sz);
- // on vide l'état d'encodage
- speex_bits_reset(@encbits);
- // il ne reste plus qu'a transmettre ou enregistrer le tableau buffBytes...
Dans le code suivant, le buffer capturé est plus grand qu’une frame speex (un multiple de cette taille) et l’encodage va se faire sur plusieurs frames. size contient la taille total des données capturées.
- i := 0; // on compte le nombre de frame déjà encodée
- l := size; // taille restante dans le buffer
- while l >= encframe do begin
- // Encode the sound data //
- speex_preprocess(encprestate, @Buffer[(i*encframe)*2], nil);
- speex_encode_int(encstate, @Buffer[(i*encframe)*2], @encbits);
- l := l - (encframe*2);
- i := i + 1;
- end;
- // sz contient le nombe d'octet à écrire / prévoir
- sz := speex_bits_nbytes(@encbits);
- if length(buffBytes) < sz then SetLength(buffBytes,sz);
- // sz2 contient le nombre d'octet écrit
- sz2 := speex_bits_write(@encbits,@buffBytes[0],sz);
- //
- speex_bits_reset(@encbits);
Décodage de speex
Egalement quelques variables :
- var
- decbits: TSpeexBits;
- decstate: PSpeexState;
- decframe: integer; // taille d'une frame
- buffDec: array of SmallInt; // décodage d'une frame
Ces variables sont donc initialisées :
- // Pour charger la DLL
- Speex_Load_DLL;
- speex_bits_init(@decbits);
- decstate := speex_decoder_init(speex_lib_get_mode(SPEEX_MODEID_NB));
- speex_decoder_ctl(decstate,SPEEX_GET_FRAME_SIZE,@decframe);
- SetLength(buffDec, decframe);
- speex_bits_reset(@decbits);
Comme toujours on pense à prévoir la libération de ces variables :
- speex_bits_destroy(@decbits);
- speex_decoder_destroy(decstate);
Voila, on va maintenant décoder un buffer de taille size qui peu contenir une ou plusieurs frames :
- // On commence par lire la totalité des données encodées, zou...
- speex_bits_read_from(@decbits,@buffer[0],size);
- // on prépare la taille du buffer en fonction de celle d'une frame
- if decframe > length(buffDec) then SetLength(buffDec,decframe);
- repeat // tant qu'il y a des données à décoder
- i := speex_decode_int(decstate,@decbits,@buffDec[0]);
- if i = 0 then // une frame décodée
- // moi je la met dans un stream...
- AudioStream.Write(buffDec[0], (decframe*2));
- until i <> 0;
