首页 > 解决方案 > 不写入 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 循环结束时

标签: pythonstreammp3pyaudio

解决方案


推荐阅读