Forum Rule: Always post complete source code & details to reproduce any issue!
Results 1 to 15 of 15

Thread: Record on SD using AudioStream

  1. #1
    Junior Member
    Join Date
    Mar 2020
    Posts
    7

    Record on SD using AudioStream

    Hi guys,
    I'm an Italian maker with a decent/good background in Arduino and C++. In the past I used Teensy to realize several projects, the last one was a sort of answering machine, made with Teensy 3.6. The project worked very well, but I found the approach to audio recording really bad compared to the one used for audio play. So I started writing a hereditary AudioStream class for recording WAV files on SD cards. The library is based on the example file Recorder.ino.

    My setup:
    - Teensy4.0 with SD card reader soldered under the board using pins 34-39
    - microSDHC Class10 UHS-I Transcend 16GB
    - Visual Studio Code wtih Platformio

    I'm sure the setup is working properly, as the Recorder.ino sketch works.

    The problem: after recording, the data block of the wav file is a long succession of 0.

    My files:
    record_sd_wav.h
    Code:
    #ifndef record_sd_wav_h_
    #define record_sd_wav_h_
    
    #include "Arduino.h"
    #include "AudioStream.h"
    #include "SD.h"
    
    #if defined(__IMXRT1062__) || defined(__MK66FX1M0__) || defined(__MK64FX512__)
    
    #define STATE_STOP 13
    #define STATE_REC 14
    
    class AudioRecordSdWav : public AudioStream
    {
    public:
        AudioRecordSdWav(void) : AudioStream(1, inputQueueArray) { begin(); }
        void begin(void);
        bool record(const char *filename);
        bool isRecording(void) { return state_record == STATE_REC; }
        void stop(void);
        virtual void update(void);
    
    private:
        audio_block_t *inputQueueArray[1];
    
        void write_header(void);
        // wav header
        uint32_t ChunkSize;
        uint32_t Subchunk1Size = 16; //const ?
        uint AudioFormat = 1;        //const ?
        uint NumChannels = 1;
        uint32_t SampleRate = AUDIO_SAMPLE_RATE;
        uint BitsPerSample = 16;                                            //const ?
        uint32_t ByteRate = SampleRate * NumChannels * (BitsPerSample / 8); // samplerate x channels x (bitspersample / 8)
        uint BlockAlign = NumChannels * BitsPerSample / 8;
        uint32_t Subchunk2Size = 0L;
        uint32_t recByteSaved = 0L;
    
        File wavfile;
        uint8_t state_record;
        audio_block_t *blocklist[2];
        uint8_t buffer[512]; // __attribute__((aligned(4))); // I've tried both with and without
        uint8_t i_block;     // index block
    };
    
    #endif
    #endif
    record_sd_wav.cpp
    Code:
    #include "Arduino.h"
    #include "record_sd_wav.h"
    #include "spi_interrupt.h"
    
    #if defined(__IMXRT1062__) || defined(__MK66FX1M0__) || defined(__MK64FX512__)
    
    void AudioRecordSdWav::begin(void)
    {
    	state_record = STATE_STOP;
    	recByteSaved = 0;
    	i_block = 0;
    
    	if (blocklist[0])
    	{
    		release(blocklist[0]);
    		blocklist[0] = NULL;
    	}
    	if (blocklist[1])
    	{
    		release(blocklist[1]);
    		blocklist[1] = NULL;
    	}
    }
    
    bool AudioRecordSdWav::record(const char *filename)
    {
    	stop();
    	AudioStartUsingSPI();
    	__disable_irq();
    	wavfile = SD.open(filename, FILE_WRITE);
    	__enable_irq();
    	if (!wavfile)
    	{
    		AudioStopUsingSPI();
    		return false;
    	}
    	state_record = STATE_REC;
    	recByteSaved = 0;
    	i_block = 0;
    	return true;
    };
    
    void AudioRecordSdWav::stop(void)
    {
    	__disable_irq();
    	if (state_record != STATE_STOP)
    	{
    		write_header();
    		wavfile.close();
    		release(blocklist[0]);
    		release(blocklist[1]);
    		__enable_irq();
    		AudioStopUsingSPI();
    		state_record = STATE_STOP;
    	}
    	else
    	{
    		__enable_irq();
    	}
    }
    
    void AudioRecordSdWav::update(void)
    {
    	audio_block_t *block;
    	block = receiveReadOnly();
    	if (!block)
    		return;
    
    	if (state_record == STATE_STOP) // or !wavfile)
    	{
    		release(block);
    		return;
    	}
    	blocklist[i_block] = block;
    	i_block = (i_block + 1) % 2;
    	if (!i_block)
    	{
    		memcpy(buffer + 0x000, blocklist[0]->data, 256);
    		memcpy(buffer + 0x100, blocklist[1]->data, 256);
    		// only to debug
    		for (int i = 0; i < 8; i++)
    		{
    			Serial.print(buffer[i], HEX);
    			Serial.print(" ");
    		}
    		Serial.println();
    		// end only to debug
    		__disable_irq();
    		wavfile.write(buffer, 512);
    		recByteSaved += 512;
    		release(blocklist[0]);
    		release(blocklist[1]);
    		__enable_irq();
    	}
    }
    void AudioRecordSdWav::write_header(void)
    {
    	Subchunk2Size = recByteSaved;
    	ChunkSize = Subchunk2Size + 36;
    	wavfile.seek(0);
    	// "RIFF" chunck descriptor
    	wavfile.write("RIFF"); // ChunkID
    	wavfile.write(ChunkSize & 0xff);
    	wavfile.write((ChunkSize >> 8) & 0xff);
    	wavfile.write((ChunkSize >> 16) & 0xff);
    	wavfile.write((ChunkSize >> 24) & 0xff);
    	wavfile.write("WAVE"); // Format
    	// "fmt" sub-chunck
    	wavfile.write("fmt "); // Subchunk1ID
    	wavfile.write(Subchunk1Size & 0xff);
    	wavfile.write((Subchunk1Size >> 8) & 0xff);
    	wavfile.write((Subchunk1Size >> 16) & 0xff);
    	wavfile.write((Subchunk1Size >> 24) & 0xff);
    	wavfile.write(AudioFormat & 0xff);
    	wavfile.write((AudioFormat >> 8) & 0xff);
    	wavfile.write(NumChannels & 0xff);
    	wavfile.write((NumChannels >> 8) & 0xff);
    	wavfile.write(SampleRate & 0xff);
    	wavfile.write((SampleRate >> 8) & 0xff);
    	wavfile.write((SampleRate >> 16) & 0xff);
    	wavfile.write((SampleRate >> 24) & 0xff);
    	wavfile.write(ByteRate & 0xff);
    	wavfile.write((ByteRate >> 8) & 0xff);
    	wavfile.write((ByteRate >> 16) & 0xff);
    	wavfile.write((ByteRate >> 24) & 0xff);
    	wavfile.write(BlockAlign & 0xff);
    	wavfile.write((BlockAlign >> 8) & 0xff);
    	wavfile.write(BitsPerSample & 0xff);
    	wavfile.write((BitsPerSample >> 8) & 0xff);
    	// "data" sub-chunk
    	wavfile.write("data"); // Subchunk2ID
    	wavfile.write(Subchunk2Size & 0xff);
    	wavfile.write((Subchunk2Size >> 8) & 0xff);
    	wavfile.write((Subchunk2Size >> 16) & 0xff);
    	wavfile.write((Subchunk2Size >> 24) & 0xff);
    }
    #endif
    main.h
    Code:
    #include <Audio.h>
    #include <Wire.h>
    #include <SPI.h>
    #include <SD.h>
    //#include <SD_t3.h> // I've tried both with and without
    #include <SerialFlash.h>
    
    #include "record_sd_wav.h"
    
    //AudioInputI2S rec_source;
    AudioSynthWaveformSine rec_source;
    AudioRecordSdWav sdWav;
    
    AudioOutputI2S out_i2s; // <- Why do I need this?
    
    AudioConnection patchCord(rec_source, sdWav);
    
    void setup()
    {
        AudioMemory(60);
        rec_source.amplitude(1);
        rec_source.frequency(432);
    
        Serial.begin(115200);
        while (!Serial)
        {
        }
        // Initialize the SD card
        while (!(SD.begin(BUILTIN_SDCARD)))
        {
            Serial.println("Unable to access the SD card");
            delay(500);
        }
        Serial.println("Start recording");
        pinMode(LED_BUILTIN, OUTPUT);
        digitalWriteFast(LED_BUILTIN, HIGH);
        Serial.println(sdWav.record("test.wav"));
        delay(3000);
        sdWav.stop();
        digitalWriteFast(LED_BUILTIN, LOW);
        Serial.println("Stop recording");
        delay(1000);
        // print wav file on Serial Monitor
        File wavfile = SD.open("test.wav");
        Serial.println("header:");
        for (int i = 0; i < 44; i++)
        {
            int value = wavfile.read();
            if (value < 16)
                Serial.print("0");
            Serial.print(value, HEX);
            if (i % 4 == 3)
                Serial.println();
            else
                Serial.print(" ");
        }
        Serial.println("\ndata:");
        for (int i = 0; i < 20; i++)
        {
            int value = wavfile.read();
            if (value < 16)
                Serial.print("0");
            Serial.print(value, HEX);
            if (i % 4 == 3)
                Serial.println();
            else
                Serial.print(" ");
        }
        Serial.println("\n\n");
    }
    bool var = true;
    void loop()
    {
        delay(100);
    }
    Part of Serial Monitor output:
    Code:
    2D 80 D5 80 F5 81 92 83 
    58 7F 4F 7E CE 7C D1 7A 
    6E 81 D8 82 B9 84 14 87 
    7E 7D B3 7B 73 79 BB 76 
    E1 83 B 86 A8 88 BC 8B 
    Stop recording
    header:
    52 49 46 46
    24 6E 04 00
    57 41 56 45
    66 6D 74 20
    10 00 00 00
    01 00 01 00
    44 AC 00 00
    88 58 01 00
    02 00 10 00
    64 61 74 61
    00 6E 04 00
    
    data:
    00 00 00 00
    00 00 00 00
    00 00 00 00
    00 00 00 00
    00 00 00 00
    If I connect the sine with a DIY I2S DAC (with PCM5100) it feels great and if I print in the serial monitor part of the recorder buffer you can see that the values are not 0, so I think it's a saving problem on the SD card, but the header saves it correctly, so what can it be?
    I also realized that if I don't declare an output, my custom class doesn't update it, why?

    Thanks a lot.

  2. #2
    Senior Member+ Frank B's Avatar
    Join Date
    Apr 2014
    Location
    Germany NRW
    Posts
    6,570
    For your last question:

    Quote Originally Posted by Paso View Post
    I also realized that if I don't declare an output, my custom class doesn't update it, why?
    You will see the same with USB. The library need at least an input or output (best is I2s) - it uses it as a timer to be able to call the internal update() (i.e. your AudioRecordSdWav::update(void) ) in the right intervals.

  3. #3
    Junior Member
    Join Date
    Mar 2020
    Posts
    7
    My friend successfully tried the sketch with Teensy 3.6 and microSDHC Class6 Traxdata 4GB.
    Part of Serial Monitor output:
    Code:
    header:
    52 49 46 46
    24 8C 05 00
    57 41 56 45
    66 6D 74 20
    10 00 00 00
    01 00 01 00
    55 AC 00 00
    AA 58 01 00
    02 00 10 00
    64 61 74 61
    00 8C 05 00
    
    data:
    80 7E 74 7F
    EF 7F EC 7F
    6E 7F 73 7E
    00 7D 12 7B
    AE 78 D4 75
    Unlike the previous output, the sampling fequency is 44117 Hz instead of 44100Hz. As a result, the sample size increased from 3.3s to 4.1s. In this second case the frequency is perfect at 432Hz.

    I'm getting more and more confused.

  4. #4
    Senior Member+ Frank B's Avatar
    Join Date
    Apr 2014
    Location
    Germany NRW
    Posts
    6,570
    All Teensy 3.x use 44117Hz as frequency. That's perfectly normal.

  5. #5
    Junior Member
    Join Date
    Mar 2020
    Posts
    7
    Thank you Frank for your always quick response. But I'd already read the documentation, and what I don't understand is something else.
    How can it be that a powerful and precise board like Teensy 4.0 can't write on a high performance SD card, while a "worse" board with a bad SD card can do it with perfect frequency? Also recording a longer sample (I know, delay() is not very precise)

  6. #6
    Senior Member+ Frank B's Avatar
    Join Date
    Apr 2014
    Location
    Germany NRW
    Posts
    6,570
    Oh I'm sure it can (sorry, havn't looked at your sourcecode).
    @Others: Where is the problem?

    Maybe you can upload a working sketch here (as zip) - makes it easier for us!

  7. #7
    Junior Member
    Join Date
    Mar 2020
    Posts
    7
    The source code is in the first post, it is the same one that was tested on both Teensy 4.0 and Teensy 3.6. I don't see how zipping it makes any difference. Anyway, I did.
    RecorderSdWav.zip

  8. #8
    Senior Member+ Frank B's Avatar
    Join Date
    Apr 2014
    Location
    Germany NRW
    Posts
    6,570
    Quote Originally Posted by Paso View Post
    I don't see how zipping it makes any difference. Anyway, I did.
    RecorderSdWav.zip
    How gracious of you to help us in our search for your bug!
    However, it displays this on a Teensy 4:

    Code:
    8B 87 70 8A C5 8D 8B 91 
    
    34 76 F4 72 47 6F 2C 6B 
    
    53 8C EC 8F EE 93 5B 98 
    
    DE 70 EE 6C 9A 68 DC 63 
    
    71 44 F6 4A 34 51 21 57 
    
    Stop recording
    
    header:
    
    52 49 46 46
    
    24 30 06 00
    
    57 41 56 45
    
    66 6D 74 20
    
    10 00 00 00
    
    01 00 01 00
    
    44 AC 00 00
    
    88 58 01 00
    
    02 00 10 00
    
    64 61 74 61
    
    00 30 06 00
    
    
    data:
    
    2A 4A 9B 43
    
    CB 3C C0 35
    
    81 2E 14 27
    
    83 1F D2 17
    
    0A 10 33 08

  9. #9
    Senior Member+ Frank B's Avatar
    Join Date
    Apr 2014
    Location
    Germany NRW
    Posts
    6,570
    ...but, writing to SD in a interrupt is a very bad idea.
    use the queues.

  10. #10
    Junior Member
    Join Date
    Mar 2020
    Posts
    7
    Quote Originally Posted by Frank B View Post
    However, it displays this on a Teensy 4:
    Can you share more information? Like what SD card reader do you use?

    Quote Originally Posted by Frank B View Post
    writing to SD in a interrupt is a very bad idea.
    Why? Can you explain it with some technical information?
    When I write to the SD card interrupts are disabled, as explained in the documentation.

  11. #11
    Senior Member+ Frank B's Avatar
    Join Date
    Apr 2014
    Location
    Germany NRW
    Posts
    6,570
    No reader. My own dev-board - it's just a socket on the board.
    You're writing in the update() method. That means you have only 2.8ms to write. If the SD Card decides to stall (due to internal wear-levelling or other magical things SD use to do) you'll have more than that 2.8ms. You know that SD-Cards have a internal CPU?
    Maybe it does not work for you becaue of that(?) - I used a brandnew card.
    Anyway, mid-term you will not have much fun with writing in update().

    Use google, you'll find many threads about this behaviour. Worst-case it can be several hundred milliseconds. You problems will start with only 1-2 millisconds...

    Using the queue is the _perfect_ way to do it. Together with large AudioMemory (as large as possible - if still not enough use extra arrays)
    Last edited by Frank B; 03-29-2020 at 08:45 PM.

  12. #12
    Junior Member
    Join Date
    Mar 2020
    Posts
    7
    Quote Originally Posted by Frank B View Post
    No reader. My own dev-board - it's just a socket on the board.
    So you use pins 34-39 to manage the SD card too?

    I measured the write times on the microSD (practically new) and although they are very variable, they never reach the millisecond.
    Here's how I made the measurement:
    Code:
        
    uint32_t m = micros();
    __disable_irq();
    wavfile.write(buffer, 512);
    recByteSaved += 512;
    release(blocklist[0]);
    release(blocklist[1]);
    __enable_irq();
    uint32_t mm = micros();
    Serial.println(mm - m);
    Also, doesn't the __disable_irq() function disable all interrupts, avoiding writing problems? Or do I only have 2.8ms anyway? Why?

    Anyway, today I managed to recover another SD card ( ScanDisk Industrial 8GB, which one did you use?)and magically with this one it works. It remains for me to understand why times are so different between teensy 4.0 and teensy 3.6.

  13. #13
    Senior Member+ Frank B's Avatar
    Join Date
    Apr 2014
    Location
    Germany NRW
    Posts
    6,570
    If it takes longer you'll just loose Audio data and this makes your recording worthless.
    I don't mind if you don't believe me that it can take a long time. It's your program, not mine, just do what you want

    It does not matter that you see short times.You'll see it sooner or later. The brand of the card does not matter that much (all are from china, only a few manufacturers)
    Some brands may be better - but even they the use different chips for the "same" cards.

    You'll have 2.8ms because after 2.8ms the buffers are full and the interrupts are called. If you stop the interrupts longer, you loose data.

  14. #14
    Junior Member
    Join Date
    Mar 2020
    Posts
    7
    Preamble: I'm trying to understand, I don't want to impose my sketch on anyone, or say that it's the best.

    Can you confirm that you use pins 34-39 (same pins, same hardware buffer, I think)?
    I'm not interested in the brand of SD card, but the technical characteristics. I would probably be able to understand as much as possible which are the factors that determine a correct functioning and which are not.

    You say that if it takes more than 2.8ms the interrupts are called (ok, I believe you), but are they called even if I use the __disable_irq() function ? If yes, why if I use queue and write in the loop shouldn't the interrupts be called damaging the writing?

    Quote Originally Posted by Frank B View Post
    If you stop the interrupts longer, you loose data.
    Are you sure about this? In industrial anthropomorphic robots interrupts are queued according to priority, but they never "jump", they are just put on hold (I know, in the audio field it's not a good thing). Isn't it the same on Teensy?

  15. #15
    Senior Member+ Frank B's Avatar
    Join Date
    Apr 2014
    Location
    Germany NRW
    Posts
    6,570
    Yes, I'm sure about this. 1. there is no queue, only a flag. 2. where should the new data be stored? magical memory/ram from deep space? :-)
    You're delaying the interrupt. If it misses one call because the "old" one is still running you'll loose data.

    Edit: Yes i'm using these pins - hey I used your sketch - it would not work with other pins - right?

    Edit: To explain it a bit better:

    You'll delay the update() of the whole library. If the delay is too long, all other updates() will be in trouble.
    As an experiment, open one of the examples with audible output, and add a delay(3) to one (does not matter which) of the update()-functions used.
    This will crash soon, maybe at the 2nd call of the update() chain and you will hear nothing. If it is only sometimes longer than 2.8 ms then it can run, but the sound will be distorted - in case of a output, the DMA will send old, not updated data to the speaker - in case of an input it outputs old data to the following objects.
    Last edited by Frank B; 03-30-2020 at 01:42 PM.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •