Pygame出現(xiàn)播放背景音樂(lè)卡頓的問(wèn)題分析及解決(發(fā)生在win10更新至win11后)
Pygame是常用的游戲開(kāi)發(fā)庫(kù)之一。然而在使用Pygame的過(guò)程中,卻出現(xiàn)了播放背景音樂(lè)卡頓的問(wèn)題。表現(xiàn)為咯咯咯的噪音。
檢查Pygame版本,為2.5.2。降級(jí)至1.9.6,此時(shí)代碼報(bào)錯(cuò):
Traceback (most recent call last):
File "D:\MyWork\Code_Learning\PythonLearning\Pygame\test2.py", line 4, in <module>
pygame.mixer.init()
pygame.error: No available audio device沒(méi)有可用的音頻設(shè)備。
改到2.0.0版本,代碼又能正常運(yùn)行,但卡頓再次出現(xiàn)。Python版本3.8.10。
看來(lái)這個(gè)問(wèn)題大概是Pygame與底層音頻驅(qū)動(dòng)的交互問(wèn)題??赡芩也坏秸r?qū)動(dòng),調(diào)用了兼容驅(qū)動(dòng),最終被套了多層接口,使得整體播放效率下降,音頻出現(xiàn)卡頓。但我無(wú)法直接解決,這是Pygame自身的問(wèn)題。
解決辦法:
1. 改用winsound庫(kù)來(lái)播放音樂(lè)。
壞處是必須使用wav格式的音頻,占用較大。
好處是終于聽(tīng)見(jiàn)了正常的音樂(lè)聲。
python內(nèi)置庫(kù),所以不用安裝。
2. 改用playsound庫(kù)來(lái)播放音樂(lè)。
然而,這個(gè)第三方庫(kù)有一些小bug。需要將原文件的第55行:
command = ' '.join(command).encode('utf-16')更改為:
command = ' '.join(command)#.encode('utf-16')不需要主動(dòng)尋找文件。報(bào)錯(cuò)時(shí)會(huì)自動(dòng)說(shuō)文件的路徑:
Error 305 for command:
open "C:\Users\16581\AppData\Local\Temp\PS_hj5h9ji.mp3"
在用引號(hào)括起的字符串不能指定額外的字符。
Error 263 for command:
close "C:\Users\16581\AppData\Local\Temp\PS_hj5h9ji.mp3"
指定的設(shè)備未打開(kāi),或不被 MCI 所識(shí)別。
Failed to close the file: "C:\Users\16581\AppData\Local\Temp\PS_hj5h9ji.mp3"
Traceback (most recent call last):
File "D:\MyWork\Code_Learning\PythonLearning\Pygame\test2.py", line 8, in <module>
playsound.playsound("./src/Hello_How are you.mp3")
File "D:\MyWork\Code_Learning\PythonLearning\Pygame\Runtime3.8\lib\site-packages\playsound.py", line 44, in _playsoundWin
_playsoundWin(tempPath, block)
File "D:\MyWork\Code_Learning\PythonLearning\Pygame\Runtime3.8\lib\site-packages\playsound.py", line 72, in _playsoundWin
winCommand(u'open {}'.format(sound))
File "D:\MyWork\Code_Learning\PythonLearning\Pygame\Runtime3.8\lib\site-packages\playsound.py", line 64, in winCommand
raise PlaysoundException(exceptionMessage)
playsound.PlaysoundException:
Error 305 for command:
open "C:\Users\16581\AppData\Local\Temp\PS_hj5h9ji.mp3"
在用引號(hào)括起的字符串不能指定額外的字符。根據(jù)路徑打開(kāi)這個(gè)playsound.py文件(Pycharm就直接點(diǎn)路徑點(diǎn)開(kāi))即可。
之所以報(bào)這個(gè)錯(cuò),是因?yàn)閜ython3默認(rèn)的是utf-8的編碼方式,而不是utf-16。這個(gè)第三方庫(kù)有些畫蛇添足??傊サ艏纯?。
可以播放mp3文件。相較于第一個(gè)辦法,減小了游戲發(fā)布大小。
此外playsound這個(gè)庫(kù)還可能在安裝時(shí)報(bào)錯(cuò)。當(dāng)我從Python3.8.10遷移至3.11.8時(shí),pip安裝出現(xiàn):
Collecting playsound
Using cached playsound-1.3.0.tar.gz (7.7 kB)
Installing build dependencies ... done
Getting requirements to build wheel ... error
error: subprocess-exited-with-error
× Getting requirements to build wheel did not run successfully.
│ exit code: 1
╰─> [29 lines of output]
Traceback (most recent call last):
File "/data/data/com.termux/files/usr/lib/python3.11/site-packages/pip/_vendor/pyproject_hooks/_in_process/_in_process.py", line 353, in
main()
File "/data/data/com.termux/files/usr/lib/python3.11/site-packages/pip/_vendor/pyproject_hooks/_in_process/_in_process.py", line 335, in main
json_out['return_val'] = hook(**hook_input['kwargs'])
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/data/data/com.termux/files/usr/lib/python3.11/site-packages/pip/_vendor/pyproject_hooks/_in_process/_in_process.py", line 118, in get_requires_for_build_wheel
return hook(config_settings)
^^^^^^^^^^^^^^^^^^^^^
File "/data/data/com.termux/files/usr/tmp/pip-build-env-pa_2_lv3/overlay/lib/python3.11/site-packages/setuptools/build_meta.py", line 338, in get_requires_for_build_wheel
return self._get_build_requires(config_settings, requirements=['wheel'])
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/data/data/com.termux/files/usr/tmp/pip-build-env-pa_2_lv3/overlay/lib/python3.11/site-packages/setuptools/build_meta.py", line 320, in _get_build_requires
self.run_setup()
File "/data/data/com.termux/files/usr/tmp/pip-build-env-pa_2_lv3/overlay/lib/python3.11/site-packages/setuptools/build_meta.py", line 485, in run_setup
self).run_setup(setup_script=setup_script) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/data/data/com.termux/files/usr/tmp/pip-build-env-pa_2_lv3/overlay/lib/python3.11/site-packages/setuptools/build_meta.py", line 335, in run_setup
exec(code, locals())
File "", line 6, in
File "/data/data/com.termux/files/usr/lib/python3.11/inspect.py", line 1262, in getsource
lines, lnum = getsourcelines(object)
^^^^^^^^^^^^^^^^^^^^^^
File "/data/data/com.termux/files/usr/lib/python3.11/inspect.py", line 1244, in getsourcelines
lines, lnum = findsource(object)
^^^^^^^^^^^^^^^^^^
File "/data/data/com.termux/files/usr/lib/python3.11/inspect.py", line 1081, in findsource
raise OSError('could not get source code')
OSError: could not get source code
[end of output]
note: This error originates from a subprocess, and is likely not a problem with pip.
error: subprocess-exited-with-error
× Getting requirements to build wheel did not run successfully.
│ exit code: 1 ╰─> See above for output.
note: This error originates from a subprocess, and is likely not a problem with pip.`最后解決辦法就是更新一下wheel就行了:
pip install wheel --upgrade
源于這個(gè)discussion: How do I install "playsound" in Python? · termux/termux-app · Discussion #3306 (github.com)
需要注意的是,使用多進(jìn)程配合playsound播放音樂(lè)時(shí),需要將pygame的初始化至少放在if __name__ == "__main__"這一層下,否則會(huì)創(chuàng)建兩個(gè)窗口。
需要注意的是,使用多進(jìn)程配合playsound播放音樂(lè)時(shí),需要將pygame的初始化至少放在if __name__ == "__main__"這一層下,否則會(huì)創(chuàng)建兩個(gè)窗口。
3. 使用pydub.AudioSegment和pydub.playback播放音頻
這個(gè)方案支持的音頻格式是最廣的。因?yàn)榭梢酝ㄟ^(guò)安裝pyaudio的方式來(lái)增加支持。
但這個(gè)方案也有問(wèn)題。播放16位音頻的話,一切正常。但當(dāng)音頻文件為32位的時(shí)候,播放音頻就啞聲了。
我寫了一個(gè)復(fù)雜的sounddevice輸出流程序,測(cè)試下來(lái)讀取的pcm數(shù)據(jù)是沒(méi)有問(wèn)題的,可以正常播放。但我不想把這個(gè)程序帶到我的主要工程中,一個(gè)是太復(fù)雜,第二是我還沒(méi)有實(shí)現(xiàn)多個(gè)音頻播放時(shí)怎么定位要終止的一個(gè)音頻。
那么播放部分pydub.playback究竟是出了什么問(wèn)題呢?我正在尋找。一個(gè)可能的原因是chunk._data(播放時(shí)播放的采樣片段)是32位整數(shù),但輸出到pyaudio的stream.write()當(dāng)中時(shí),它需要的又是32位浮點(diǎn)。p.get_format_from_width(song.sample_width)可以看到得到的是所謂的"paFloat32",但我在sounddevice的實(shí)現(xiàn)程序中明明是把數(shù)據(jù)當(dāng)做整數(shù)來(lái)處理的。
重寫了playback的_play_with_pyaudio()方法。
def _play_with_pyaudio(seg):
import pyaudio
p = pyaudio.PyAudio()
stream = p.open(format=p.get_format_from_width(seg.sample_width),
channels=seg.channels,
rate=seg.frame_rate,
output=True)
# Just in case there were any exceptions/interrupts, we release the resource
# So as not to raise OSError: Device Unavailable should play() be used again
try:
# break audio into half-second chunks (to allows keyboard interrupts)
for chunk in make_chunks(seg, 500):
data = chunk._data
need = bytearray(len(data))
for i in range(0, len(data) // 4, 4):
f = (int.from_bytes(data[i:i + 4], byteorder='little', signed=True)
/ float(1 << (seg.sample_width * 8 - 1)))
need[i:i + 4] = struct.pack('<f', f)
need = bytes(need)
stream.write(need)
finally:
stream.stop_stream()
stream.close()
p.terminate()通過(guò)除以一個(gè)float(1 << (song.sample_width * 8 - 1)),這個(gè)值是32位有符號(hào)整數(shù)的最大值,然后就有了一個(gè)[-1, 1)的浮點(diǎn)數(shù),能夠送入音頻流中播放。
但是實(shí)際效果并不好。播放時(shí)非??D??磥?lái)整數(shù)轉(zhuǎn)浮點(diǎn)的過(guò)程必須放在外面進(jìn)行。畢竟用C語(yǔ)言的時(shí)候都是直接用指針讀值的,哪里需要這些復(fù)雜的封裝工程。
還有一個(gè)問(wèn)題是我這個(gè)方法還只適用于32位音頻,其他音頻沒(méi)做適配。還需要一些編程。
最后是用numpy成功解決了卡頓的問(wèn)題。并加上了采樣寬度(采樣深度)的判斷。
def _play_with_pyaudio(seg):
import pyaudio
import numpy as np
p = pyaudio.PyAudio()
stream = p.open(format=p.get_format_from_width(seg.sample_width),
channels=seg.channels,
rate=seg.frame_rate,
output=True)
# Just in case there were any exceptions/interrupts, we release the resource
# So as not to raise OSError: Device Unavailable should play() be used again
try:
# break audio into half-second chunks (to allows keyboard interrupts)
if seg.sample_width == 4:
for chunk in utils.make_chunks(seg, 500):
samples = chunk.get_array_of_samples()
samples = np.array(samples, dtype=np.float32)
samples /= 1 << (seg.sample_width * 8 - 1)
stream.write(samples.tobytes())
# stream.write(chunk._data)
# print(chunk._data[:500])
# print(song.get_array_of_samples()[:500])
# os.system('pause')
else:
for chunk in utils.make_chunks(seg, 500):
stream.write(chunk._data)
finally:
stream.stop_stream()
stream.close()
p.terminate()這是一個(gè)有些惱人但不嚴(yán)重的bug。人們遇到這個(gè)問(wèn)題,估計(jì)會(huì)把所有音效文件改成16位來(lái)播放。但我非要用原格式不可。
此外,也許應(yīng)該用array.array來(lái)轉(zhuǎn)換更好一些,與原代碼傾向的用法保持一致。但我不知道怎么才能除出來(lái)一個(gè)float32的數(shù)組,array.array我又不熟。所以姑且就這么實(shí)現(xiàn)了。后面有時(shí)間再來(lái)熟悉熟悉這個(gè)所謂的array.array。貌似它是python自帶的,不用自己安裝。
比較疑惑的是,32位音頻怎么才能判斷它是浮點(diǎn)數(shù)的32位,還是整數(shù)的32位。AudioSegment找了一圈都沒(méi)有看到有關(guān)的API,但是get_array_of_samples貌似輸出的一定是整數(shù)數(shù)組??傊?,一切都還需要在array.array中揭曉。
最后,將p.get_format_from_width(song.sample_width)替換為pyaudio._portaudio.paInt32也能正常播放。
以上就是Pygame出現(xiàn)播放背景音樂(lè)卡頓的問(wèn)題分析及解決(發(fā)生在win10更新至win11后)的詳細(xì)內(nèi)容,更多關(guān)于Pygame播放背景音樂(lè)卡頓的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Python動(dòng)態(tài)創(chuàng)建類實(shí)例詳解
這篇文章主要為大家介紹了Python動(dòng)態(tài)創(chuàng)建類實(shí)例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-12-12
python中return的返回和執(zhí)行實(shí)例
今天小編就為大家分享一篇python中return的返回和執(zhí)行實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-12-12
python3 使用Opencv打開(kāi)USB攝像頭,配置1080P分辨率的操作
今天小編就為大家分享一篇python3 使用Opencv打開(kāi)USB攝像頭,配置1080P分辨率的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-12-12

