2015年3月29日 星期日

Understanding Wave Format, and Implement a Wave Reader



I found there is no detail discussion for extra data of wave format. Here I post a explicit (for myself) wave header in below:

Data in each column are all in little endian.


Sample Value size in byte Description offset
Chunk ID 4 Marks the file as a riff file.,Characters are each 1 byte long
That should be "RIFF".
0
Chunk Size 4 4 + (8 + SubChunk1Size) + (8 + SubChunk2Size) 4
Format 4 File Type Header.,For wave purposes, it always equals "WAVE". 8
Format Mark 4 Format chunk marker.,Includes trailing null. should be "fmt " 12
Sub-chunk 1 Size 4 Length of format data 16
Audio Format 2 PCM = 1 (i.e. Linear quantization),Values other than 1 indicate some,form of compression. 20
Number of Channels 2 Mono = 1, Stereo = 2, etc. 22
Sampling Rate 4 Number of Samples per second 24
Byte Rate 4 Byte per second, it should be SamplingRate*NumberOfChannel*BitsPerSample/8 28
Block Align 2 Byte for whole sampling point,, it should be NumberOfChannel*BitsPerSample/8 32
Bits Per Sample 2 8 bits = 8, 16 bits = 16 34
Extra Data Header ?(2) Extra wave note, in most,case, that "size" is 0 (no extra data), or 2.
This size could be computed by "Subchunk1Size - 16 "
36
Extra Data ? extra data, most for audio notes(artist, album, publish year, music type. etc) ?
Sub chunk 2 ID 4 Contains the letters "data" ??
Sub chunk 2 size 4 Audio data length ?? + 4
Audio data Audio data length Audio data ?? + 8


Lots posts has discuss wave file without extra data, in here, I just note about extra data:

    Length of extra data  is various, but it should be multiple of 4.

    Extra data is useful for audience, it is not junk. If your wave file is within extra data, just print that data in your console or window.

    To find where is the end of extra data, is to find where is the beginning of sub-chunk2. That is, to find the "data" string.


Below is my implement to read a wave file in C.


#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#ifndef TRUE
#define TRUE        (1)
#define FALSE        (0)
#endif

#define MAX_STR_LEN       (256)


#ifdef _MSC_VER
#pragma warning(disable:4996) /*fuss*/

#include <stdarg.h>

int snprintf(char *pBuf, size_t maxN, const char *pFmt, ...)
{
 int len; 
 va_list argList; 
 
 va_start(argList, pFmt); 

 len = vsprintf_s(pBuf, maxN, pFmt, argList);

 va_end(argList); 

 return len;
}/*snprintf*/

#endif


unsigned int GetFileSize(FILE *pFile)
{
 unsigned int currentLen, fileSize;

 currentLen = ftell(pFile);

 fseek(pFile,0, SEEK_END);
 fileSize = ftell(pFile);   

 fseek(pFile,currentLen ,SEEK_SET);
 

 return (unsigned int)fileSize;

}/*GetFileSize*/


unsigned int GetRemainderFileSize(FILE *pFile)
{
 unsigned int currentLen, fileSize;

 currentLen = ftell(pFile);

 fseek(pFile,0, SEEK_END);
 fileSize = ftell(pFile);   

 fseek(pFile,currentLen ,SEEK_SET);

 return (unsigned int)(fileSize - currentLen );

}/*GetRemainderFileSize*/


typedef struct _WaveFeature
{
 char               ChunkID[4];
 /* "RIFF" Header      */ 
 
 unsigned int       ChunkSize;
 /* 36 + SubChunk2Size, or more precisely:4 + (8 + SubChunk1Size) + (8 + SubChunk2Size)  */
 
 char               Format[4];
 /* "WAVE"  */
 
 char               fmt[4];
 /* "fmt "*/
 
 unsigned int       Subchunk1Size;
 /* Size of the fmt chunk */
 
 unsigned short     AudioFormat;
 /* Audio format 1=PCM,6=mulaw,7=alaw, 257=IBM Mu-Law, 258=IBM A-Law, 259=ADPCM */
 
 unsigned short     NumChannels;
 /* Number of channels 1=Mono 2=Sterio */
 
 unsigned int       SampleRate;
 /* Sampling Frequency in Hz*/
 
 unsigned int       ByteRate;
 /* bytes per second,  SampleRate * NumChannels * BitsPerSample/8*/
 
 unsigned short     BlockAlign;
 /* 2=16-bit mono, 4=16-bit stereo, NumChannels * BitsPerSample/8*/
 
 unsigned short     BitsPerSample;
 /* Number of bits per sample  */
} WaveFeature;


typedef struct _WaveData
{
 char               Subchunk2ID[4]; /* "data"  string   */
 unsigned int       Subchunk2Size; /* Sampled data length    */
} WaveData;


int GetWaveFeatures(unsigned char *pWaveHead, unsigned short *pNumChannel, 
    unsigned int *pNumSamplesPerSec,  unsigned short *pBitsPerSample, 
    unsigned int *pBytesPerSec)
{
 
 char charWord[MAX_STR_LEN];  
 snprintf(&charWord[0], MAX_STR_LEN, "RIFF");
 if(0 != memcmp( &pWaveHead[0], &charWord[0], 4) ) 
  return -1;   
 
 
 snprintf(&charWord[0], MAX_STR_LEN, "WAVE");
 if(0 != memcmp( &pWaveHead[8], &charWord[0], 4) ) 
  return -1;   
 
 snprintf(&charWord[0], MAX_STR_LEN, "fmt ");
 if(0 != memcmp( &pWaveHead[12], &charWord[0], 4) ) 
  return -1; 

 unsigned int  chunkSize;
 memcpy( &chunkSize, &pWaveHead[16], 4);

 *pNumChannel = *((unsigned short*)(pWaveHead + 22));
 *pNumSamplesPerSec = *((unsigned int*)(pWaveHead + 24));
 *pBitsPerSample = *((unsigned short*)(pWaveHead + 34));
 
 *pBytesPerSec = *((unsigned int*)(pWaveHead + 28)); /*ByteRate         */
 if(*pBytesPerSec != (*pNumSamplesPerSec)*(*pNumChannel)*(*pBitsPerSample/8))
  return -2;

 int blockAlign;

 blockAlign = *((unsigned short*)(pWaveHead + 32)); 
 if(blockAlign != (*pNumChannel)*(*pBitsPerSample/8))
  return -2;

 return 0;
}/*GetWaveInfo*/


int GetWaveLength(unsigned char *pWaveHead, unsigned int *pAudioDataLen)
{
 char charWord[MAX_STR_LEN];  
 snprintf(&charWord[0], MAX_STR_LEN, "data");

 
 if(0 != memcmp( &pWaveHead[0], &charWord[0], 4) )
  return -1;  
 
 *pAudioDataLen = *((unsigned int*)(pWaveHead + 4));

 return 0;
}/*GetWaveLength*/


int main(int argc, char *argv[])
{
 
 char inputFileName[MAX_STR_LEN];

 snprintf(&inputFileName[0], MAX_STR_LEN, "03 Rondeau in C Minor.wav");
    
#if defined(__MACH__) && defined(_DEBUG)/*xcode stupid...*/
    char tempStr[MAX_STR_LEN];
    snprintf(&tempStr[0], MAX_STR_LEN, "../../../../../");
    strncat(&tempStr[0], &inputFileName[0], strnlen(&inputFileName[0],MAX_STR_LEN));
    memset(&inputFileName[0], 0, MAX_STR_LEN);
    strncpy(&inputFileName[0], &tempStr[0], strnlen(&tempStr[0],MAX_STR_LEN));
#endif
    
 int k; 
 k = 0;

 while (k < argc) 
 {
  if( 0 == strncmp("-i",argv[k], MAX_STR_LEN))
  {
   if(k + 1 < argc)
   {
    snprintf(inputFileName, MAX_STR_LEN, "%s", argv[k + 1]); 
   }
   else
   {
    printf("-i should be fellowed by input wav file.");
   }/*if*/
  }/*if strncmp*/  

  k++;
 }/*while opt*/

 FILE *pWaveFile;

 pWaveFile = fopen(&inputFileName[0], "rb");

 if(NULL == pWaveFile)
 {
  printf("open file %s error \n", inputFileName);
  return -1;
 }/*if NULL == pWaveFile*/

 printf("file : %s\n", inputFileName);
 
 WaveFeature wavFea;

 fread(&wavFea, sizeof(WaveFeature), 1, pWaveFile);

 unsigned short nChannel; 
 unsigned int nSamplesPerSec;
 unsigned short bitsPerSample;
 unsigned int audioDataLen;
 unsigned int bytesPerSec;
 int isContentExtaBytes;

 int sts;
 sts = GetWaveFeatures((unsigned char*)&wavFea, &nChannel, &nSamplesPerSec, 
  &bitsPerSample,&bytesPerSec);

 
 if(-1 == sts)
 {
  printf("not a wav format\n");
  return -1;
 }
 else if(-2 == sts)
 {
  printf("wav format do not be consisted\n");
  return -1;
 }
 else if(-3 == sts)
 {
  printf("extra parameters are not known\n");
 }/*if -1 == sts*/

 printf("%u channel\n", nChannel);
 printf("simpling frequency = %u\n", nSamplesPerSec);
 printf("%u bits Per Sample\n", bitsPerSample);
 

 {/*scan the data flag, and print extra infomation...*/
  char readBuff[MAX_STR_LEN];
  char charWorddata[8];
  unsigned int ii, jj;
 
  memset(&readBuff[0], 0, MAX_STR_LEN);
  memset(&charWorddata[0], 0, 8);
  snprintf(&charWorddata[0], 8, "data");
 
  unsigned int remainLen;
  int isFoundDataFlag;
  isFoundDataFlag = FALSE;
  remainLen = GetRemainderFileSize(pWaveFile);

  ii = 0;
  while(MAX_STR_LEN < remainLen)
  {

   fread(&readBuff[0], 1, MAX_STR_LEN, pWaveFile);
   jj = 0;
   while(jj < MAX_STR_LEN)
   {
    if(0 == memcmp(&readBuff[jj], &charWorddata[0], 4))
    {
     isFoundDataFlag = TRUE; 
     break;
    }
    else
    {
     if(0 != jj && 0 == readBuff[jj] &&  0 != readBuff[jj - 1])
      printf("\n");
     else
      printf("%c", readBuff[jj]);
    }/*if */

    jj++; 
   }
   if(FALSE != isFoundDataFlag)
    break;
   remainLen -= MAX_STR_LEN;
   ii++;
  }

  if(FALSE == isFoundDataFlag)
  {
   printf("no data flag has been found\n");
   return -2;
  }/*if FALSE == isFoundDataFlag*/

  fseek(pWaveFile, -(MAX_STR_LEN-jj), SEEK_CUR);
 }/*local variables*/


 WaveData wavData;

 fread(&wavData, sizeof(WaveData), 1, pWaveFile);
 sts = GetWaveLength((unsigned char*)&wavData, &audioDataLen);

 if(-1 == sts)
 {
  printf("not a wav format\n");
  return -1;
 }

 audioDataLen = wavData.Subchunk2Size; 

 if(GetRemainderFileSize(pWaveFile) != audioDataLen)
 {
  printf("warning: data length is not consistency\n");
  
  unsigned int remainderLen;
  remainderLen = GetRemainderFileSize(pWaveFile);

  printf("remainderLen = %u\n", remainderLen);  
  printf("audioDataLen = %u\n", audioDataLen);

  if((unsigned int)audioDataLen > remainderLen)
   audioDataLen = remainderLen;
 }/*if */

 float audioPlayTime;
 audioPlayTime = (float)audioDataLen/bytesPerSec;

 printf("audio time = %3.2f sec", audioPlayTime);

 if(audioPlayTime > 60.0f)
  printf(" (%4.3fmins)\n", audioPlayTime/60.0f); 
 else
  printf("\n");

 return 0;
 
}/*main*/


You could use command line -i XXX.wav to specified what file you want to read.



Above is the result when I read a wave file, 03 Rondeau in C Minor.wav.






沒有留言:

張貼留言