python中的多線程編程與暫停、播放音頻的結合


先給兩個原文鏈接:

https://blog.csdn.net/u013755307/article/details/19913655

https://www.cnblogs.com/scolia/p/6132950.html

 

播放wav音頻的原代碼:

#引入庫
import pyaudio
import wave
import sys
 
#定義數據流塊
chunk = 1024
 
#只讀方式打開wav文件
f = wave.open(r"../resource/3.wav","rb")
 
p = pyaudio.PyAudio()
 
#打開數據流
stream = p.open(format = p.get_format_from_width(f.getsampwidth()),
                channels = f.getnchannels(),
                rate = f.getframerate(),
                output = True)
 
#讀取數據
data = f.readframes(chunk)
 
#播放
while data !="":
    stream.write(data)
    data = f.readframes(chunk)
 
#停止數據流
stream.stop_stream()
stream.close()
 
#關閉 PyAudio
p.terminate()

對其進行封裝之后的代碼:

#-*-coding:utf-8-*-

#引入庫
import pyaudio
import wave
import sys

# 定義數據流塊
CHUNK = 1024

# if len(sys.argv) < 2:
#     print("Plays a wave file.\n\nUsage: %s filename.wav" % sys.argv[0])
#     sys.exit(-1)
class playWavAudio():
    def __init__(self):
        #只讀方式打開wav文件
        self.wf = wave.open(r'../resource/3.wav', 'rb')   #(sys.argv[1], 'rb')
    def play(self):

        p = pyaudio.PyAudio()   #創建一個播放器

        # 打開數據流
        stream = p.open(format=p.get_format_from_width(self.wf.getsampwidth()),
                        channels=self.wf.getnchannels(),
                        rate=self.wf.getframerate(),
                        output=True)

        # 讀取數據
        data = self.wf.readframes(CHUNK)

        # 播放
        while data != '':
            stream.write(data)
            data = self.wf.readframes(CHUNK)

        # 停止數據流
        stream.stop_stream()
        stream.close()

        # 關閉 PyAudio
        p.terminate()

if __name__ == "__main__":
    aaa = playWavAudio()
    aaa.play()

 

 

多線程(暫停,恢復,停止)的代碼:

import threading
import time


class Job(threading.Thread):

    def __init__(self, *args, **kwargs):
        super(Job, self).__init__(*args, **kwargs)
        self.__flag = threading.Event()  # 用於暫停線程的標識
        self.__flag.set()  # 設置為True
        self.__running = threading.Event()  # 用於停止線程的標識
        self.__running.set()  # 將running設置為True

    def run(self):
        while self.__running.isSet():
            self.__flag.wait()  # 為True時立即返回, 為False時阻塞直到內部的標識位為True后返回
            print(time.time())
            time.sleep(1)
            # print("sleep 1s")
    def pause(self):
        self.__flag.clear()  # 設置為False, 讓線程阻塞
        print("pause")
    def resume(self):
        self.__flag.set()  # 設置為True, 讓線程停止阻塞
        print("resume")
    def stop(self):
        # self.__flag.set()  # 將線程從暫停狀態恢復, 如果已經暫停的話(要是停止的話我就直接讓他停止了,干嘛還要執行這一句語句啊,把這句注釋了之后就沒有滯后現象了。)
        self.__running.clear()  # 設置為False
        print("停止!")
if __name__ == "__main__":
    a = Job()
    a.start()
    time.sleep(3)
    a.pause()
    time.sleep(6)
    a.resume()
    time.sleep(3)
    a.pause()
    time.sleep(2)
    a.stop()

原文解釋:

我們都知道python中可以是threading模塊實現多線程, 但是模塊並沒有提供暫停, 恢復和停止線程的方法, 一旦線程對象調用start方法后, 只能等到對應的方法函數運行完畢. 也就是說一旦start后, 線程就屬於失控狀態. 不過, 我們可以自己實現這些. 一般的方法就是循環地判斷一個標志位, 一旦標志位到達到預定的值, 就退出循環. 這樣就能做到退出線程了. 但暫停和恢復線程就有點難了, 我一直也不清除有什么好的方法, 直到我看到threading中Event對象的wait方法的描述時.

復制代碼
wait([timeout])

    Block until the internal flag is true. If the internal flag is true on entry, return immediately. Otherwise, block until another thread calls set() to set the flag to true, or until the optional timeout occurs.

    阻塞, 直到內部的標志位為True時. 如果在內部的標志位在進入時為True時, 立即返回. 否則, 阻塞直到其他線程調用set()方法將標准位設為True, 或者到達了可選的timeout時間.


    When the timeout argument is present and not None, it should be a floating point number specifying a timeout for the operation in seconds (or fractions thereof).

    This method returns the internal flag on exit, so it will always return True except if a timeout is given and the operation times out.

    當給定了timeout參數且不為None, 它應該是一個浮點數,以秒為單位指定操作的超時(或是分數)。

    此方法在退出時返回內部標志,因此除非給定了超時且操作超時,否則它將始終返回True。


    Changed in version 2.7: Previously, the method always returned None.

    2.7版本以前, 這個方法總會返回None.
復制代碼
 

 

  利用wait的阻塞機制, 就能夠實現暫停和恢復了, 再配合循環判斷標識位, 就能實現退出了, 下面是代碼示例:


這完成了暫停, 恢復和停止的功能. 但是這里有一個缺點: 無論是暫停還是停止, 都不是瞬時的, 必須等待run函數內部的運行到達標志位判斷時才有效. 也就是說操作會滯后一次.

  但是這有時也不一定是壞事. 如果run函數中涉及了文件操作或數據庫操作等, 完整地運行一次后再退出, 反而能夠執行剩余的資源釋放操作的代碼(例如各種close). 不會出現程序的文件操作符超出上限, 數據庫連接未釋放等尷尬的情況.
原文解析

這里我修改了原作者的一個地方,就是把self.__flag.set()這一句刪掉了,因為你要是想直接停止線程的話就不需要以后再執行其他操作了。刪了這一句之后原作者所說的滯后現象就沒了,原因未知。

 

將播放音頻的代碼和線程控制的代碼整合后(只播放一次的):

from threading import *
import time
from playsound import playsound
import pyaudio
import wave
# 定義數據流塊
CHUNK = 1024
class MyThread(Thread):
    def init(self,filename):
        self.wf = wave.open(filename, 'rb')  # (sys.argv[1], 'rb')
        self.p = pyaudio.PyAudio()  # 創建一個播放器
    # def init(self,filename):
        # 打開數據流
        self.stream = self.p.open(format=self.p.get_format_from_width(self.wf.getsampwidth()),
                             channels=self.wf.getnchannels(),
                             rate=self.wf.getframerate(),
                             output=True)
        # 讀取數據
        self.data = self.wf.readframes(CHUNK)

        self.__flag =  Event()  # 用於暫停線程的標識
        self.__flag.set()
        self.ifdo = True;

    def run (self):

        while self.ifdo and self.data != '' :
            self.__flag.wait()
            print('I am running...')
            # time.sleep(2)

            # 播放
            self.stream.write(self.data)
            self.data = self.wf.readframes(CHUNK)
        # self.data = ''
    def pause(self):
        self.__flag.clear()  # 設置為False, 讓線程阻塞
        print("pause")

    def resume(self):
        self.__flag.set()  # 設置為True, 讓線程停止阻塞
        print("resume")
    def stop (self):
        print('I am stopping it...')
        self.ifdo = False
    # def restart(self):
    #     self.ifdo


if __name__ == "__main__":
    tr = MyThread()
    tr.init("c2.wav")
    # tr.setDaemon(True)
    tr.start()
    print('\nI will pause it...')
    time.sleep(2)
    tr.pause()
    print("i will resume it...")
    time.sleep(2)
    tr.resume()
    print("i will stop it...")
    time.sleep(2)
    tr.stop()
    # time.sleep(1)
    # tr.stop()
    #

下面是循環播放版本的:

from threading import *
import time
from playsound import playsound
import pyaudio
import wave
# 定義數據流塊
CHUNK = 1024
class MyMusic(Thread):
    def init(self,filename):
        self.filename = filename
        self.wf = wave.open(filename, 'rb')  # (sys.argv[1], 'rb')
        self.p = pyaudio.PyAudio()  # 創建一個播放器
    # def init(self,filename):
        # 打開數據流
        self.stream = self.p.open(format=self.p.get_format_from_width(self.wf.getsampwidth()),
                             channels=self.wf.getnchannels(),
                             rate=self.wf.getframerate(),
                             output=True)
        # 讀取數據
        self.data = self.wf.readframes(CHUNK)

        self.__flag =  Event()  # 用於暫停線程的標識
        self.__flag.set()
        self.ifdo = True;

    def run (self):

        while self.ifdo :
            while len(self.data) > 5:
                self.__flag.wait()
                print('I am running...')
                # time.sleep(2)

                # 播放
                self.stream.write(self.data)
                self.data = self.wf.readframes(CHUNK)
            self.wf = wave.open(self.filename, 'rb')
            self.data = self.wf.readframes(CHUNK)
        # self.data = ''
    def pause(self):
        self.__flag.clear()  # 設置為False, 讓線程阻塞
        print("pause")

    def resume(self):
        self.__flag.set()  # 設置為True, 讓線程停止阻塞
        print("resume")
    def stop (self):
        print('I am stopping it...')
        self.ifdo = False
    # def restart(self):
    #     self.ifdo


if __name__ == "__main__":
    tr = MyMusic()
    tr.init("../resource/2.wav")
    # tr.setDaemon(True)
    tr.start()
    print('\nI will pause it...')
    time.sleep(2)
    # tr.pause()
    print("i will resume it...")
    time.sleep(2)
    tr.resume()
    print("i will stop it...")
    time.sleep(2)
    # tr.stop()
    # time.sleep(1)
    # tr.stop()
    # tr.join()

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM