Pygame出现播放背景音乐卡顿的问题分析及解决(发生在win10更新至win11后),


Pygame是常用的游戏开发库之一。然而在使用Pygame的过程中,却出现了播放背景音乐卡顿的问题。表现为咯咯咯的噪音。

检查Pygame版本,为2.5.2。降级至1.9.6,此时代码报错:

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

没有可用的音频设备。

改到2.0.0版本,代码又能正常运行,但卡顿再次出现。Python版本3.8.10。

看来这个问题大概是Pygame与底层音频驱动的交互问题。可能它找不到正常驱动,调用了兼容驱动,最终被套了多层接口,使得整体播放效率下降,音频出现卡顿。但我无法直接解决,这是Pygame自身的问题。

解决办法:

1. 改用winsound库来播放音乐。

坏处是必须使用wav格式的音频,占用较大。

好处是终于听见了正常的音乐声。

python内置库,所以不用安装。

2. 改用playsound库来播放音乐。

然而,这个第三方库有一些小bug。需要将原文件的第55行:

command = ' '.join(command).encode('utf-16')

更改为:

command = ' '.join(command)#.encode('utf-16')

不需要主动寻找文件。报错时会自动说文件的路径:

    Error 305 for command:
        open "C:\Users\16581\AppData\Local\Temp\PS_hj5h9ji.mp3"
    在用引号括起的字符串不能指定额外的字符。
 
    Error 263 for command:
        close "C:\Users\16581\AppData\Local\Temp\PS_hj5h9ji.mp3"
    指定的设备未打开,或不被 MCI 所识别。
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"
    在用引号括起的字符串不能指定额外的字符。

根据路径打开这个playsound.py文件(Pycharm就直接点路径点开)即可。

之所以报这个错,是因为python3默认的是utf-8的编码方式,而不是utf-16。这个第三方库有些画蛇添足。总之,去掉即可。

可以播放mp3文件。相较于第一个办法,减小了游戏发布大小。

此外playsound这个库还可能在安装时报错。当我从Python3.8.10迁移至3.11.8时,pip安装出现:

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

源于这个discussion: How do I install "playsound" in Python? · termux/termux-app · Discussion #3306 (github.com)

需要注意的是,使用多进程配合playsound播放音乐时,需要将pygame的初始化至少放在if __name__ == "__main__"这一层下,否则会创建两个窗口。

需要注意的是,使用多进程配合playsound播放音乐时,需要将pygame的初始化至少放在if __name__ == "__main__"这一层下,否则会创建两个窗口。

3. 使用pydub.AudioSegment和pydub.playback播放音频

这个方案支持的音频格式是最广的。因为可以通过安装pyaudio的方式来增加支持。

但这个方案也有问题。播放16位音频的话,一切正常。但当音频文件为32位的时候,播放音频就哑声了。

我写了一个复杂的sounddevice输出流程序,测试下来读取的pcm数据是没有问题的,可以正常播放。但我不想把这个程序带到我的主要工程中,一个是太复杂,第二是我还没有实现多个音频播放时怎么定位要终止的一个音频。

那么播放部分pydub.playback究竟是出了什么问题呢?我正在寻找。一个可能的原因是chunk._data(播放时播放的采样片段)是32位整数,但输出到pyaudio的stream.write()当中时,它需要的又是32位浮点。p.get_format_from_width(song.sample_width)可以看到得到的是所谓的"paFloat32",但我在sounddevice的实现程序中明明是把数据当做整数来处理的。

重写了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()

通过除以一个float(1 << (song.sample_width * 8 - 1)),这个值是32位有符号整数的最大值,然后就有了一个[-1, 1)的浮点数,能够送入音频流中播放。

但是实际效果并不好。播放时非常卡顿。看来整数转浮点的过程必须放在外面进行。毕竟用C语言的时候都是直接用指针读值的,哪里需要这些复杂的封装工程。

还有一个问题是我这个方法还只适用于32位音频,其他音频没做适配。还需要一些编程。

最后是用numpy成功解决了卡顿的问题。并加上了采样宽度(采样深度)的判断。

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()

这是一个有些恼人但不严重的bug。人们遇到这个问题,估计会把所有音效文件改成16位来播放。但我非要用原格式不可。

此外,也许应该用array.array来转换更好一些,与原代码倾向的用法保持一致。但我不知道怎么才能除出来一个float32的数组,array.array我又不熟。所以姑且就这么实现了。后面有时间再来熟悉熟悉这个所谓的array.array。貌似它是python自带的,不用自己安装。

比较疑惑的是,32位音频怎么才能判断它是浮点数的32位,还是整数的32位。AudioSegment找了一圈都没有看到有关的API,但是get_array_of_samples貌似输出的一定是整数数组。总之,一切都还需要在array.array中揭晓。

最后,将p.get_format_from_width(song.sample_width)替换为pyaudio._portaudio.paInt32也能正常播放。

以上就是Pygame出现播放背景音乐卡顿的问题分析及解决(发生在win10更新至win11后)的详细内容,更多关于Pygame播放背景音乐卡顿的资料请关注3672js教程其它相关文章!

您可能感兴趣的文章:
  • pygame编写音乐播放器的实现代码示例
  • pygame播放音乐的方法
  • 使用Python和Pygame轻松实现播放音频播放器
  • pygame播放视频并实现音视频同步的解决方案

评论关闭