ArmDeveloperEcosystem / microphone-library-for-pico

Capture audio from a microphone on your Raspberry Pi Pico or any RP2040 based board. 🎤
Apache License 2.0
253 stars 50 forks source link

Audio samples being skipped while recording using analog microphone #5

Closed mahaju closed 3 years ago

mahaju commented 3 years ago

I made the following changes to the main.c file in the hello_analog_microphone sample

/*
 * Copyright (c) 2021 Arm Limited and Contributors. All rights reserved.
 *
 * SPDX-License-Identifier: Apache-2.0
 * 
 * 
 * This examples captures data from an analog microphone using a sample
 * rate of 8 kHz and prints the sample values over the USB serial
 * connection.
 */

#include <stdio.h>

#include "pico/stdlib.h"
#include "pico/analog_microphone.h"
#include "tusb.h"

#define AUDIO_SAMPLE_RATE 8000
#define RECORD_LENGTH_S   10

// configuration
const struct analog_microphone_config config = {
    // GPIO to use for input, must be ADC compatible (GPIO 26 - 28)
    .gpio = 26,

    // bias voltage of microphone in volts
    .bias_voltage = 0.0,

    // sample rate in Hz
    // .sample_rate = 8000,
    .sample_rate = AUDIO_SAMPLE_RATE,

    // number of samples to buffer
    .sample_buffer_size = 256,
};

// variables
int16_t sample_buffer[256];
volatile int samples_read = 0;

int16_t full_record[AUDIO_SAMPLE_RATE * RECORD_LENGTH_S]; 
int full_record_INDEX = 0;

void on_analog_samples_ready()
{
    // callback from library when all the samples in the library
    // internal sample buffer are ready for reading 
    samples_read = analog_microphone_read(sample_buffer, 256);
}

int main( void )
{
    bool break_while = false;

    // initialize stdio and wait for USB CDC connect
    stdio_init_all();
    while (!tud_cdc_connected()) {
        tight_loop_contents();
    }

    printf("hello analog microphone\n");

    // initialize the analog microphone
    if (analog_microphone_init(&config) < 0) {
        printf("analog microphone initialization failed!\n");
        while (1) { tight_loop_contents(); }
    }

    // set callback that is called when all the samples in the library
    // internal sample buffer are ready for reading
    analog_microphone_set_samples_ready_handler(on_analog_samples_ready);

    // start capturing data from the analog microphone
    if (analog_microphone_start() < 0) {
        printf("PDM microphone start failed!\n");
        while (1) { tight_loop_contents();  }
    }

    printf("record audio\n");
    while (1) {
        // store and clear the samples read from the callback
        int sample_count = samples_read;
        samples_read = 0;

        // loop through any new collected samples
        for (int i = 0; i < sample_count; i++) {
            // printf("%d\n", sample_buffer[i]);
            full_record[full_record_INDEX++] = sample_buffer[i];
            if(full_record_INDEX >= AUDIO_SAMPLE_RATE*RECORD_LENGTH_S){
                break_while = true;
                break;      // break inner for loop in case this does not happen at end of loop
                            // at this point AUDIO_SAMPLE_RATE*RECORD_LENGTH_S number of sample should be available in memory
            }
        }
        if(break_while == true) break;      // break the while() loop if this happens
    }
    printf("Recording complete, stop mic\n");
    analog_microphone_stop();
    analog_microphone_deinit();

    printf("Sending recorded data to serial\n");
    // send the AUDIO_SAMPLE_RATE*RECORD_LENGTH_S number of audio samples to USB serial
    for (int i = 0; i < AUDIO_SAMPLE_RATE*RECORD_LENGTH_S; i++) {
        printf("%d: %d\n",i, full_record[i]);
        sleep_ms(2);
    }

    printf("dummy loop --- \n");
    while(1){

    }

    return 0;
}

I am using a different analog microphone, not the one suggested in this example. Note that I have changed .bias_voltage = 0.0,

I am recording 10 seconds of audio at 8000 Hz entirely in memory, then sending all the recorded audio samples to USB serial port, where I display all the data in putty as well as save as putty log file. I use this log file to regenerate audio in the PC.

If I play back the audio samples in matlab the sound is very fast (only high speed, not high pitch), and sounds as though some audio samples are being skipped. Since the number of audio samples matches what I expect, it seems the skipping is happening at the ADC itself. Could you please give me some hints on how to fix this?

I have uploaded a sample of my recording here: https://soundcloud.com/user-202091011/pico-mic-rec-01-samsung-galaxy-s7-over-the-horizon-2016

The sound volume is a bit low but my main problem is the speed of the playback and the audio frames being skipped

The original sound is Samsung galaxy s7 over the horizon (2016): https://www.youtube.com/watch?v=SyJyB2niF3s

sandeepmistry commented 3 years ago

Hi @mahaju,

I've just tried your code and changed the bias voltage to match my microphone, and was unable to reproduce the issue. I'm using macOS to capture the serial data to a .txt file and then the following Python code to convert to a .bin file to import to Audacity as raw data.

I made a small change to the while loop:

    printf("record audio %u\n", to_ms_since_boot(get_absolute_time()));
    while (1) {
        // store and clear the samples read from the callback
        while (samples_read == 0); //// <==== wait for new samples

        int sample_count = samples_read;
        samples_read = 0;

        // loop through any new collected samples
        for (int i = 0; i < sample_count; i++) {
            // printf("%d\n", sample_buffer[i]);
            full_record[full_record_INDEX++] = sample_buffer[i];
            if(full_record_INDEX >= AUDIO_SAMPLE_RATE*RECORD_LENGTH_S){
                break_while = true;
                break;      // break inner for loop in case this does not happen at end of loop
                            // at this point AUDIO_SAMPLE_RATE*RECORD_LENGTH_S number of sample should be available in memory
            }
        }
        if(break_while == true) break;      // break the while() loop if this happens
    }
    printf("Recording complete, stop mic %u\n", to_ms_since_boot(get_absolute_time()));

I've attached the files from my test, I had the YouTube video you linked playing during recording:

data.txt raw.bin.zip recoding.wav.zip

and Python code to convert from .txt to .bin:

import struct

samples = []

with open('data.txt', 'r') as input:
    for line in input:
        if ': ' in line:
            sample = int(line.split(': ')[1])
            samples.append(sample)

with open('raw.bin', 'wb') as output:
    for sample in samples:
        output.write(struct.pack('<h', sample))

Would you be able to try again with the C code change from above? As well as share the text data from the serial output of the recording.

mahaju commented 3 years ago

I checked out your updated code and it is working. Here are the raw data and binary for two recordings I made, the binary created using your python code

putty_com14_1k_sine.log putty_com14_music.log bin_version.zip

I can play the log file in matlab, or binary in audacity by importing as raw data and using following import settings: Screenshot audaciy

Although when I played the file you attached above, it only sounds like static noise. It's the same in txt, bin and the wav versions. Were wrong files uploaded or did I do something incorrectly? I used the same import settings as shown above when importing your file in audacity.

This is my final main.c file

/*
 * Copyright (c) 2021 Arm Limited and Contributors. All rights reserved.
 *
 * SPDX-License-Identifier: Apache-2.0
 * 
 * 
 * This examples captures data from an analog microphone using a sample
 * rate of 8 kHz and prints the sample values over the USB serial
 * connection.
 */

#include <stdio.h>

#include "pico/stdlib.h"
#include "pico/analog_microphone.h"
#include "tusb.h"

#define AUDIO_SAMPLE_RATE 8000
#define RECORD_LENGTH_S   10

// configuration
const struct analog_microphone_config config = {
    // GPIO to use for input, must be ADC compatible (GPIO 26 - 28)
    .gpio = 26,

    // bias voltage of microphone in volts
    .bias_voltage = 0.0,

    // sample rate in Hz
    // .sample_rate = 8000,
    .sample_rate = AUDIO_SAMPLE_RATE,

    // number of samples to buffer
    .sample_buffer_size = 256,
};

// variables
int16_t sample_buffer[256];
volatile int samples_read = 0;

int16_t full_record[AUDIO_SAMPLE_RATE * RECORD_LENGTH_S]; 
int full_record_INDEX = 0;

void on_analog_samples_ready()
{
    // callback from library when all the samples in the library
    // internal sample buffer are ready for reading 
    samples_read = analog_microphone_read(sample_buffer, 256);
}

int main( void )
{
    bool break_while = false;

    // initialize stdio and wait for USB CDC connect
    stdio_init_all();
    while (!tud_cdc_connected()) {
        tight_loop_contents();
    }

    printf("hello analog microphone\n");

    // initialize the analog microphone
    if (analog_microphone_init(&config) < 0) {
        printf("analog microphone initialization failed!\n");
        while (1) { tight_loop_contents(); }
    }

    // set callback that is called when all the samples in the library
    // internal sample buffer are ready for reading
    analog_microphone_set_samples_ready_handler(on_analog_samples_ready);

    // start capturing data from the analog microphone
    if (analog_microphone_start() < 0) {
        printf("PDM microphone start failed!\n");
        while (1) { tight_loop_contents();  }
    }

    printf("record audio\n");
    printf("record audio %u\n", to_ms_since_boot(get_absolute_time()));
    while (1) {
        // store and clear the samples read from the callback
        while (samples_read == 0); //// <==== wait for new samples

        int sample_count = samples_read;
        samples_read = 0;

        // loop through any new collected samples
        for (int i = 0; i < sample_count; i++) {
            // printf("%d\n", sample_buffer[i]);
            full_record[full_record_INDEX++] = sample_buffer[i];
            if(full_record_INDEX >= AUDIO_SAMPLE_RATE*RECORD_LENGTH_S){
                break_while = true;
                break;      // break inner for loop in case this does not happen at end of loop
                            // at this point AUDIO_SAMPLE_RATE*RECORD_LENGTH_S number of sample should be available in memory
            }
        }
        if(break_while == true) break;      // break the while() loop if this happens
    }
    printf("Recording complete, stop mic %u\n", to_ms_since_boot(get_absolute_time()));
    printf("Recording complete, stop mic\n");
    analog_microphone_stop();
    analog_microphone_deinit();

    printf("Sending recorded data to serial\n");
    // send the AUDIO_SAMPLE_RATE*RECORD_LENGTH_S number of audio samples to USB serial
    for (int i = 0; i < AUDIO_SAMPLE_RATE*RECORD_LENGTH_S; i++) {
        printf("%d: %d\n",i, full_record[i]);
        sleep_ms(2);
    }

    printf("dummy loop --- \n");
    while(1){

    }

    return 0;
}
sandeepmistry commented 3 years ago

@mahaju great, thank you for the update!

I'll push a change shortly to update the examples to include the while (samples_read == 0); loop.

Although when I played the file you attached above, it only sounds like static noise. It's the same in txt, bin and the wav versions. Were wrong files uploaded or did I do something incorrectly?

Sorry, the files have lots of noise and there is a faint music on the background. I most likely uploaded the wrong ones.

sandeepmistry commented 3 years ago

Loop added in https://github.com/ArmDeveloperEcosystem/microphone-library-for-pico/commit/00b8a25284eee9bc286bfc01623f55747bae9a72.

I'm going to close this issue now.