Go Down

Topic: wav file playback speed issue (Read 1 time) previous topic - next topic

MohitB

Aug 27, 2018, 03:03 pm Last Edit: Aug 27, 2018, 03:11 pm by MohitB
Hi,

I have seen some really good suggestions for working with wav file on this forum, Currently i am having i
issue while playing back wav files on media player. This issue is not on arduino platform, i am using a board from silicon labs and a analog mems microphone INMP401, i am recording wav files on sdcard interfaced to the board. Previously i have successfully recorded 8bit-mono wav file on arduino mkr zero without playback issue.  So with the silicon labs board when i record 20sec wav file(8bit-mono) at 44100 hz sample rate, file is created with 20sec audio data with correct speed but voice is distorted and when i record 20sec wav file(16bit-mono) at 44100 hz sample rate, file is created with 10sec audio data with double the playback speed but voice is not distorted. I have attached 2 audio file 8bit-mono and 16bit-mono, by looking at it can anyone tell me what i might be doing wrong while recording.

Audio4.wav is 8bit-mono and sound2.wav is 16bit-mono.

DVDdoug

#1
Aug 27, 2018, 06:07 pm Last Edit: Aug 27, 2018, 06:24 pm by DVDdoug
I can't listen to your files right now 'cause I'm at work.

But, every-other byte in AUDIO4 is zero.     

Double-speed playback (half-speed recording) means you are only saving samples at half the sample-rate.   Like, you're writing 41,100 bytes per second instead of 441,00 samples per second.


6v6gt

I guess you have a mess with mono and stereo, like recording in stereo but playing it back as a mono recording.

Maybe look at the headers of the file to confirm the recording format: http://soundfile.sapp.org/doc/WaveFormat/

MohitB

Thanks DVDdoug, As the Audio4.wav is recorded for 20sec at 44100hz(8bit-mono), it should have file size of around 882000 bytes (882kb) and as the actual file size is 863KB, doesnt that mean data is sampled correctly.

Hi 6v6gt, i have already gone through this document and in my wav header i am defining number of channel as 1, which is mono.

el_supremo

#4
Aug 28, 2018, 05:11 pm Last Edit: Aug 28, 2018, 05:12 pm by el_supremo
Quote
as the actual file size is 863KB, doesnt that mean data is sampled correctly
No. Neither of them have the data recorded correctly. As @DVDdoug pointed out, "every-other byte in AUDIO4 is zero". The data in the stereo file are also incorrect. Also, both your files have an incorrect file size recorded in the wav header.

Post your code (in code tags using the </> icon).

Pete
Don't send me technical questions via Private Message.

MohitB

I am posting only the wav header part as code is not on arduino platform,

Code: [Select]
typedef struct {
    char id[RIFF_ID_LENGTH];
    uint32_t size;
} chunk_t;

typedef struct {
    uint16_t format;
    uint16_t numberOfChannels;
    uint32_t samplesPerSecond;
    uint32_t bytesPerSecond;
    uint16_t bytesPerCapture;
    uint16_t bitsPerSample;
} wavFormat_t;

typedef struct {
    chunk_t riff;
    char format[RIFF_ID_LENGTH];
    chunk_t fmt;
    wavFormat_t wavFormat;
    chunk_t data;
} wavHeader_t;

static wavHeader_t wavHeader = {
    .riff = {.id = "RIFF", .size = 0},
    .format = "WAVE",
    .fmt = {.id = "fmt ", .size = 16},
    .wavFormat = {.format = 1, .numberOfChannels = 1, .samplesPerSecond = 0, .bytesPerSecond = 0, .bytesPerCapture = 2, .bitsPerSample = 16},
    .data = {.id = "data", .size = 0}
};

void setHeaderDetails(uint32_t sampleRate, uint32_t numberOfSamples)
{
    wavHeader.wavFormat.samplesPerSecond = sampleRate;
    wavHeader.wavFormat.bytesPerSecond = sampleRate;
    wavHeader.riff.size = sizeof(wavHeader_t) + numberOfSamples- sizeof(chunk_t);
    wavHeader.data.size = sizeof(wavHeader_t) + numberOfSamples;
}


Basically i am using timer to trigger ADC at desired sample rate and after that through DMA i am collecting samples and writing data to sdcard. At the start of recording i will write wav header to file and update final file size using setHeaderDetails() function at end of recording.

Also when i recorded wav file on mkr zero, there's slight background noise. I cant figure out is it due to power supply or other factor.
(Zip file contains file recorded from mkr zero)

el_supremo

Here's some info and comments on the header of one of your files:
Code: [Select]
-a----       1999-12-31   1:30 PM         203660 SOUND38.WAV

.\wav_header .\Downloads\SOUND38.WAV
wav_header V1.02
riff       = RIFF
flength    = 203652      //This is correct = length of whole file minus 8
wave       = WAVE
fmt        = fmt
blocksize  = 16          // PCM = 16
formattag  = 1           // PCM = 1
numchans   = 1           // mono = 1
srate      = 18750       // sample rate
bytes/sec  = 18750       //
bytes/samp = 1           // 1 byte per sample
bits/samp  = 8           // 8 bits per sample
data       = data        // data chunk
dlength    = 203652      // WRONG - This should be flength-36 = 203608



Your data is incorrect too. 8-bit data are unsigned offset by 128 (=0x80). Yours don't appear to have the offset applied - all the values are above 0x80. But if you don't post your code, I can't help you with that.

Pete
Don't send me technical questions via Private Message.

MohitB

This is the code for my recording with mkr zero and analog mic

Code: [Select]

#include <SD.h>
#include <SPI.h>

#define TIME_LIMIT    15  // (seconds)
#define PIN                 A3

File rec;
uint8_t buffer[512],buffer1[512];
volatile int available = 0;
volatile int read = 0;
unsigned long recordStart = 0;
bool recordInProgress = false;
char filename[16];
unsigned long fileSize = 0L;
unsigned long waveChunk = 16;             //16 for PCM
unsigned int waveType = 1;
unsigned int numChannels = 1;            //1:Mono 2:Stereo
unsigned long sampleRate = 18750;        //Sample_rate
unsigned long bytesPerSec = 18750;       //(Sample_rate * BitsPerSample * Channels) / 8
unsigned int blockAlign = 1;             //(BitsPerSample * Channels) / 8
unsigned int bitsPerSample = 8;          
unsigned long dataSize = 0L;
unsigned long recByteSaved = 0L;
byte byte1, byte2, byte3, byte4;
byte bufferToWrite[2048];
byte buffer_val[2048];
int lenOfBufferToWrite = 0;
int temp = 0;
int16_t analog_mic;

static int n = 0;              //variable to increment file number

const byte gClk = 0; //used to define which generic clock we will use for ADC
const int cDiv = 1; //divide factor for generic clock

void setup()
{
    Serial.begin(115200);

    // see if the card is present and can ben initialized
    if (!SD.begin())
    {
       Serial.println("Card SD failed or not present");    
       return; // do nothing
    }
    setMeasPin();
    genericClockSetup(gClk,cDiv); //setup generic clock and routed it to ADC
    aDCSetup();
}

void loop()
{
  if(!recordInProgress)
   {
    StartRec();
    while (ADC->INTFLAG.bit.RESRDY == 0);   // Waiting for conversion to complete
    analog_mic = ADC->RESULT.reg;
   }
    if (millis() - recordStart <= TIME_LIMIT * 1000)
    {
      
           for (int i = 0; i < 96; i++)
            {
                while (ADC->INTFLAG.bit.RESRDY == 0);   // Waiting for conversion to complete
                analog_mic = ADC->RESULT.reg;
                bufferToWrite[lenOfBufferToWrite + i] = analog_mic;
            }
           lenOfBufferToWrite += 96;
            if (lenOfBufferToWrite == 2016)
            {
                // Write on SD card by batches of 2048 bits for better performance
                rec.write(bufferToWrite, 2016);
                lenOfBufferToWrite = 0;
                recByteSaved += 2016;
            }
    }
    else if (recordInProgress)
    {

        Serial.println(recByteSaved);
        StopRec();
    }
}

void StartRec()
{ // begin recording process
    writeWavHeader();
    Serial.println(F("Starting recording"));
    recordStart = millis();
    recordInProgress = true;
}

void StopRec()
{ // stop recording process, update WAV header, close file
    Serial.println(F("Ending recording"));
    writeOutHeader();
    recordInProgress = false;
}

void writeOutHeader()
{ // update WAV header with final filesize/datasize
    rec.seek(4);
    recByteSaved = recByteSaved + 36;
    byte1 = recByteSaved & 0xff;
    byte2 = (recByteSaved >> 8) & 0xff;
    byte3 = (recByteSaved >> 16) & 0xff;
    byte4 = (recByteSaved >> 24) & 0xff;
    rec.write(byte1);
    rec.write(byte2);
    rec.write(byte3);
    rec.write(byte4);

    recByteSaved = recByteSaved - 36;
    rec.seek(40);
    rec.write(byte1);
    rec.write(byte2);
    rec.write(byte3);
    rec.write(byte4);
    rec.close();
    Serial.println("File closed!");

}

void writeWavHeader()
{ // write out original WAV header to file
    recByteSaved = 0;
    snprintf(filename, sizeof(filename), "audio%d.wav", n);
    if (SD.exists(filename))
    {
      //SD.remove(filename);
      n++;
      snprintf(filename, sizeof(filename), "audio%d.wav", n);
      Serial.println(filename);
    }
    rec = SD.open(filename, FILE_WRITE);
    Serial.println("Open file soundfile!");
    rec.write("RIFF");
    byte1 = fileSize & 0xff;
    byte2 = (fileSize >> 8) & 0xff;
    byte3 = (fileSize >> 16) & 0xff;
    byte4 = (fileSize >> 24) & 0xff;
    rec.write(byte1);
    rec.write(byte2);
    rec.write(byte3);
    rec.write(byte4);
    rec.write("WAVE");
    rec.write("fmt ");
    byte1 = waveChunk & 0xff;
    byte2 = (waveChunk >> 8) & 0xff;
    byte3 = (waveChunk >> 16) & 0xff;
    byte4 = (waveChunk >> 24) & 0xff;
    rec.write(byte1);
    rec.write(byte2);
    rec.write(byte3);
    rec.write(byte4);
    byte1 = waveType & 0xff;
    byte2 = (waveType >> 8) & 0xff;
    rec.write(byte1);
    rec.write(byte2);
    byte1 = numChannels & 0xff;
    byte2 = (numChannels >> 8) & 0xff;
    rec.write(byte1);
    rec.write(byte2);
    byte1 = sampleRate & 0xff;
    byte2 = (sampleRate >> 8) & 0xff;
    byte3 = (sampleRate >> 16) & 0xff;
    byte4 = (sampleRate >> 24) & 0xff;
    rec.write(byte1);
    rec.write(byte2);
    rec.write(byte3);
    rec.write(byte4);
    byte1 = bytesPerSec & 0xff;
    byte2 = (bytesPerSec >> 8) & 0xff;
    byte3 = (bytesPerSec >> 16) & 0xff;
    byte4 = (bytesPerSec >> 24) & 0xff;
    rec.write(byte1);
    rec.write(byte2);
    rec.write(byte3);
    rec.write(byte4);
    byte1 = blockAlign & 0xff;
    byte2 = (blockAlign >> 8) & 0xff;
    rec.write(byte1);
    rec.write(byte2);
    byte1 = bitsPerSample & 0xff;
    byte2 = (bitsPerSample >> 8) & 0xff;
    rec.write(byte1);
    rec.write(byte2);
    rec.write("data");
    byte1 = dataSize & 0xff;
    byte2 = (dataSize >> 8) & 0xff;
    byte3 = (dataSize >> 16) & 0xff;
    byte4 = (dataSize >> 24) & 0xff;
    rec.write(byte1);
    rec.write(byte2);
    rec.write(byte3);
    rec.write(byte4);
}

static void ADCsync()
{
  while (ADC->STATUS.bit.SYNCBUSY == 1); //Just wait till the ADC is free
}

void aDCSetup() {
  // Select reference
  REG_ADC_REFCTRL = 0; //set vref for ADC to VCC

  // Average control 1 sample, no right-shift
  REG_ADC_AVGCTRL |= ADC_AVGCTRL_SAMPLENUM_1;

  // Sampling time, no extra sampling half clock-cycles
  REG_ADC_SAMPCTRL = ADC_SAMPCTRL_SAMPLEN(0);
  
  // Input control and input scan
  REG_ADC_INPUTCTRL |= ADC_INPUTCTRL_GAIN_1X | ADC_INPUTCTRL_MUXNEG_GND | ADC_INPUTCTRL_MUXPOS_PIN3;
  // Wait for synchronization
  while (REG_ADC_STATUS & ADC_STATUS_SYNCBUSY);

  ADC->CTRLB.reg |= ADC_CTRLB_RESSEL_8BIT | ADC_CTRLB_PRESCALER_DIV512 | ADC_CTRLB_FREERUN; //This is where you set the divide factor, note that the divide call has no effect until you change Arduino wire.c
  //Wait for synchronization
  while (REG_ADC_STATUS & ADC_STATUS_SYNCBUSY);

  ADC->WINCTRL.reg = ADC_WINCTRL_WINMODE_DISABLE; // Disable window monitor mode
  while(ADC->STATUS.bit.SYNCBUSY);

  ADC->INPUTCTRL.bit.MUXPOS = g_APinDescription[PIN].ulADCChannelNumber; // Selection for the positive ADC input
  while(ADC->STATUS.bit.SYNCBUSY);

  ADC->CTRLA.reg |= ADC_CTRLA_ENABLE; //set ADC to run in standby
  while (ADC->STATUS.bit.SYNCBUSY);
}

//setup measurement pin, using Arduino ADC pin A3
void setMeasPin() {
  // Input pin for ADC Arduino A3/PA04
  REG_PORT_DIRCLR1 = PORT_PA04;

  // Enable multiplexing on PA04
  PORT->Group[0].PINCFG[4].bit.PMUXEN = 1;
  PORT->Group[0].PMUX[1].reg = PORT_PMUX_PMUXE_B | PORT_PMUX_PMUXO_B;
}

//this function sets up the generic clock that will be used for the ADC unit
//by default it uses the 48M system clock, input arguments set divide factor for generic clock and choose which generic clock
void genericClockSetup(int clk, int dFactor) {
  // Enable the APBC clock for the ADC
  REG_PM_APBCMASK |= PM_APBCMASK_ADC;
  
  //This allows you to setup a div factor for the selected clock certain clocks allow certain division factors: Generic clock generators 3 - 8 8 division factor bits - DIV[7:0]
  GCLK->GENDIV.reg |= GCLK_GENDIV_ID(clk)| GCLK_GENDIV_DIV(dFactor);
  while (GCLK->STATUS.reg & GCLK_STATUS_SYNCBUSY);  

  //configure the generator of the generic clock with 48MHz clock
  GCLK->GENCTRL.reg |= GCLK_GENCTRL_GENEN | GCLK_GENCTRL_SRC_DFLL48M | GCLK_GENCTRL_ID(clk); // GCLK_GENCTRL_DIVSEL don't need this, it makes divide based on power of two
  while (GCLK->STATUS.reg & GCLK_STATUS_SYNCBUSY);
  
  //enable clock, set gen clock number, and ID to where the clock goes (30 is ADC)
  GCLK->CLKCTRL.reg |= GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN(clk) | GCLK_CLKCTRL_ID(30);
  while (GCLK->STATUS.bit.SYNCBUSY);
}

6v6gt

#8
Sep 01, 2018, 02:22 pm Last Edit: Sep 01, 2018, 02:24 pm by 6v6gt
I guess it is a 10 bit ADC so you should probably handle any normalisation (like compensating for the DC offset on the microphone and adjusting the volume) in an unsigned 16 bit integer before writing the value to an element of the unsigned 8 bit array.

I don't know the STM32 very well, but maybe you are able to query the ADC, do the normalisation of the ADC value and write the value to the buffer all in the timer call back routine. In the loop(), just do the dump to the SD card.

Using audacity is quite good for visualising the sound files. Here is a view of yours before and after normalisation.




el_supremo

Code: [Select]
   recByteSaved = recByteSaved - 36;
    rec.seek(40);
    rec.write(byte1);
    rec.write(byte2);
    rec.write(byte3);
    rec.write(byte4);


When you write recByteSaved the second time, you haven't converted it into byte1, byte2 etc. Therefore you write out the same number as before.
You need to insert this after the seek:
Code: [Select]
    byte1 = recByteSaved & 0xff;
    byte2 = (recByteSaved >> 8) & 0xff;
    byte3 = (recByteSaved >> 16) & 0xff;
    byte4 = (recByteSaved >> 24) & 0xff;



For the ADC value itself, your code is only storing the low eight bits of the ADC value. Assuming that it is a 10-bit ADC and that your audio input is correctly offset to the centre of the ADC range (512), you can modify the line which stores the value in the array to this:
Code: [Select]
bufferToWrite[lenOfBufferToWrite + i] = analog_mic >> 2;
If it is a 12-bit ADC, shift right 4 places, >> 4.


What is going on here?
Code: [Select]
           if (lenOfBufferToWrite == 2016)
            {
                // Write on SD card by batches of 2048 bits for better performance
                rec.write(bufferToWrite, 2016);

You are writing 2016 bytes but the comment says 2048.

Pete
Don't send me technical questions via Private Message.

Lucario448

#10
Sep 02, 2018, 08:58 pm Last Edit: Sep 02, 2018, 08:59 pm by Lucario448
but voice is distorted
Looking closely with Audacity at your files, I noticed this in AUDIO4.WAV (I had to amplify, otherwise is hard to tell):

This is not normal, if the wave becomes chopped like this; it means some samples are missing. I bet this is the culprit of your distortion problem (SOUND2.WAV has this same issue too).


Sampling at 44100 Hz might be too precise for some applications; you have only aprox. 22.68 microseconds to spare between samples. If some process takes longer than that, you'll definitely skip some and probably create a little of jitter.

I can say there may be two reasons why this is happening:
  • An interrupt request (most likely by a timer) is delaying the time-critical sample capture.
  • Lack of DMA in the SPI controller. If this transfer is CPU-driven, it will block the program execution; it even postpones any pending IRQ.

To make matters worse, the SD's library doesn't transfer data byte by byte, but in blocks of 512 bytes (aka 1 sector); plus a read back of the next block and some filesystem overhead (if the file grows surpassing the cluster boundary defined in such filesystem). In the worst case (file fragmentation), a simple 512-byte block transfer ends up in 4096 bytes worth of SPI I/O due to filesystem overhead.
If DMA were available, the program execution will be blocked once every 512 bytes, and not every byte 512 times (obviating any other IRQ).


Delays caused by interrupts are inavoidable, even when using DMA (data transfer completion triggers an IRQ as well); thus I suggest you the double and maybe even the triple buffer technique, if RAM allows it.
When the SPI controller lacks of DMA, the only way to make double/triple buffer useful is by making the sampling process interrupt-driven. This way, while the main program takes care of the samples transfer (to the SD card) in one buffer, the interrupt samples new data and stores it in another buffer; minimizing losses if not eliminating them.

MohitB

@6v6gt I did normalisation on audacity, still i can hear some background buzz sound. Which technique will be better for implementing noise reduction.

Code: [Select]
  ADC->CTRLB.reg |= ADC_CTRLB_RESSEL_8BIT | ADC_CTRLB_PRESCALER_DIV512 | ADC_CTRLB_FREERUN;

@el_supremo I have set ADC resolution for 8bit in the program, so i dont think i need to shift any bits. But i will try with 10 and 12bit resolution as well.

Code: [Select]
  // Write on SD card by batches of 2048 bits for better performance

and here i was playing with number of data to be written in sdcard at once, i forgot to change that. It was 2016 instead of 2048.

@Lucario448 I havent tried double buffer technique,but will try to implement that.

6v6gt

#12
Sep 03, 2018, 10:01 am Last Edit: Sep 03, 2018, 10:46 am by 6v6gt
If the original sound recording is of poor quality, Audacity is of only limited help in improving it. From the pictures, you can see a large proportion of noise in the signal before your spoken digits and it is that which you have to address first. Anyway, Audacity was suggested only as an analysis tool.

Are you using such a module which produces around 200 mV (I guess offset by half of 3.3 volts) at "normal" volume  ?  https://www.sparkfun.com/products/9868
And you have connected the output directly to the ADC pin and the devices share a common ground ?

The raw data in your file shows a level of around 0xD0 with little movement so it does look like you have captured the 8 most significant bits (somehow) but the dynamic range is very limited.

I don't know the ADC on the device you are using, but the basic configuration for static items such as voltage reference, gain and the 8 bit resolution which you appear to have set, you can test by connecting a linear potentiometer (say 10k) across the power rails with the slider connected to the ADC input. You should get a clean sweep from 0x00 to 0xFF. Clearly, that can't test the dynamic configuration like sample interval etc.

Edit:

Incidentally, does the code which you have posted in #7 run on the Arduino MKRZero ? It looks remarkably arduino like with setup() and loop() , but I can't see where the object ADC is defined. Is something missing here ?



Lucario448

Edit:

Incidentally, does the code which you have posted in #7 run on the Arduino MKRZero ? It looks remarkably arduino like with setup() and loop() , but I can't see where the object ADC is defined. Is something missing here ?
I was wondering about the same; but I guess it's for the Zero since no other architecture implements such thing in the Arduino core (up to my knowledge). Another proof is when you notice that there is no additional libraries other than SD and SPI.

MohitB

Code: [Select]
Are you using such a module which produces around 200 mV (I guess offset by half of 3.3 volts) at "normal" volume  ?  https://www.sparkfun.com/products/9868
And you have connected the output directly to the ADC pin and the devices share a common ground ?

@6v6gt Yes, this is the module i am using and output of mic is connected directly to A3 pin of mkr zero.

Code: [Select]
Incidentally, does the code which you have posted in #7 run on the Arduino MKRZero ? It looks remarkably arduino like with setup() and loop() , but I can't see where the object ADC is defined. Is something missing here ?

Initially this post was not for arduino platform, files uploaded in zip.zip is from a board from silicon labs. I was having playback issue in those file. But later in #5 i uploaded files i recorded on mkr zero as i didnt have playback issue but there was constant background buzz noise.

Go Up