How can I plot on matplotlib input signal from microphone?
I have tried to plot with plt.plot(frames) but frames is for some reason a string?
a) Why is frames variable a string list?
b) Why is data variable string list?
c) Should they represent energy/amplitude of single sample and be integers?
d) Why is length of data 2048 when I specified I want chunk size of 1024?
(I guess because i use paInt16, but cannot see still why it couldn't be 1024)
I have the following code for microphone input:
import pyaudio
import audioop
import matplotlib.pyplot as plt
import numpy as np
from itertools import izip
import wave
FORMAT = pyaudio.paInt16 # We use 16bit format per sample
CHANNELS = 1
RATE = 44100
CHUNK = 1024 # 1024bytes of data red from a buffer
RECORD_SECONDS = 3
WAVE_OUTPUT_FILENAME = "file.wav"
audio = pyaudio.PyAudio()
# start Recording
stream = audio.open(format=FORMAT,
channels=CHANNELS,
rate=RATE, input=True,
frames_per_buffer=CHUNK)
frames = []
for i in range(0, int(RATE / CHUNK * RECORD_SECONDS)):
data = stream.read(CHUNK)
frames.append(data)
frames = ''.join(frames)
stream.stop_stream()
stream.close()
audio.terminate()
a) Why is frames variable a string list?
As a consequence of b), that's how you build it in your code.
b) Why is data variable string list?
It is a byte string, that is just a raw sequence of bytes. That's what read() returns.
c) Should they represent energy/amplitude of single sample and be integers?
They are. They're just packed in a byte sequence and not in Python integers.
d) Why is length of data 2048 when I specified I want chunk size of 1024?
1024 is the number of frames. Each frame is 2 bytes long, so you get 2048 bytes.
How can I plot on matplotlib input signal from microphone? I have tried to plot with plt.plot(frames) but frames is for some reason a string?
Depends on what you want to plot. Just raw amplitude can be obtained by transforming the byte string to a numpy array:
fig = plt.figure()
s = fig.add_subplot(111)
amplitude = numpy.fromstring(frames, numpy.int16)
s.plot(amplitude)
fig.savefig('t.png')
A more useful plot would be a spectrogram:
fig = plt.figure()
s = fig.add_subplot(111)
amplitude = numpy.fromstring(frames, numpy.int16)
s.specgram(amplitude)
fig.savefig('t.png')
But you can tinker with amplitude however you want, now that you have a numpy array.
Related
I'm recording live audio in 5 second clips with Python and want to cut out all sound below a certain frequency e.g. 10kHz. This is my script so far:
import pyaudio, wave, time, sys, os
from array import array
from scipy import signal
FORMAT=pyaudio.paInt16
CHANNELS=1
CHUNK=1024
RATE=44100
RECORD_SECONDS=5
def butter_highpass(cutoff, fs, order=5):
nyq = 0.5 * fs
normal_cutoff = cutoff / nyq
b, a = signal.butter(order, normal_cutoff, btype='high', analog=False)
return b, a
def butter_highpass_filter(data, cutoff, fs, order=5):
b, a = butter_highpass(cutoff, fs, order=order)
y = signal.filtfilt(b, a, data)
return y
audio=pyaudio.PyAudio()
stream=audio.open(format=FORMAT,channels=CHANNELS,
rate=RATE,
input=True,
frames_per_buffer=CHUNK)
while True:
# read data
data=stream.read(CHUNK)
data_chunk=array('h',data)
data_chunk = butter_highpass_filter(data_chunk,10000,RATE)
frames=[]
for i in range(0, int(RATE / CHUNK * RECORD_SECONDS)):
data = stream.read(CHUNK)
frames.append(data)
# write to file
words = ["RECORDING-", time.strftime("%Y%m%d-%H%M%S"), ".wav"]
FILE_NAME= "".join(words)
wavfile=wave.open(FILE_NAME,'wb')
wavfile.setnchannels(CHANNELS)
wavfile.setsampwidth(audio.get_sample_size(FORMAT))
wavfile.setframerate(RATE)
wavfile.writeframes(b''.join(frames))
wavfile.close()
But this doesn't seem to work. I want to cut out all (or as much as possible of the) sound below the specified frequency. Why doesn't the filter I'm using seem to cut out the sound below 10kHz? How can I make it work?
Brief
The goal is to apply a brick-wall 10 kHz high-pass filter to audio, then save it. Audio is recorded continuously and saved in 5 second snippets to separate .wav files.
What we have so far
At the moment the current script:
declares a function to apply a butterworth high-pass filter (butter_high-pass_filter) whose output is an array of floating point values
butter_high-pass_filter uses signal.filtfilt
the input to the function is in short format (bug 1)
data_chunk=array('h',data)
data_chunk = butter_high-pass_filter(data_chunk,10000,RATE)
data_chunk is never used, so a high passed frame of audio is never saved to file (bug 2)
data is read for 5 seconds worth audio
frames=[]
for i in range(0, int(RATE / CHUNK * RECORD_SECONDS)):
data = stream.read(CHUNK)
frames.append(data)
stream.read is blocking, so this will wait until the correct amount of audio has been read.
collected data is then written to a wav file in the same format
words = ["RECORDING-", time.strftime("%Y%m%d-%H%M%S"), ".wav"]
FILE_NAME= "".join(words)
wavfile=wave.open(FILE_NAME,'wb')
wavfile.setnchannels(CHANNELS)
wavfile.setsampwidth(audio.get_sample_size(FORMAT))
wavfile.setframerate(RATE)
wavfile.writeframes(b''.join(frames))
wavfile.close()
Solution
The problem here is that the solutions is multifaceted and requires multiple parts that are currently missing from the current script.
Also, to avoid further complications a slightly different approach needs to be taken than the one originally intended. Rather than applying a filter in real-time a filter can simply be applied to the wav sample data before it is saved.
This
Removes the need for dealing with filter state continuity
limits the need for casting back and forth between data types
Also, the outer forever while loop has been removed. Timing and memory start to become an issue. The program can simply be re-run over and over until there is more clarity on the use case covering
Why must the audio be high pass filtered?
Why can't the filtering take place after all data is recorded (i.e. applied to wav files?
Why does data have to be saved?
Are there limitations on data format? bit-depth? sampling rate?
Until those are answered there are too many possible routes each with limitation that can realistically be covered in a single answer.
Breakdown
Full breakdown for the process will be
Declare a function that takes floating point sample data as input and high pass filters with floating point data as ouput
concatenate 5 seconds of byte-string data from pyaudio into a single variable
unpack data as a 16-bit (signed short) format array of samples
scale samples to floating point format between 1.0 and -1.0
send data to high pass filter
scale filter samples in the range of 16-bit (signed short) format
pack 16-bit filtered samples into a byte string
write filtered byte-string data to a wav file.
Script
import pyaudio, wave, time, sys, os, struct
from array import array
from scipy import signal
# 1. Declare a function that takes floating point sample data as input and high pass
# filters with floating point data as output
# 2. concatenate 5 seconds of byte-string data from PyAudio into a single variable
# 3. unpack data as a 16-bit (signed short) format array of samples
# 4. scale samples to floating point format between 1.0 and -1.0
# 5. send data to high pass filter
# 6. scale filter samples in the range of 16-bit (signed short) format
# 7. pack 16-bit filtered samples into a byte string
# 8. write filtered byte-string data to a wav file.
# ---------------------------------------------------------------------
# 1. Declare High Pass Filter
def butter_highpass(cutoff: float, fs: float, order: int = 5) -> tuple:
"""
Generate FIR and IIR coefficients for a butterworth highpass filter
:param cutoff: cutoff frequency (hz)
:param fs: sampling rate
:param order: filter order
:return: tuple of filter coefficients
"""
nyq = 0.5 * fs
normal_cutoff = cutoff / nyq
b, a = signal.butter(order, normal_cutoff, btype='high', analog=False)
return b, a
def butter_highpass_filter(data: [float], cutoff: float, fs: float, order: int = 5) -> [float]:
"""
apply a butterworth high pass filter to sample data in floating point format
:param data: float sample data array
:param cutoff: filter cutoff (hz)
:param fs: sample data sampling rate
:param order: filter order
:return: floating point array of filtered sample data
"""
b, a = butter_highpass(cutoff, fs, order=order)
y = signal.filtfilt(b, a, data)
return y
# ---------------------------------------------------------------------
# Init Global Variables
FORMAT = pyaudio.paInt16
CHANNELS = 1
CHUNK = 1024
RATE = 44100
RECORD_SECONDS = 5
audio = pyaudio.PyAudio()
stream = audio.open(format=FORMAT,
channels=CHANNELS,
rate=RATE,
input=True,
frames_per_buffer=CHUNK)
# ---------------------------------------------------------------------
# Main Program
if __name__ == '__main__':
# ---------------------------------------------------------------------
# 2. concat 5 seconds of data into a single string
frames = b''
for i in range(0, int(RATE / CHUNK * RECORD_SECONDS)):
frames += stream.read(CHUNK)
# ---------------------------------------------------------------------
# 3. Unpack data as a 16 - bit
sample_data = array('h', frames)
# ---------------------------------------------------------------------
# 4. scale samples to floating point format between 1.0 and -1.0
samples = [sample_datum / (2**15) for sample_datum in sample_data]
print("Max Amplitude:", max(samples))
# ---------------------------------------------------------------------
# 5. send data to high pass filter
filtered_sample_data = butter_highpass_filter(samples, 10000.0, RATE)
# filtered_sample_data = samples
# ---------------------------------------------------------------------
# 6. scale filter samples in the range of 16-bit (signed short) format
# (2 ** 14) for headroom (very lazy)
sample_data_16_bit = [int(sample * (2 ** 14)) for sample in filtered_sample_data]
# # ---------------------------------------------------------------------
# # 7. pack 16-bit filtered samples into a byte string
raw_data = [struct.pack('h', sample) for sample in sample_data_16_bit]
# # ---------------------------------------------------------------------
# # 8. Write Wav
file_name = "".join(["RECORDING-", time.strftime("%Y%m%d-%H%M%S"), ".wav"])
wavfile = wave.open(file_name, 'wb')
wavfile.setnchannels(CHANNELS)
wavfile.setsampwidth(audio.get_sample_size(FORMAT))
wavfile.setframerate(RATE)
wavfile.writeframes(b''.join(raw_data))
wavfile.close()
Comments
Potential additions could include plotting the spectrum with matplotlib, normalising the filtered audio, encapsulating the process into custom functions, but these have been left as an exercise for the OP.
Below is included a screen shot of the audio spectrum after normalisation. As has been referenced in comments by #marco, the filter does have a slope, which is expected. This can be improved by increasing the order of the filter.
There really doesn't seem to be any function to get these values
import vlc
p = vlc.MediaPlayer(path/to/file.mp3)
p.play()
here I need to get the frequency, pitch and other properties of sound. I am not sure if this is possible using vlc-python, any other solution will also be of help
I think you probably need an additional library to achieve what you want.
Here's a video tutorial on how to build an audio spectrum analyzer with Python:
https://youtu.be/AShHJdSIxkY
This was final --python2-- code from a commenter on that video (thanks user Hey Jose!)
#! /usr/bin/python
import pyaudio
import struct
import numpy as np
import matplotlib.pyplot as plt
import time
#%matplotlib tk
CHUNK = 4000
FORMAT = pyaudio.paInt16
CHANNELS = 1
RATE = 16000
p = pyaudio.PyAudio()
chosen_device_index = -1
for x in xrange(0,p.get_device_count()):
info = p.get_device_info_by_index(x)
#print p.get_device_info_by_index(x)
if info["name"] == "pulse":
chosen_device_index = info["index"]
print "Chosen index: ", chosen_device_index
stream = p.open(format=FORMAT,
channels=CHANNELS,
rate=RATE,
input_device_index=chosen_device_index,
input=True,
output=True,
frames_per_buffer=CHUNK
)
plt.ion()
fig, ax = plt.subplots()
x = np.arange(0, CHUNK)
data = stream.read(CHUNK)
data_int16 = struct.unpack(str(CHUNK) + 'h', data)
line, = ax.plot(x, data_int16)
#ax.set_xlim([xmin,xmax])
ax.set_ylim([-2**15,(2**15)-1])
while True:
data = struct.unpack(str(CHUNK) + 'h', stream.read(CHUNK))
line.set_ydata(data)
fig.canvas.draw()
fig.canvas.flush_events()
This tutorial uses PyAudio (https://people.csail.mit.edu/hubert/pyaudio/docs/)
I haven't done the tutorial yet but reading the sample code they seem to take the approach of reading the audio stream in chunks and mapping the bytes to the y-axis of a plot.
If this doesn't fit your application, you might check out
extracting pitch features from audio file
The answer to that question suggests this library: http://digitalmusics.dartmouth.edu/~mcasey/bregman/
Use the AudioEqualizer for the frequency info https://www.olivieraubert.net/vlc/python-ctypes/doc/vlc.AudioEqualizer-class.html
The Audio track will have the channels and rate information https://www.videolan.org/developers/vlc-branch/doc/doxygen/html/structlibvlc__audio__track__t.html
And the libvlc_media_stats_t struct has more info as well https://www.videolan.org/developers/vlc-branch/doc/doxygen/html/structlibvlc__media__stats__t.html like
int i_read_bytes
float f_input_bitrate
int i_demux_read_bytes
float f_demux_bitrate
int i_demux_corrupted
int i_demux_discontinuity
int i_decoded_video
int i_decoded_audio
int i_displayed_pictures
int i_lost_pictures
int i_played_abuffers
int i_lost_abuffers
int i_sent_packets
int i_sent_bytes
float f_send_bitrate
I am streaming audio from a mic to my speaker. But I want to increase the volume of the sound live but I can;t figure out a way and I've searched google for a while.
Her Is my code
import pyaudio
Chunk = 1024
AudioFormat = pyaudio.paInt16
Channels = 2
Rate = 44100
PortAudio = pyaudio.PyAudio()
sourceDevice = PortAudio.open(format=AudioFormat,
channels=Channels,
rate=Rate,
input=True,
input_device_index=2,
frames_per_buffer=Chunk
)
destinationDevice = PortAudio.open(format=AudioFormat,
channels=Channels,
rate=Rate,
output=True,
output_device_index=4,
frames_per_buffer=Chunk
)
while True:
try:
data = sourceDevice.read(Chunk)
except OSError:
data = '\x00' * Chunk
except IOError as ex:
if ex[1] != pyaudio.paInputOverflowed:
raise
data = '\x00' * Chunk
# Doing Something To Data Here To Incrase Volume Of It
data = data # Function Here??
destinationDevice.write(data, Chunk, exception_on_underflow=True)
an example of what the data variable is is
(This is Shortened Quite by a lot the original is MASSIVE)
b'\xec\x00G\x01\xa7\x01\xbe\x01\x95\x00\xf7\x00+\x00\x91\x00\xa1\x01W\x01\xec\x01\x94\x01n\x00\xac\x00I\x00\xa4\x00\xfb\x00"\x01g\x00\x8d\x00*\x00m\x00\xde\x00\x04\x01\xb2\x00\xc7\x005\x00-\x00(\x01\xb0\x00\xec\x01Q\x01.'
You can use numpy to convert the raw data into numpy arrays, then multiply the array by a volume ratio and write it to the output stream.
from math import sqrt
import numpy as np
# ...
# convert the linear volume to a logarithmic scale (see explanation below)
volumeFactor = 2
multiplier = pow(2, (sqrt(sqrt(sqrt(volumeFactor))) * 192 - 192)/6)
while True:
try:
data = sourceDevice.read(Chunk)
except OSError:
data = '\x00' * Chunk
except IOError as ex:
if ex[1] != pyaudio.paInputOverflowed:
raise
data = '\x00' * Chunk
# Doing Something To Data Here To Incrase Volume Of It
numpy_data = np.fromstring(data, dtype=np.int16)
# double the volume using the factor computed above
np.multiply(numpyData, volumeMultiplier,
out=numpyData, casting="unsafe")
destinationDevice.write(numpy_data.tostring(), Chunk, exception_on_underflow=True)
The concept is that audio data is conceptually an array of samples, each one with a value that depends on the bit "depth". Standard digital audio (as CD audio) is at 44100kHz, 16bit, stereo, which means that each seconds has 88200 samples (since it's stereo) with each sample occupying 2 bytes (8bit + 8bit). If you equally change the value of each of those samples, you will actually change its volume.
Now, the problem is that perceived volume is not linear, but logarithmic. So, if you want to get twice the volume, you can't just double sample values.
I'm using a conversion I found out some years ago (from Ardour sliders, if I recall correctly), which should be accurate enough.
Be careful, though, you could easily get very high levels, which will result in distorted sound.
I want to make a real time fft analysis of my audio file, when converting from binary of the wav file to int with struct.unpack I get the error:
data_int =struct.unpack(format, data)
struct.error: unpack requires a buffer of 4096 bytes
Every time I change the audio format or rate to match the 4096 buffersize goes up 2x or something similar.
What is weird is that in the while loop, I see no error only at the last iteration of the while loop, and then I can't get out of it.
import numpy as np
import pyaudio
import matplotlib.pyplot as plt
import wave
import argparse
from struct import *
import struct
#Get file
filename = 'snare16.wav'
# Set chunk size of 1024 samples per data frame
chunk = 1024
# Open the sound file
wf = wave.open(filename, 'rb')
# Create an interface to PortAudio
p = pyaudio.PyAudio()
# Open a .Stream object to write the WAV file to
stream = p.open(format = p.get_format_from_width(wf.getsampwidth()),
channels = wf.getnchannels(),
rate = wf.getframerate(),
output = True)
# Read data in chunks
data = wf.readframes(chunk)
#Convert binary numbers into int (Bit-Depth)
count = len(data)/2
format = "%dh"%(count) #results in '2048h' as format: 2048 short
# Play the sound by writing the audio data to the stream
while data != '':
#play audio
stream.write(data)
#Read the chunk each rate
data = wf.readframes(chunk)
#Convert data into np array, i.e integers
data_int =struct.unpack(format, data)
My main task is to recognize a human humming from a microphone in real time. As the first step to recognizing signals in general, I have made a 5 seconds recording of a 440 Hz signal generated from an app on my phone and tried to detect the same frequency.
I used Audacity to plot and verify the spectrum from the same 440Hz wav file and I got this, which shows that 440Hz is indeed the dominant frequency :
(https://i.imgur.com/2UImEkR.png)
To do this with python, I use the PyAudio library and refer this blog. The code I have so far which I run with the wav file is this :
"""PyAudio Example: Play a WAVE file."""
import pyaudio
import wave
import sys
import struct
import numpy as np
import matplotlib.pyplot as plt
CHUNK = 1024
if len(sys.argv) < 2:
print("Plays a wave file.\n\nUsage: %s filename.wav" % sys.argv[0])
sys.exit(-1)
wf = wave.open(sys.argv[1], 'rb')
p = pyaudio.PyAudio()
stream = p.open(format=p.get_format_from_width(wf.getsampwidth()),
channels=wf.getnchannels(),
rate=wf.getframerate(),
output=True)
data = wf.readframes(CHUNK)
i = 0
while data != '':
i += 1
data_unpacked = struct.unpack('{n}h'.format(n= len(data)/2 ), data)
data_np = np.array(data_unpacked)
data_fft = np.fft.fft(data_np)
data_freq = np.abs(data_fft)/len(data_fft) # Dividing by length to normalize the amplitude as per https://www.mathworks.com/matlabcentral/answers/162846-amplitude-of-signal-after-fft-operation
print("Chunk: {} max_freq: {}".format(i,np.argmax(data_freq)))
fig = plt.figure()
ax = fig.add_subplot(1,1,1)
ax.plot(data_freq)
ax.set_xscale('log')
plt.show()
stream.write(data)
data = wf.readframes(CHUNK)
stream.stop_stream()
stream.close()
p.terminate()
In the output, I get that the max frequency is 10 for all the chunks and an example of one of the plots is :
(https://i.imgur.com/zsAXME5.png)
I had expected this value to be 440 instead of 10 for all the chunks. I admit I know very little about the theory of FFTs and I appreciate any help in letting my solve this.
EDIT:
The sampling rate is 44100. no. of channels is 2 and sample width is also 2.
Forewords
As xdurch0 pointed out, you are reading a kind of index instead of a frequency. If you are about to make all computation by yourself you need to compute you own frequency vector before plotting if you want to get consistent result. Reading this answer may help you towards the solution.
The frequency vector for FFT (half plane) is:
f = np.linspace(0, rate/2, N_fft/2)
Or (full plane):
f = np.linspace(-rate/2, rate/2, N_fft)
On the other hand we can delegate most of the work to the excellent scipy.signal toolbox which aims to cope with this kind of problems (and many more).
MCVE
Using scipy package it is straight forward to get the desired result for a simple WAV file with a single frequency (source):
import numpy as np
from scipy import signal
from scipy.io import wavfile
import matplotlib.pyplot as plt
# Read the file (rate and data):
rate, data = wavfile.read('tone.wav') # See source
# Compute PSD:
f, P = signal.periodogram(data, rate) # Frequencies and PSD
# Display PSD:
fig, axe = plt.subplots()
axe.semilogy(f, P)
axe.set_xlim([0,500])
axe.set_ylim([1e-8, 1e10])
axe.set_xlabel(r'Frequency, $\nu$ $[\mathrm{Hz}]$')
axe.set_ylabel(r'PSD, $P$ $[\mathrm{AU^2Hz}^{-1}]$')
axe.set_title('Periodogram')
axe.grid(which='both')
Basically:
Read the wav file and get the sample rate (here 44.1kHz);
Compute the Power Spectrum Density and frequencies;
Then display it with matplotlib.
This outputs:
Find Peak
Then we can find the frequency of the first highest peak (P>1e-2, this criterion is subject to tuning) using find_peaks:
idx = signal.find_peaks(P, height=1e-2)[0][0]
f[idx] # 440.0 Hz
Putting all together it merely boils down to:
def freq(filename, setup={'height': 1e-2}):
rate, data = wavfile.read(filename)
f, P = signal.periodogram(data, rate)
return f[signal.find_peaks(P, **setup)[0][0]]
Handling multiple channels
I tried this code with my wav file, and got the error for the line
axe.semilogy(f, Pxx_den) as follows : ValueError: x and y must have
same first dimension. I checked the shapes and f has (2,) while
Pxx_den has (220160,2). Also, the Pxx_den array seems to have all
zeros only.
Wav file can hold multiple channels, mainly there are mono or stereo files (max. 2**16 - 1 channels). The problem you underlined occurs because of multiple channels file (stereo sample).
rate, data = wavfile.read('aaaah.wav') # Shape: (46447, 2), Rate: 48 kHz
It is not well documented, but the method signal.periodogram also performs on matrix and its input is not directly consistent with wavfile.read output (they perform on different axis by default). So we need to carefully orient dimensions (using axis switch) when performing PSD:
f, P = signal.periodogram(data, rate, axis=0, detrend='linear')
It also works with Transposition data.T but then we need to back transpose the result.
Specifying the axis solve the issue: frequency vector is correct and PSD is not null everywhere (before it performed on the axis=1 which is of length 2, in your case it performed 220160 PSD on 2-samples signals we wanted the converse).
The detrend switch ensure the signal has zero mean and its linear trend is removed.
Real application
This approach should work for real chunked samples, provided chunks hold enough data (see Nyquist-Shannon sampling theorem). Then data are sub-samples of the signal (chunks) and rate is kept constant since it does not change during the process.
Having chunks of size 2**10 seems to work, we can identify specific frequencies from them:
f, P = signal.periodogram(data[:2**10,:], rate, axis=0, detrend='linear') # Shapes: (513,) (513, 2)
idx0 = signal.find_peaks(P[:,0], threshold=0.01, distance=50)[0] # Peaks: [46.875, 2625., 13312.5, 16921.875] Hz
fig, axe = plt.subplots(2, 1, sharex=True, sharey=True)
axe[0].loglog(f, P[:,0])
axe[0].loglog(f[idx0], P[idx0,0], '.')
# [...]
At this point, the trickiest part is the fine tuning of find-peaks method to catch desired frequencies. You may need to consider to pre-filter your signal or post-process the PSD in order to make the identification easier.