Une brève introduction au format de fichier audio WAVE et à son extension 64 bits

texte

En ce qui concerne le format de fichier WAVE, il existe de nombreuses introductions sur Internet, mais il n'y a presque aucune introduction sur le format étendu WAVE 64 bits.

Le but de cet article est donc de présenter brièvement le format WAVE standard, ainsi que les deux principales extensions.

Tous les codes de cet article sont décrits en langage C. Bien que le langage C ne soit pas si pratique, il est très courant ; même si je suis meilleur en langage Pascal, et il peut être plus pratique de décrire le code dans cet article, mais il y a vraiment pas beaucoup de gens qui utilisent Pascal maintenant.

Si vous souhaitez afficher les documents standard les plus complets et les plus détaillés, je fournirai un lien vers le disque réseau à la fin de l'article. (Il est fortement recommandé de regarder la documentation si vous voulez en savoir plus)

En même temps, du fait de mon niveau limité, il est inévitable qu'il y ait des erreurs, si vous avez des questions, n'hésitez pas à me les signaler.

Le but de cet article n'est pas seulement de présenter ces contenus, mais aussi de guider les lecteurs pour créer une roue (au moins pour les contenus de base comme WAVE).

avant le départ

Définissons d'abord les types de données qui doivent être utilisés pour faciliter l'explication du contenu suivant.

typedef char Int8;
typedef short Int16;
typedef long Int32;
typedef long long Int64;
typedef unsigned char UInt8;
typedef unsigned short UInt16;
typedef unsigned long UInt32;
typedef unsigned long long UInt64;
typedef UInt8 Byte;
typedef UInt16 Word;
typedef UInt32 DWord;
typedef UInt64 QWord;

typedef struct
{
    
    
    DWord D1;
    Word D2;
    Word D3;
    Byte D4[8];
} Guid;

typedef union
{
    
    
    DWord dw;
    char chr[4];
} FourCC;

Les types d'entiers ci-dessus sont bien connus et faciles à utiliser. Parmi eux, Byte~QWord est une utilisation courante de l'accès mémoire en langage assembleur.


GUID (ou UUID) est une structure de 16 octets qui utilise un algorithme spécifique pour s'assurer que son binaire est unique (pour Windows, ole32.dll a un CoCreateGuid pour réaliser une telle fonction), si vous regardez Windows Vous y trouverez Il y a un grand nombre de clés sous forme de chaînes (décrites ci-dessous) dans le registre. Bien sûr, ceux qui comprennent la programmation COM sont également très familiers avec cela. En bref, c'est une partie importante du système Windows.

Voici une brève introduction à la relation correspondante entre sa forme de chaîne et son binaire.

Nous attribuons les valeurs suivantes à D1~D4 :

D1=0x12345678;
D2=0x9ABC;
D3=0xDEF0;
D4={
    
    'A','B','C','D','E','F','G','H'};

Ensuite, sa forme de chaîne est comme ceci, qui est représenté par des octets en hexadécimal :

{12345678-9ABC-DEF0-4142-434445464748}

Notez que c'est le cas du little-endian.Généralement, les processeurs d'architecture que nous utilisons x86(principalement IntelHe et AMDles deux) utilisent du stockage little-endian.

S'il s'agit d'un big endian, la situation correspondante devrait ressembler à ceci :

D1=0x78563412;
D2=0xBC9A;
D3=0xF0DE;
D4={
    
    'A','B','C','D','E','F','G','H'};

Pour certains problèmes sous forme de chaîne GUID et sous forme binaire, nous continuerons à en discuter plus tard.

Remarque : Le Guid précédent consiste à distinguer la définition de GUID dans windows.h.


FourCCDWordLe type (code à quatre caractères) est utilisé pour marquer le bloc et peut être utilisé ou remplacé dans l'utilisation réelle char[4](le type de caractère doit être à un octet).

L'utilisation principale de ce type est d'utiliser 4 caractères pour représenter le type d'un bloc, tel que "RIFF"le bloc qui sera mentionné plus tard.

Quant à savoir pourquoi un DWord et char[4] sont utilisés pour former une union, c'est pour la commodité de la lecture et de l'écriture. Si vous utilisez C++, ce n'est pas un gros problème d'avoir une surcharge d'opérateur. Si c'est pour le langage C, c'est plus gênant, donc en plus le type de langage DWord de C est obsolète.

Notez que si vous utilisez DWord pour lire et écrire, vous devez faire attention au problème du gros et du petit boutien. Par exemple, dans le petit boutien, c'est équivalent au même, 'FFIR'mais "RIFF"dans le cas du gros boutien, les deux sont cohérents . En raison de ce problème, il n'est pas recommandé d'utiliser le type DWord. .

Cependant, si vous 'FFIR'utilisez un type comme celui-ci comme entier, le compilateur donnera un avertissement. Si vous voulez éliminer ce problème, vous pouvez utiliser une définition de macro pour le résoudre :

// 大端把后面的a,b,c,d反过来就行了
#define MAKE_DWORD(a,b,c,d) (DWord)(((a&0xff))|((b&0xff)<<8)|((c&0xff)<<16)|((d&0xff)<<24))
#define RIFF_CHUNK_DWID MAKE_DWORD('R','I','F','F')
// ...

Mais en fait, ce n'est pas "RIFF"aussi simple et direct que cela, donc si ce n'est pas trop gênant d'utiliser le langage C, vous pouvez utiliser char[4]la strncmpfonction et (ou écrire une macro pour juger), dans d'autres langages, il est plus pratique de surcharge directement l'opérateur.

Remarque : La macro MAKEFOURCC dans windows.h a le même effet que MAKE_DWORD ici.


D'accord, ne soyons pas trop verbeux, je pense que tout le monde aura une solution pour les détails spécifiques, commençons le sujet.

Format standard RIFF/WAVE

Pour une introduction au format standard et l'accès aux documents connexes, cliquez sur ce lien .

Le format WAVE appartient au format RIFF. La structure de fichiers de ce format est basée sur des blocs. Pour plus de détails, vous pouvez rechercher en ligne ou consulter des documents.

Une brève introduction aux en-têtes de bloc :

typedef struct
{
    
    
    FourCC id; // 区块类型
    DWord size; // 区块大小(不包括id和size字段的大小)
} RIFFChunkHeader;
  • Le champ de taille peut être un nombre impair, mais la taille réelle du bloc doit être un nombre pair, c'est-à-dire qu'il est aligné sur 2 octets, donc 0 doit être ajouté à la fin

Généralement, chaque bloc peut être défini de la manière suivante :

typedef struct
{
    
    
    RIFFChunkHeader header;
    // ...
} SomeChunkName;

Cependant, pour la commodité de la lecture et de l'écriture, le texte suivant n'est pas défini dans un tel format.


La définition de l'en-tête du fichier RIFF est la suivante :

typedef struct
{
    
    
    FourCC id; // 必须是 "RIFF"
    DWord size; // 文件大小(字节数)-8
    FourCC type; // 必须是 "WAVE"
} RIFFHeader;

Après avoir compris la signification de l'en-tête de bloc, sizele champ n'a pas besoin d'être trop expliqué ; typela raison du champ idest la même que celle du champ.


Immédiatement après, il y a un fmtbloc très important.

typedef struct
{
    
    
    FourCC id; // 必须是 "fmt " (注意后面的空格哦)
    DWord size; // 必须是 16
    Word FormatTag;
    Word Channels;
    DWord SampleRate;
    DWord BytesRate;
    Word BlockAlign;
    Word BitsPerSample;
} WaveChunkFormat;

Brièvement

  • FormatTagGénéralement 1 ou 3, où 1 représente PCM et 3 représente les nombres à virgule flottante IEEE
  • ChannelsEst le nombre de canaux, généralement 1 ou 2, représentant respectivement mono et stéréo
  • SampleRateEst le taux d'échantillonnage, généralement 8000, 44100, 48000, etc.
  • BytesRateest le nombre d'octets joués par seconde, égal àBlockAlign*SampleRate
  • BlockAlignest le nombre d'octets par trame audio, égal àBitsPerSample*Channels/8
  • BitsPerSampleC'est le nombre de bits par échantillon, généralement 8, 16, 24, 32, 64

Puisque le son est stocké en alternance 1 , la taille de chaque trame audio est la taille de chaque échantillon multipliée par le nombre de canaux


Pour le bloc Format étendu, il y a généralement les deux suivants

tout d'abord

typedef struct
{
    
    
    FourCC id; // 必须是 "fmt "
    DWord size; // 一般是18
    Word FormatTag;
    Word Channels;
    DWord SampleRate;
    DWord BytesRate;
    Word BlockAlign;
    Word BitsPerSample;
    Word ExSize; // 一般是0
} WaveChunkNonPCMFormat;
  • sizeLe champ est généralement de 18, cela dépend de ExSizela taille, la taille réelle est égale à ExSize+18
  • ExSizeLe champ est généralement 0, si ce n'est pas 0, vous devez ajouter votre propre structure correspondante derrière

Cette structure est généralement utilisée dans les formats encodés non PCM (souvent des formats compressés), mais elle est généralement rare maintenant, et le format spécifique est FormatTagspécifié par le champ. Pour être honnête, j'ai vu quelques fichiers dans ce format, mais la plupart d'entre eux FormatTagont une valeur de 3, ce qui signifie le format d'échantillonnage IEEE nombre à virgule flottante 2 . Bien que les nombres à virgule flottante IEEE ne soient pas PCM 3 , mais parce que l'effet est similaire à 4 , donc si nous devons utiliser l'échantillonnage au format IEEE, nous utiliserons la norme WaveChunkFormatau lieu de celle-ci embarrassante WaveChunkNonPCMFormat, sauf si vous avez un format étendu défini par vous-même .

Pour les fichiers de ce format, il y a généralement un factbloc (pour la virgule flottante IEEE, il peut n'y en avoir aucun) :

typedef struct
{
    
    
    FourCC id; // "fact"
    DWord size; // 12
    DWord FactSize; // 每个通道的采样总数
} WaveChunkFact;

L'objectif principal de ce bloc est de fournir le nombre total d'échantillons (c'est-à-dire le nombre total de trames audio) pour estimer la taille réelle du format compressé après décompression, qui n'est pas utilisée dans le format non compressé.


Après cela

typedef struct
{
    
    
    FourCC id; // 必须是 "fmt "
    DWord size; // 必须是40
    Word FormatTag; // 必须是0xFFFE
    Word Channels;
    DWord SampleRate;
    DWord BytesRate;
    Word BlockAlign;
    Word BitsPerSample;
    Word ExSize; // 必须是22
    Word ValidBitsPerSample;
    DWord ChannelMask;
    Guid SubFormat;
} WaveChunkFormatExtensible;

C'est le format le plus utilisé par Microsoft, et si vous connaissez WASAPI, vous constaterez que c'est le format utilisé en interne par Windows Mixer.

  • VaildBitsPerSampleFait référence au nombre réel de bits d'échantillonnage, tels BitsPerSampleque 24, alors ce champ peut être 17-24, ce qui signifie que certains ou tous les 24 bits sont utilisés ; les combinaisons courantes sont 12/16, 20/24, mais généralement Equal BitsPerSample, parce que c'est si rare
  • ChannelMaskFait référence à la disposition des haut-parleurs multicanaux, tels que les canaux 5.1 et 7.1. Pour plus de détails, consultez le document
  • SubFormatIl s'agit d'un Guid 16 bits. Étant donné que FormatTagle champ doit être 0xFFFE, il doit être redéfini ultérieurement. Les deux premiers bits (c'est-à-dire un mot) représentent l'original FormatTaget les derniers bits sont fixes, mais pour plus de commodité, dans En fait, les 6 octets tiers sont tous 0, comme {00000001-0000-0010-8000-00AA00389B71}représentant PCM, {00000003-0000-0010-8000-00AA00389B71}représentant le nombre à virgule flottante IEEE

Il peut y avoir divers autres blocs après cela, mais ils ne sont généralement pas utilisés, sauf en cas de besoins particuliers, tels que le bloc d'informations sur l'auteur, le bloc de liste de lecture, le bloc de type d'instrument, le bloc d'échantillons, etc., mais ces contenus sont rarement rencontrés.

Pour en savoir plus sur les différents autres blocs, vous pouvez les consulter dans la documentation standard.


Ensuite, il y a le bloc de données qui stocke réellement les données (également notre objectif principal après avoir lu les informations préalables).

La définition d'un bloc de données est simple :

typedef struct
{
    
    
    FourCC id; // 必须是 "data"
    DWord size; // 实际数据大小
    // 后面就是以音频帧为单位存放的数据了
} WaveChunkData;

À ce stade, l'une des parties les plus simples - et en même temps les plus importantes - est terminée et, sur la base de celles-ci, un fichier au format WAVE standard peut être créé ou lu.

Format WAVE étendu

Si vous voyez un fichier au format WAVE, vous le considérerez généralement comme un fichier audio sans perte. Cependant, en fait, le format WAVE est un conteneur qui peut également stocker des données dans d'autres formats compressés, tels que, , et, etc. ., mais généralement nous utilisons le codage ADPCMPCM sans perte comme hôte.ALawMuLaw

Mais vous avez également vu que le champ utilisé dans l'en-tête du fichier au format WAVE standard sizeest d'un seul DWordtype, et sa taille maximale ne peut représenter que 4GiBla taille des données. Pour les besoins du stockage multicanal sans perte, une si petite taille est seulement suffisant pour économiser quelques dix minutes de données, c'est trop peu, nous devons donc étendre le format WAVE standard.

Comment l'agrandir ? Microsoft n'a pas donné de réponse, et l'industrie de la télédiffusion et l'industrie du disque ont développé leurs propres normes à cet égard.

Il y a donc les deux protagonistes suivants.

Mais avant de les présenter, rajoutons quelques connaissances préalables.


Ces choses sur les blocs JUNK (ordures).

Les blocs JUNK font partie de la norme RIFF et s'appliquent à tous les formats de fichiers qui utilisent RIFF, et ne sont pas spécifiques à WAVE.

Alors répondez d'abord à une question, pourquoi avons-nous besoin de blocs JUNK ?

Etant donné que les données du fichier WAVE sont stockées en continu dans le bloc de données, une fois l'écriture lancée, le décalage du bloc de données dans le fichier est fixe, alors que se passe-t-il si nous voulons ajouter d'autres blocs avant le bloc de données ? Ensuite, utilisez un bloc de déchets pour occuper la place. Ce bloc n'a pas de données, et il sera ignoré directement lors de la lecture, mais nous pouvons revenir en arrière et réécrire ce bloc après l'écriture du fichier, et changer une partie du contenu en un autre zones., et réduisez la taille de ce bloc JUNK. Bien sûr, une autre raison est de remplir les ordures pour réaliser l'alignement des fichiers. J'ai vu que le décalage du bloc de données de certains fichiers WAVE est de 4088 octets, tandis que le décalage des données audio réelles est de 4096 octets (seulement 4K), donc c'est possible Facilite la lecture séquentielle des fichiers.

En résumé, la définition d'un bloc de déchets devrait être très simple :

typedef struct
{
    
    
    FourCC id; // "JUNK"
    DWord size;
    // 垃圾数据
} RIFFChunkJunk;
  • idLes minuscules sont également utilisées "junk", mais rares
  • Généralement rempli de 0, bien sûr, il peut aussi être rempli de déchets (écrire une mémoire aléatoire)

Pour le format étendu, pourquoi avons-nous besoin d'utiliser des blocs JUNK ? Je donnerai les réponses correspondantes plus tard. Mais maintenant, regardons à quoi ressemblent ces deux formats.

Format étendu RF64/WAVE

Comme son nom l'indique, RF64il s'agit RIFFd'une extension 64 bits du format, mais uniquement pour le format WAVE.

RF64 utilise le même en-tête de fichier que RIFF. L'une des différences est que idles champs "RIFF"passent de à "RF64", et la deuxième différence est que sizeles champs sont remplis avec 0xFFFFFFFF(en fait, cela n'a pas d'importance).

Légèrement différent de RIFF, RF64 exige que l'en-tête de fichier soit ds64un bloc, qui est défini comme suit :

typedef struct
{
    
    
    FourCC id; // 必须是 "ds64"
    DWord size; // 一般是28
    UInt64 RIFFSize; // 实际的RIFF大小(即文件大小-8)
    UInt64 DataSize; // 实际的数据块大小
    UInt64 FactSize; // 实际的数据量(解压后)
    DWord TableLen; // 一般为0
    // 后面紧跟 RIFFChunkHeader64 数组
} RIFFChunkDS64;
  • sizeC'est généralement 28, si TableLence n'est pas 0, il faut l'ajouter12*TableLen
  • RIFFSizeUtilisé pour représenter le champ de taille d'en-tête RIFF réel
  • DataSizeUtilisé pour représenter le champ de taille réelle du bloc de données
  • FactSizeLa taille utilisée pour compresser le format, généralement non compressé, égale DataSizeà
  • TableLenC'est généralement 0, car il y a très peu de champs qui nécessitent une taille de 64 bits ; si ce n'est pas 0, la taille doit être recalculée et un RIFFChunkHeader64tableau doit être suivi

qui est RIFFChunkHeader64défini comme suit :

typedef struct
{
    
    
    FourCC id;
    UInt64 size;
} RIFFChunkHeader64;

Ensuite, il y a le bloc fmt et le bloc de données, qui sont identiques à RIFF (sauf que la taille du bloc de données est définie sur 0xFFFFFFFF), mais généralement le bloc fmt est utilisé car l'industrie de la radiodiffusion doit prendre en charge le son surround, mais utiliser les WaveChunkFormatExtensibledeux autres One est aussi parfaitement possible.

On peut voir que les changements de RF64 en RIFF sont encore assez petits, donc la compatibilité de RF64 avec le format d'origine est toujours très bonne, et de nombreux programmes peuvent facilement prendre en charge ce format sans ajouter trop de code.

Format étendu Sony Wave64

Pour le format WAVE étendu, Sonic Foundry a donné son plan, et plus tard cette société a été acquise par Sony, donc leur standard est devenu Sony Wave64.

Sony Wave64 a beaucoup changé.Tout d'abord, il a changé l'en-tête du bloc.

typedef struct
{
    
    
    Guid id;
    Int64 size;
} SonyWave64Header;
  • idChangé en un Guid de 16 octets, la définition est donnée dans le document de valeur spécifique
  • sizeChangé en un entier signé de 8 octets, et sa valeur inclut la taille de la somme idelle-même size, ce qui est très différent de RIFF et RF64

La définition de l'en-tête du fichier est modifiée comme suit :

typedef struct
{
    
    
    Guid id;    // 必须是 {66666972-912E-11CF-A5D6-28DB04C10000}
    Int64 size; // 等于文件大小
    Guid type;  // 必须是 {65766177-ACF3-11D3-8CD1-00C04F8EDB8A}
} SonyWave64Wave;

Si vous observez attentivement, vous constaterez que les 4 premiers octets sont exactement 4 caractères "riff" et "wave"

Cependant, il n'a apporté aucune modification aux définitions spécifiques des différents blocs de WAVE, mais a uniquement modifié l'en-tête de bloc de chaque bloc SonyWave64Header.

Bien sûr, la définition de chaque en-tête de bloc est toujours différente, principalement les 12 derniers octets.Les deux blocs principaux sont listés ci-dessous :

  • Bloc 'fmt' : {20746D66-ACF3-11D3-8CD1-00C04F8EDB8A}
  • Bloc 'données' : {61746164-ACF3-11D3-8CD1-00C04F8EDB8A}

Dans le même temps, il impose également des exigences strictes sur la structure du fichier - tous les blocs doivent être alignés sur 8 octets au lieu de l'alignement d'origine sur 2 octets.

Répondez à JUNK

En ce qui concerne l'expansion du format WAVE, si RF64 n'est qu'une petite réparation, alors Sony Wave64 a radicalement changé.

Mais tout reste le même, ces deux extensions sont toujours basées sur le mécanisme de bloc d'origine, nous pouvons donc utiliser un bloc JUNK pour occuper l'espace à l'avance, afin que nous puissions réaliser l'expansion dynamique de RF64 ou Sony Wave64 au format WAVE.

Bien sûr, le bloc JUNK peut également être utilisé pour effectuer un alignement 4K pour la position de départ des données, mais ce n'est pas ce dont nous discutons principalement.

La méthode de mise en œuvre spécifique de l'expansion dynamique consiste à calculer à l'avance la taille du bloc JUNK et à le remplir, puis à écrire les données et à vérifier la taille du montant écrit lorsqu'il est sur le point de se terminer : si le montant écrit est inférieur à 4GiB, alors nous pouvons le terminer directement, peu importe Il s'agit d'un bloc de déchets ; sinon, nous pouvons utiliser l'espace occupé par le bloc de déchets pour réécrire les informations de l'en-tête du fichier en informations conformes au format.

Généralement, la taille JUNK de base requise pour étendre WAVE à RF64 est de 28 octets, tandis que la taille JUNK de base requise pour étendre à Sony Wave64 est de 52 octets, et chaque bloc supplémentaire nécessite 16 octets supplémentaires.

Bien sûr, l'implémentation du code spécifique est encore très compliquée, et il faudra beaucoup de temps pour l'écrire.Par exemple, un logiciel appelé Reaper 5 réalise cette fonction.

Outre le GUID

En raison du problème du langage C, il est plus difficile de convertir la forme de chaîne GUID et la forme binaire. Ce n'est pas un problème pour les autres langages de haut niveau, donc si vous utilisez le langage C, vous ne pouvez pas utiliser de chaînes.

Par exemple, Pascal (y compris Free Pascal et Delphi) peut utiliser ce formulaire pour définir une certaine constante GUID :

const
  WavSubFmtPCM:TGUID       = '{00000001-0000-0010-8000-00AA00389B71}';
  WavSubFmtIEEEFloat:TGUID = '{00000003-0000-0010-8000-00AA00389B71}';

Je crois que les langages courants actuels prennent essentiellement en charge des méthodes similaires.

Par exemple, Microsoft C++ a un uuid qui peut être utilisé.

Pour le langage C, si vous avez besoin de l'utiliser, vous pouvez l'utiliser avec la définition de macro suivante

#define MAKE_GUID(uid,dw1,w1,w2,b1,b2,b3,b4,b5,b6,b7,b8) \
    const Guid uid = {
      
      dw1,w1,w2,{
      
      b1,b2,b3,b4,b5,b6,b7,b8}};
#define MAKE_WAVE_SUBFORMAT(uid,d) \
    MAKE_GUID(uid,d,0,0x10,0x80,0,0,0xAA,0x00,0x38,0x9B,0x71)
MAKE_WAVE_SUBFORMAT(WAV_SUB_FMT_IEEE, 3)
MAKE_WAVE_SUBFORMAT(WAV_SUB_FMT_FLAC, 0xF1AC)
MAKE_GUID(SONY_WAVE64_RIFF,0x66666972,0x912e,0x11cf,0xa5,0xd6,0x28,0xdb,0x04,0xc1,0,0)
// ...

post-scriptum

En fait, après avoir écrit l'article AIFF, je voulais écrire cet article WAV, mais je ne l'ai jamais écrit (car la charge de travail est bien plus importante que celle d'AIFF), et même arrêté après en avoir écrit la moitié. ça (ça a pris un mois avant et après ) ).

À l'avenir, j'aurai l'occasion d'écrire du contenu sur la façon de lire WAV et de le lire avec DirectSound ou WASAPI, en particulier WASAPI.Il y a encore relativement peu d'informations à cet égard.

S'il y a une chance, j'ajouterai un contenu simple de traitement du son.

netdisc

https://lanzoui.com/b011tcx9g Mot de passe : fz7c

Si le lien ne peut pas être ouvert, vous pouvez changer lanzoui en lanzoux ou autres, et vous pouvez rechercher des mots-clés 蓝奏云打不开.

Il contient plusieurs fichiers, vous pouvez les télécharger au besoin. Si vous avez besoin de tous, vous pouvez télécharger le fichier waveformat.zip, qui contient tous les fichiers.


  1. Il y a beaucoup d'informations sur Internet sur la méthode de stockage spécifique du son, donc je n'entrerai pas dans les détails ici↩︎

  2. Le but de l'utilisation des nombres à virgule flottante IEEE est d'obtenir une plus grande plage dynamique et d'éviter la distorsion. ↩︎

  3. Le nom complet de PCM est la modulation par impulsions codées, qui utilise des nombres entiers pour quantifier les signaux analogiques échantillonnés, contrairement aux nombres à virgule flottante. ↩︎

  4. Les deux sont des encodages non compressés. ↩︎

  5. C'est un logiciel audio très geek, le site officiel est https://www.reaper.fm/index.php ↩︎

Je suppose que tu aimes

Origine blog.csdn.net/PeaZomboss/article/details/126311968
conseillé
Classement