1
$\begingroup$

I'm using pyaudio to record sound and return the peak frequency of the recorded sound.

In one example, I use a sound card with 2 channels (stereo), and the peak frequency detected is the correct frequency. I'm testing it with a tone generator app.

Here's the code that I've used:

import pyaudio
import numpy as np
from scipy.fftpack import fft, dct,idct
from scipy import signal
from scipy.signal import argrelmax
from scipy.stats.mstats import gmean
import heapq
import struct
import threading
import Queue
 import time

def callback_1(in_data, frame_count, time_info, status):
    stream_queue_1.put(in_data)
    return (in_data, pyaudio.paContinue)

global stream_queue_1
global stream_queue_2

stream_queue_1 = Queue.Queue()
stream_queue_2 = Queue.Queue()

if __name__ == "__main__":
    reqdRes = 30 #Hz
    fs = 44100
    RATE = 44100
    CHANNELS = 2
    WIDTH = 2
    fftLen = 2**(np.ceil(np.log2(fs/float(reqdRes))))
    CHUNK = 4096#np.int32(fftLen)+1000
    # check for 
    p = pyaudio.PyAudio()
    a = p.get_device_count()

    for i in range (0,a):
        if p.get_device_info_by_host_api_device_index(0,i).get('maxInputChannels')==2:
            print "Input Device id ", i, " - ", p.get_device_info_by_host_api_device_index(0,i).get('name')
        deviceNum = i       

devinfo = p.get_device_info_by_index(deviceNum)
print "Selected device is ",devinfo.get('name')
# List of available audio devices


stream = p.open(format=p.get_format_from_width(WIDTH),
            channels=CHANNELS,
            rate=RATE,
            input=True,
            output=True,
            input_device_index=deviceNum,
            frames_per_buffer=CHUNK,
        stream_callback=callback_1
        )

stream_queue_1 = Queue.Queue()

# Find peaks in the spectrogram/cepstrogram after smoothing each time frame data
def find_peaks(cepstr):
    value = []
    N  = 3    # Filter order
    Wn = 0.1 # Cutoff frequency
    B, A = signal.butter(N, Wn, output='ba')
    arr1 = []
    maxn_rows = []
    relmax_rows = []
    smooth_data = np.array(signal.filtfilt(B,A, cepstr))    # A
    relmax_rows = argrelmax(smooth_data)            # B 
    values = np.array(smooth_data[relmax_rows])# C
    arr1 = heapq.nlargest(3,range(len(values)),values.take) # D         
    try:
        value.append(int(relmax_rows[0][arr1[1]]))
    except IndexError:
        value.append(0)
    return value, relmax_rows

print fftLen

# The arrays to plot frequency and time
full_freq = np.linspace(0,fs,fftLen)
R = []
LSFM_bufSize = 10   
LSFM_buf=[0]*LSFM_bufSize
threshold = -30

# check past 10 frames for fundamental frequency to check the variation in the measured fundamental
# if the fundamental varies a lot, another way to tell that it's noise

while(1):   
    data = stream_queue_1.get()
    shorts = struct.unpack('h'*(CHUNK*2),data)
    input_wave = np.array(list(shorts),dtype = float)
# update the FFT buffer and give space for a new entry  
    LSFM_buf.pop(0)

# take FFT of newest frame and compute cepstrum 
    R = np.abs(dct(np.multiply(input_wave[0:fftLen], np.hamming(fftLen))))/np.sqrt(fftLen)  
    AM = np.mean(R)
    GM = gmean(R)
    LSFM_buf.append(100*np.log10(GM/float(AM)))
# Compute spectral flatness coefficient
    max_LSFM = max(LSFM_buf)
    min_LSFM = min(LSFM_buf)

    max_val,relmax_val= find_peaks(R)
    funda_freq = full_freq[max_val]
# print fundamental frequency
    if funda_freq > fs/2.0:
        funda_freq = fs-funda_freq
        print funda_freq ,' | ',  LSFM_buf[0]
    else:   
        print funda_freq,' | ',  LSFM_buf[0]
# decide whether noise, music or voice
    if max_LSFM<=threshold and funda_freq > 370:
        print 'music'
    elif max_LSFM>threshold:
        print 'noise'
    elif max_LSFM<=threshold and funda_freq <= 370:
        print 'voice'
    print ' '

stream.stop_stream()
stream.close()
p.terminate() 

When I change the sound card to a mono sound card, I change the number of channels to 1, and it detects the device correctly and opens the stream, records etc. correctly. In this case, the peak frequency returned is double of the frequency of the tone generator that I'm giving as input.

Here's the code for this -

import pyaudio
import numpy as np
from scipy.fftpack import fft, dct,idct
from scipy import signal
from scipy.signal import argrelmax
from scipy.stats.mstats import gmean
import heapq
import struct



reqdRes = 30 #Hz

fs = 44100
RATE = 44100
CHANNELS = 1
WIDTH = 2
fftLen = 2**(np.ceil(np.log2(fs/float(reqdRes))))
CHUNK = 4096#np.int32(fftLen)+1000

# check for 
p = pyaudio.PyAudio()

a = p.get_device_count()

for i in range (0,a):
    if p.get_device_info_by_host_api_device_index(0,i).get('maxInputChannels')==CHANNELS:
        print "Input Device id ", i, " - ", p.get_device_info_by_host_api_device_index(0,i).get('name')
    deviceNum = i       

    devinfo = p.get_device_info_by_index(deviceNum)
    print "Selected device is ",devinfo.get('name')
    # List of available audio devices


     stream = p.open(format=p.get_format_from_width(WIDTH),
        channels=CHANNELS,
        rate=RATE,
        input=True,
        output=True,
        input_device_index=deviceNum,
        frames_per_buffer=CHUNK
    )


     # Find peaks in the spectrogram/cepstrogram after smoothing each time frame data
    def find_peaks(cepstr):
        value = []
        N  = 3    # Filter order
        Wn = 0.1 # Cutoff frequency
        B, A = signal.butter(N, Wn, output='ba')
        arr1 = []
        maxn_rows = []
        relmax_rows = []
        smooth_data = np.array(signal.filtfilt(B,A, cepstr))    # A
        relmax_rows = argrelmax(smooth_data)            # B 
        values = np.array(smooth_data[relmax_rows])# C
        arr1 = heapq.nlargest(3,range(len(values)),values.take) # D         
        try:
             value.append(int(relmax_rows[0][arr1[0]]))
        except IndexError:
             value.append(0)
        return value, relmax_rows

    print fftLen

    # The arrays to plot frequency and time
    full_freq = np.linspace(0,fs,fftLen)
    R = []
    LSFM_bufSize = 10   
    LSFM_buf=[0]*LSFM_bufSize
    threshold = -30

    # check past 10 frames for fundamental frequency to check the variation   in the measured fundamental
    # if the fundamental varies a lot, another way to tell that it's noise

    while(1):   
        data = stream.read(CHUNK)
        shorts = struct.unpack('h'*(CHUNK),data)
        input_wave = np.array(list(shorts),dtype = float)
    # update the FFT buffer and give space for a new entry  
        LSFM_buf.pop(0)
    # take FFT of newest frame and compute cepstrum 
        R = np.abs(dct(np.multiply(input_wave[0:fftLen], np.hamming(fftLen))))/np.sqrt(fftLen)  
        AM = np.mean(R)
        GM = gmean(R)
        LSFM_buf.append(100*np.log10(GM/float(AM)))
    # Compute spectral flatness coefficient
        max_LSFM = max(LSFM_buf)
        min_LSFM = min(LSFM_buf)

        max_val,relmax_val= find_peaks(R)
        funda_freq = full_freq[max_val]
        # print fundamental frequency
        print funda_freq ,' | ',  LSFM_buf[0]
        # decide whether noise, music or voice
        if max_LSFM<=threshold and funda_freq > 370:
            print 'music'
        elif max_LSFM>threshold:
            print 'noise'
        elif max_LSFM<=threshold and funda_freq <= 370:
            print 'voice'
        print ' '

    stream.stop_stream()
    stream.close()
    p.terminate() 
$\endgroup$
  • 2
    $\begingroup$ AFAIK, the peak frequency detected should be the same in both cases. $\endgroup$ – MBaz Aug 10 '16 at 14:53
2
$\begingroup$

Fundamentally @MBaz is right in the sense that measuring a sound source via a single or a stereo (or even multi-channels) pair of microphones would yield the same signal which could only differ because of the 3D positioning of the individual mics, which is not the case in your synthetic experimentation. So the frequencies should be the same according to the physical nature of the problem. So what remains is probably a software issue.

The simplest casue of such a problem would result from using the number of channels information in a wrong way (as a scale) during certain computations. So check to see them properly.

A more sophisticated error could be the following: which I only propose based on the fact that there is an exact doubling of frequency in your measurement according to your claim: so a possible cause of the doubling of frequency (or halving of the duration) that you experience on a pure tone could be based on stereo-mono packing of the sound samples in the sound buffer that's being transmitted between the trio of 1-your application, 2-windows API, and the 3-soundcard.

Now under Windows operating system, in a stereo channel configuration, the audio card fills in its audio buffers with packs of right and left samples for each sampling interval like this: buffer--->[R1|L1|R2|L2|R3|L3....Rn|Ln]. The mono recording simply puts every samples in order as: buffer--->[M1|M2|M3...|Mn]

Therefore in a stereo buffer the individual channel samples take twice bigger array indices than the mono channel for the same signal duration. It can be potential problem if not implemented properly, that the same sinus would seem to be a shorter duration (being exactly half of it would be which means a doubling in frequency !) signal than compared to its stereo buffer.

As this thing is a low-level audio issue, I cannot, unfortunetely, say whether your application code or your audio library, or your os api or any other piece of software may cause it. I dont think any standard piece of code would cause that however.

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.