First commit

This commit is contained in:
Fierelier 2023-02-08 19:35:08 +01:00
parent e9cfe68120
commit 5a7a47a140
4 changed files with 191 additions and 2 deletions

View File

@ -1,6 +1,6 @@
MIT License
Copyright (c) <year> <copyright holders>
Copyright (c) 2023
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

View File

@ -1,3 +1,26 @@
# me.fier.aspew
Audio I/O for real-time applications, like VoIP.
Audio I/O for real time applications
## Tools
- `aspew-in.py` takes a microphone as an input, and outputs it to pipe.
- `aspew-out.py` takes input from a pipe, and outputs it to a speaker.
## Arguments
Arguments are pairs of key=value.
### in & out
- `device`: The device, a number (Default: Your default audio device.)
- `format`: The encoding of your audio, choices: [https://people.csail.mit.edu/hubert/pyaudio/docs/#pasampleformat](https://people.csail.mit.edu/hubert/pyaudio/docs/#pasampleformat) (Default: `paUInt8`)
- `channels`: How many channels the audio has (Default: `1`)
- `bitrate`: How high the refresh rate of the audio is in Hz (Default: `8000`)
- `buffersize`: The buffer-size, in seconds. Higher buffer-sizes reduce CPU load and risk of stutter, but raise delay (Default: `0.05`)
### out-only
- `store`: How much audio to store in the back-buffer at maximum before cutting it off, in seconds. Raise this if you get inconsistent playback (Default: 0.3s)
## Examples
`./aspew-in.py | ./aspew-out.py`
Listen to your default microphone at default settings.
`./aspew-in.py format=paInt16 bitrate=48000 channels=2 | ./aspew-out.py format=paInt16 bitrate=48000 channels=2`
Listen to your default microphone at 16-bit, 48000Hz, stereo.

55
aspew-in.py Executable file
View File

@ -0,0 +1,55 @@
#!/usr/bin/env python3
import sys
import os
import time
import pyaudio
unbufferedStdout = os.fdopen(sys.stdout.fileno(),"wb",0) # Make unbuffered stdout
def parseSettings():
rtn = {}
for arg in sys.argv[1:]:
arg = arg.split("=",1)
if len(arg) < 2: arg.append("")
arg[0] = arg[0].strip(" \t\r\n")
arg[1] = arg[1].strip(" \t\r\n")
rtn[arg[0]] = arg[1]
return rtn
def getSetting(lst,tp,keys,default = None):
for key in keys:
if key in lst:
return tp(lst[key])
return default
def streamHandler(in_data, frame_count, time_info, status):
unbufferedStdout.write(in_data)
return (in_data, pyaudio.paContinue)
settings = parseSettings()
audioFormat = getSetting(settings,str,["f","format"],"paUInt8")
audioChannels = getSetting(settings,int,["c","channel","channels"],1)
audioRate = getSetting(settings,int,["r","rate","bitrate"],8000)
audioBuffer = getSetting(settings,float,["b","buffer","buffersize"],0.05)
audioDevice = getSetting(settings,int,["d","device"],None)
audioFormat = getattr(pyaudio,audioFormat)
audioBuffer = round(audioRate * audioBuffer)
kwargs = {
"input": True,
"input_device_index": audioDevice,
"format": audioFormat,
"channels": audioChannels,
"frames_per_buffer": audioBuffer,
"rate": audioRate,
"stream_callback": streamHandler
}
audio = pyaudio.PyAudio()
stream = audio.open(**kwargs)
try:
while stream.is_active():
time.sleep(0.1)
except KeyboardInterrupt:
pass

111
aspew-out.py Executable file
View File

@ -0,0 +1,111 @@
#!/usr/bin/env python3
import sys
import os
import threading
import queue
import time
import math
import pyaudio
unbufferedStdout = os.fdopen(sys.stdout.fileno(),"wb",0) # Make unbuffered stdout
audioQueue = queue.Queue()
lastTime = 0
def legacyTimeMethod():
currentTime = time.clock()
if lastTime == 0:
lastTime = currentTime
return 0
timeSpent = currentTime - lastTime
lastTime = currentTime
return timeSpent
timeMethod = legacyTimeMethod
try:
timeMethod = time.perf_counter
except Exception:
pass
class inThread(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
def run(self):
try:
while True:
b = sys.stdin.buffer.read(audioFrameSize)
if b == b"": return
audioQueue.put(b)
except KeyboardInterrupt:
pass
def getAudioFrame():
global lastFrame
global audioStore
data = lastFrame
#print(audioQueue.qsize())
if audioQueue.qsize() > 0:
while audioQueue.qsize() > audioStore + 1:
data = audioQueue.get(False)
data = audioQueue.get(False)
lastFrame = data
return data
def streamHandler(in_data, frame_count, time_info, status):
data = b""
data += getAudioFrame()
return (data, pyaudio.paContinue)
def parseSettings():
rtn = {}
for arg in sys.argv[1:]:
arg = arg.split("=",1)
if len(arg) < 2: arg.append("")
arg[0] = arg[0].strip(" \t\r\n")
arg[1] = arg[1].strip(" \t\r\n")
rtn[arg[0]] = arg[1]
return rtn
def getSetting(lst,tp,keys,default = None):
for key in keys:
if key in lst:
return tp(lst[key])
return default
settings = parseSettings()
audioFormat = getSetting(settings,str,["f","format"],"paUInt8")
audioChannels = getSetting(settings,int,["c","channel","channels"],1)
audioRate = getSetting(settings,int,["r","rate","bitrate"],8000)
audioBuffer = getSetting(settings,float,["b","buffer","buffersize"],0.05)
audioStore = getSetting(settings,float,["s","store"],0.3)
letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
audioBitrate = audioFormat
audioFormat = getattr(pyaudio,audioFormat)
for letter in letters: audioBitrate = audioBitrate.replace(letter,"")
audioBitrate = int(audioBitrate)
audioBuffer = round(audioRate * audioBuffer)
audioFrameSize = int(float(audioChannels) * (audioBitrate / 8) * audioBuffer)
audioStorePerSecond = audioBuffer / audioRate
audioStore = math.ceil(audioStore / audioStorePerSecond)
lastFrame = bytearray(int(audioFrameSize))
kwargs = {
"output": True,
"format": audioFormat,
"channels": audioChannels,
"frames_per_buffer": audioBuffer,
"rate": audioRate,
"stream_callback": streamHandler
}
audio = pyaudio.PyAudio()
stream = audio.open(**kwargs)
inThread().start()
try:
while stream.is_active():
time.sleep(0.1)
except KeyboardInterrupt:
pass