Reputation: 11
I need to change sound volume in real time with Python 3.6, using PyBinSim and Anaconda, but not mandatory (it can be just Python 3.6 and any other library good for this). The situation is the following: With a laptop and a microphone we're recording sounds and playing them back immediately, but we need to change the sound volume between recording and playback. I tried some code samples but I can't make it work without errors. Any ideas or suggestions? Thanks in advance!
Code examples
import sounddevice as sd
import time
import numpy as np
from scipy import signal
duration = 10 #seconds
def callback(indata, outdata, frames, time):
outdata[:] = indata
def print_sound(indata, outdata, frames, time, status):
volum_norm = np.linalg.norm(indata) * 10
print("|") * int(volum_norm)
with sd.Stream(callback=print_sound):
sd.sleep(duration * 1000)
import sounddevice as sd
import time
import numpy as np
from scipy import signal
from ctypes import cast, POINTER
from comtypes import CLSCTX_ALL
from pycaw.pycaw import AudioUtilities, IAudioEndpointVolume
duration = 1000 #seconds
def callback(indata, outdata, frames, time, status):
if status:
print(status)
outdata[:]=indata
devices = AudioUtilities.GetSpeakers()
interface = devices.Activate(
IAudioEndpointVolume._iid_, CLSCTX_ALL, None)
volume = cast(interface, POINTER(IAudioEndpointVolume))
# Control volume
#volume.SetMasterVolumeLevel(-0.0, None) #max
#volume.SetMasterVolumeLevel(-5.0, None) #72%
volume.SetMasterVolumeLevel(-10.0, None) #72%
volume.SetMasterVolumeLevel(-10.0, None) #51%
with sd.Stream(channels=2, callback=callback):
sd.sleep(duration*1000)
The second example returns no errors but when tested on a Win10 laptop there is no sound to be heard either.
Upvotes: 1
Views: 4960
Reputation: 11443
pyaudio has a method for blocking callback. Which means that you can record from microphone in chunks and then play them back. This method is suitable for manipulation of audio chunks to one's needs.
However, instead of converting chunks to numpy arrays , manipulating and converting back to chunks, which can be pretty messy, audioop provides simpler methods to get power of each chunk and then increase/decrease per one's needs.
Below code is a modification of pyaudio callback code.
As per audioop documentation (below) we can use audioop.rms
to get RMS value of fragment(in our case CHUNK). While this is not needed for changing audio volume, it can be used as debug to see if indeed changes are actually taking place on given audio chunk.
audioop.rms(fragment, width):
''' Return the root-mean-square of the fragment, i.e. sqrt(sum(S_i^2)/n).
This is a measure of the power in an audio signal.'''
Next, we multiply all values within a chunk by some factor.
e.g. multiplying by a factor of (2) will roughly double the RMS value. and hence increase volume level accordingly.
audioop.mul(fragment, width, factor):
'''Return a fragment that has all samples in the original fragment
multiplied by the floating-point value factor.
Samples are truncated in case of overflow.'''
Armed with these two method, we simply use code from pyaudio
site and modify volume of each chunk as per multiplication factor.
I've also added code to collect old and new RMS values in a dict of list rmsdict
for each chunk. Simply printing them on screen was causing clicking sound due to break in processing.
The audio from microphone was recorded for (3) seconds. You can try different lengths.
Working Code
import pyaudio
import audioop
from collections import OrderedDict
class DefaultListOrderedDict(OrderedDict):
'''
Ordered Dict of list to hold old and new RMS values
'''
def __missing__(self,k):
self[k] = []
return self[k]
FORMAT = pyaudio.paInt16
CHUNK = 2048
WIDTH = 2
CHANNELS = 1
RATE = 44100
RECORD_SECONDS = 3
FACTOR = 2
rmsdict = DefaultListOrderedDict()
p = pyaudio.PyAudio()
stream = p.open(format=FORMAT,
channels=CHANNELS,
rate=RATE,
input=True,
output=True,
frames_per_buffer=CHUNK)
print("* recording from microphone")
for i in range(0, int(RATE / CHUNK * RECORD_SECONDS)):
data = stream.read(CHUNK)
rmsdict["chunk{}".format(i)].append(audioop.rms(data, 2))
new_data = audioop.mul(data, WIDTH, FACTOR)
rmsdict["chunk{}".format(i)].append(audioop.rms(new_data, 2))
stream.write(new_data, CHUNK)
print("* done recording")
stream.stop_stream()
stream.close()
p.terminate()
Below is data from rmsdict
. I've truncated it for visibility.
Python 2.7.9 (default, Dec 10 2014, 12:24:55) [MSC v.1500 32 bit (Intel)] on win32 Type "copyright", "credits" or "license()" for more information.
================================ RESTART ================================
- recording from microphone
- done recording
rmsdict DefaultListOrderedDict([('chunk0', [71, 143]), ('chunk1', [77, 155]), ('chunk2', [45, 91]), ('chunk3', [29, 59]), ('chunk4', [33, 66]), ('chunk5', [35, 71]), ('chunk6', [27, 54]), ('chunk7', [17, 34]), ('chunk8', [18, 36]), ('chunk9', [142, 285]), ('chunk10', [1628, 3257]), ('chunk11', [2666, 5333]), ('chunk12', [2174, 4348]),
..
..
('chunk51', [1723, 3446]), ('chunk52', [1524, 3049]), ('chunk53', [1329, 2659]), ('chunk54', [1166, 2333]), ('chunk55', [573, 1147]), ('chunk56', [1777, 3555]), ('chunk57', [1588, 3176]), ('chunk58', [698, 1397]), ('chunk59', [383, 767]), ('chunk60', [152, 305]), ('chunk61', [799, 1599]), ('chunk62', [2158, 4316])])
Upvotes: 2