First commit
This commit is contained in:
parent
e9cfe68120
commit
5a7a47a140
2
LICENSE
2
LICENSE
|
@ -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:
|
||||
|
||||
|
|
25
README.md
25
README.md
|
@ -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.
|
|
@ -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
|
|
@ -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
|
Loading…
Reference in New Issue