python - 不写入 pyaudio 流时同步音频数据
问题描述
以下代码读取 mp3 广播流,并使用 pyqt5 显示
perradio.ui
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'peradio_graph.ui'
#
# Created by: PyQt5 UI code generator 5.15.4
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(800, 200)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Preferred)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(MainWindow.sizePolicy().hasHeightForWidth())
MainWindow.setSizePolicy(sizePolicy)
MainWindow.setMinimumSize(QtCore.QSize(0, 200))
MainWindow.setMaximumSize(QtCore.QSize(16777215, 200))
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.verticalLayout = QtWidgets.QVBoxLayout(self.centralwidget)
self.verticalLayout.setContentsMargins(0, 0, 0, 0)
self.verticalLayout.setSpacing(0)
self.verticalLayout.setObjectName("verticalLayout")
MainWindow.setCentralWidget(self.centralwidget)
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "Πειραϊκή εκκλησία"))
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
MainWindow = QtWidgets.QMainWindow()
ui = Ui_MainWindow()
ui.setupUi(MainWindow)
MainWindow.show()
sys.exit(app.exec_())
文件perradio.py
from peradio_ui import *
import threading
import urllib.request
import time
import miniaudio
from PyQt5 import QtCore, QtGui, QtWidgets
import matplotlib.pyplot as plt
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.dates import num2date
from matplotlib.animation import FuncAnimation
from matplotlib.ticker import FuncFormatter
import matplotlib.patheffects as pe
from pydub import AudioSegment
import numpy as np
import datetime
import pyaudio
import ctypes
import sys
#CHUNK = 4096
CHUNK = int(16384/4)
CHUNK_TIME = 93 #msec
FORMAT = pyaudio.paInt16
CHANNELS = 2
RATE = 44100
TIME_WINDOW = 3000 #2 sec
SILENT_VOLUME = 3
p = pyaudio.PyAudio()
class RadioThread(threading.Thread):
def __init__(self, peradio_code_instance):
threading.Thread.__init__(self)
self.peradio_code = peradio_code_instance
def run(self):
self.url = "https://impradio.bytemasters.gr/8002/stream"
#self.url = "http://shaincast.caster.fm:40636/listen.mp3?authn5d58c0f854bb653172d829db9545080d"
#run in threading
client = miniaudio.IceCastClient(self.url)
pcm_stream = MiniaudioDecoderPcmStream(client.audio_format,client)
#print(client.audio_format)
#self.audio_playback(pcm_stream)
self.now = datetime.datetime.now()
self.x_vals = []
self.y_vals = []
#self.y_vals_up = []
#self.y_vals_down = []
self.low_volumes_average = []
self.low_volume_text = None
self.output_device = p.open(format=pyaudio.paInt16,channels=2,rate=RATE,output=True)
try:
while True:
self.now = datetime.datetime.now()
audio = pcm_stream.read(CHUNK)
slice = AudioSegment(audio.tobytes(), sample_width=2, frame_rate=44100, channels=2)
print(len(slice))
audio_data = np.frombuffer(audio.tobytes(), np.int16)[::16] #down sampling
microphone_average_value = abs(np.average(audio_data))
#print(microphone_average_value)
if(len(self.low_volumes_average)<8):
self.low_volumes_average.append(microphone_average_value)
else:
self.low_volumes_average.append(microphone_average_value)
del self.low_volumes_average[0]
total = 0
silent = True
for average_number in self.low_volumes_average:
if(average_number>SILENT_VOLUME):
silent = False
break
time_data = np.array([])
#self.now_old = self.now
for i in range(0,len(audio_data)):
self.datetime_1 = self.now+datetime.timedelta(milliseconds=i*CHUNK_TIME/len(audio_data))
time_data = np.append(time_data, self.datetime_1)
#packet_time = time_data[len(time_data)-1]-self.now_old
#print(packet_time)
#self.now = self.now_old+datetime.timedelta(milliseconds=CHUNK_TIME)
self.x_vals = np.concatenate((self.x_vals, time_data))
self.y_vals = np.concatenate((self.y_vals, audio_data))
#self.y_vals_up = np.concatenate((self.y_vals_up, audio_data+1))
#self.y_vals_down = np.concatenate((self.y_vals_down, audio_data-1))
if(self.x_vals.size>audio_data.size*(TIME_WINDOW/CHUNK_TIME)):
self.x_vals = self.x_vals[audio_data.size:]
self.y_vals = self.y_vals[audio_data.size:]
#self.y_vals_up = self.y_vals_up[audio_data.size:]
#self.y_vals_down = self.y_vals_down[audio_data.size:]
if(self.low_volume_text==None):
pass
else:
self.low_volume_text.set_visible(False)
#self.peradio_code.chart.li_up.set_xdata(self.x_vals)
#self.peradio_code.chart.li_up.set_ydata(self.y_vals_up)
#self.peradio_code.chart.li_down.set_xdata(self.x_vals)
#self.peradio_code.chart.li_down.set_ydata(self.y_vals_down)
self.peradio_code.chart.li.set_xdata(self.x_vals)
self.peradio_code.chart.li.set_ydata(self.y_vals)
print(self.x_vals[0])
print(self.x_vals[len(self.x_vals)-1])
print(self.x_vals[len(self.x_vals)-1]-self.x_vals[0])
print("---------------------")
x_ticks = []
for i in range(0,TIME_WINDOW+1,1000):
tick = self.x_vals[0]+datetime.timedelta(milliseconds=i)
x_ticks.append(tick)
#print(x_ticks[-1])
#print(self.x_vals[0]+datetime.timedelta(milliseconds=3000))
#print("------------")
plt.xticks(x_ticks)
self.peradio_code.chart.ax.set_xlim(x_ticks[0],x_ticks[len(x_ticks)-1])
self.peradio_code.chart.ax.xaxis.set_major_formatter(FuncFormatter(self.peradio_code.date_formatter_1))
if(silent):
text_x_datetime = x_ticks[0]+datetime.timedelta(milliseconds=80)
self.low_volume_text = self.peradio_code.chart.ax.text(text_x_datetime, 14000, 'Χαμηλή στάθμη', style='italic',bbox={'facecolor': 'red', 'alpha': 0.5, 'pad': 5},fontsize=5)
self.peradio_code.chart.fig.canvas.draw()
self.peradio_code.chart.fig.canvas.flush_events()
self.output_device.write(audio.tobytes())
finally:
pass
def get_id(self):
# returns id of the respective thread
if hasattr(self, '_thread_id'):
return self._thread_id
for id, thread in threading._active.items():
if thread is self:
return id
def raise_exception(self):
thread_id = self.get_id()
res = ctypes.pythonapi.PyThreadState_SetAsyncExc(thread_id,
ctypes.py_object(SystemExit))
if res > 1:
ctypes.pythonapi.PyThreadState_SetAsyncExc(thread_id, 0)
#print('Exception raise failure')
class MiniaudioDecoderPcmStream(miniaudio.StreamableSource):
def __init__(self, fmt, stream):
self.pcm_stream = miniaudio.stream_any(stream, fmt, dither=miniaudio.DitherMode.TRIANGLE)
def read(self, size):
try:
return self.pcm_stream.send(size)
except StopIteration:
return b""
'''
def main():
global stop_peradio_thread
stop_peradio_thread = False
t1 = RadioThread()
t1.start()
while True:
try:
time.sleep(1)
except KeyboardInterrupt:
stop_peradio_thread = True
t1.join()
sys.exit()
'''
class PeradioCode:
global stop_peradio_thread
def __init__(self,ui,MainWindow):
self.ui = ui
self.MainWindow = MainWindow
self.chart = Canvas(self)
self.chart.ax.set_facecolor((1,1,1))
self.chart.ax.tick_params(labelcolor='white')
self.MainWindow.closeEvent = lambda event:self.closeEvent(event)
self.t1 = RadioThread(self)
self.t1.start()
def date_formatter_1(self,a, b):
t = num2date(a)
ms = str(t.microsecond)[:1]
res = f"{t.hour:02}:{t.minute:02}:{t.second:02}.{ms}"
#res = f"{t.hour:02}:{t.minute:02}:{t.second:02}"
return res
def closeEvent(self, event):
stop_peradio_thread = True
self.t1.raise_exception()
self.t1.join()
event.accept()
class Canvas(FigureCanvas):
def __init__(self,parent):
self.fig , self.ax = plt.subplots(figsize=(5,4),dpi=200)
self.fig.patch.set_facecolor((6/255,21/255,154/255))
self.ax.set_position([0., 0, 1., 0.8])
self.ax.xaxis.tick_top()
self.ax.tick_params(color=(1,1,1))
super().__init__(self.fig)
parent.ui.verticalLayout.addWidget(self)
self.now = datetime.datetime.now()
self.chart_stop = self.now+datetime.timedelta(milliseconds=3000)
plt.cla()
#date_format = mpl_dates.DateFormatter("%H:%M:%S.%f")
plt.gca().xaxis.set_major_formatter(FuncFormatter(parent.date_formatter_1))
plt.xticks(fontsize=5)
self.ax.grid(False)
#self.li_up, = self.ax.plot([], [],color=(0.42,0.36,0),linestyle='solid',marker=",")
#self.li_down, = self.ax.plot([], [],color=(0.42,0.36,0),linestyle='solid',marker=",")
self.li, = self.ax.plot([], [],color=(0,1,0.29),linestyle='solid',marker=",")
self.ax.set_ylim(-23000,23000)
x_ticks = []
for i in range(0,3000+1,1000):
tick = self.now+datetime.timedelta(milliseconds=i)
x_ticks.append(tick)
plt.xticks(x_ticks)
self.ax.set_xlim(x_ticks[0],x_ticks[-1])
self.show()
global stop_peradio_thread
global peradio_code
if __name__ == "__main__":
stop_peradio_thread = False
import sys
app = QtWidgets.QApplication(sys.argv)
app.setStyle("Fusion")
MainWindow = QtWidgets.QMainWindow()
ui = Ui_MainWindow()
ui.setupUi(MainWindow)
MainWindow.showMaximized()
peradio_code = PeradioCode(ui,MainWindow)
sys.exit(app.exec_())
输出:
尝试删除 peradio.py 的第 157 行(self.output_device.write(audio.tobytes())
听起来是音频数据),然后一些数字数据丢失:
我认为这是一个奇怪的错误。我该怎么做才能拥有 TIME_WINDOW=3sec 绘图数据?
我找到了解决方案,但我不知道为什么有效。
slice = slice.apply_gain(-200)
self.output_device.write(slice.raw_data)
在 RadioThread 类的函数运行的 while 循环结束时