wxPython和pycairo练习记录19

Sound.py 播放音频文件 https://github.com/mx0c/super-mario-python/blob/5623213c2eb001d8dabd95eb7035d7545e86ccf6/classes/Sound.py
在 wxPython 中 wx.adv.Sound 只能播放 WAV 格式的文件,或者在 UI 中调用 wx.media.MediaCtrl 控件,但素材里的音频还有 OGG 格式的,没法用 wxPython 在后台播放。

pygame.mixer.Sound 使用的是 Simple DirectMedia Layer (SDL) 提供的接口,详情见:
https://www.libsdl.org/index.php
https://github.com/libsdl-org/SDL
https://github.com/pygame/pygame/blob/main/src_c/cython/pygame/_sdl2/mixer.pxd
https://www.pygame.org/docs/ref/mixer.html

在 pygame 安装目录 python3\Lib\site-packages\pygame 中有 SDL2_mixer.dll,应该可以通过它调用并播放多种格式音频。另外还有个 libogg-0.dll,不考虑扩展,这个应该才是需要的。
使用工具查看 dll 导出函数:
https://github.com/lucasg/Dependencies/releases/tag/v1.11.1
dll导出函数

libogg-0.dll 提供了对 OGG 容器格式的基本支持,可以用来读取和处理 OGG 文件的数据结构。好像没什么用,它的作用有点像为现实里的鸡蛋编写一个鸡蛋类。

OGG 文件中保存的是压缩后的音频数据,而不是解码后的原始音频数据。解码是将压缩的音频数据转换为原始的 PCM 数据的过程,编码则相反。PCM(Pulse Code Modulation)脉冲编码调制,是一种将模拟信号转换为数字信号的方法。

这里的目标是读取 OGG 文件中的压缩数据,解码为 PCM 数据,转换为 WAV 格式数据,最后转为 wx.adv.Sound 对象。 WAV 数据就是 WAV 文件头加上 PCM 字节数据。

找到另一个库:
https://github.com/xiph/vorbis
编译好的 dll,libvorbisfile-3.dll 依赖 libogg-0.dll 和 libvorbis-0.dll,所以三个都需要:
https://github.com/QutEcoacoustics/audio-analysis/blob/master/lib/audio-utils/win-x64/sox/libvorbisfile-3.dll
https://github.com/QutEcoacoustics/audio-analysis/blob/master/lib/audio-utils/win-x64/sox/libogg-0.dll
https://github.com/QutEcoacoustics/audio-analysis/blob/master/lib/audio-utils/win-x64/sox/libvorbis-0.dll

# 此代码不能正常运行,提示 OSError: exception: access violation writing 0x0062F564
import ctypes

# 加载 libvorbisfile-3.dll
vorbisfile_lib = ctypes.CDLL("./classes/libvorbisfile-3.dll")

# 定义函数参数类型
vorbisfile_lib.ov_open_callbacks.argtypes = [ctypes.c_void_p, ctypes.POINTER(ctypes.c_void_p), ctypes.c_char_p, ctypes.c_long, ctypes.c_void_p]
vorbisfile_lib.ov_open_callbacks.restype = ctypes.c_int

vorbisfile_lib.ov_read.argtypes = [ctypes.c_void_p, ctypes.POINTER(ctypes.c_char), ctypes.c_int, ctypes.POINTER(ctypes.c_int), ctypes.c_int, ctypes.c_int, ctypes.POINTER(ctypes.c_int)]
vorbisfile_lib.ov_read.restype = ctypes.c_int

# 打开 OGG 文件
ogg_file = "./sfx/coin.ogg"
ogg_file_handle = open(ogg_file, "rb")

# 创建 OggVorbis_File 结构体
ov_struct = ctypes.c_void_p()

# 打开 OGG 文件流
# https://xiph.org/vorbis/doc/vorbisfile/ov_open_callbacks.html
vorbisfile_lib.ov_open_callbacks(ogg_file_handle.fileno(), ctypes.byref(ov_struct), None, 0, None)

# 获取音频信息
info = vorbisfile_lib.ov_info(ctypes.byref(ov_struct), -1)
print(info)
# 读取和解码音频数据
pcm_data = b""
while True:
    pcm_temp = (ctypes.c_char * 4096)()  # 创建缓冲区数组
    ret = vorbisfile_lib.ov_read(ctypes.byref(ov_struct), pcm_temp, len(pcm_temp), None, 2, 1, None)
    if ret == 0:
        break
    pcm_data += pcm_temp.raw[:ret]

# 关闭 OGG 文件流
vorbisfile_lib.ov_clear(ctypes.byref(ov_struct))

# 关闭 OGG 文件
ogg_file_handle.close()

print(pcm_data)

不知道怎么解决这个上面代码的问题,只能先换个方法了。pip3 install pyogg ,它可以用于读取并解码 OGG。

wx.adv.Sound 并没有播放声道参数,只有标志位用于设置是同步还是异步播放。pygame.mixer.Channel 这里的声道和音频文件的声道含义不一样,这里的声道是指播放通道,0 为第一个声道。SFX(Sound Effects)指声音特效。原作者的意思应该是一个声道用于播放背景音乐,一个声道用于播放音效。

测试的时候,只有设置同步播放 wx.adv.SOUND_SYNC 才有声音,后面再修改,所有代码都完成了如果还是这样就再创建一个线程来播放声音。

# Sound.py
import struct
import pyogg
import wx.adv


class Sound:
    def __init__(self):
        self.allow_sfx = True

        self.soundtrack = self.load_ogg("../sfx/main_theme.ogg")
        self.coin = self.load_ogg("../sfx/coin.ogg")
        self.bump = self.load_ogg("../sfx/bump.ogg")
        self.stomp = self.load_ogg("../sfx/stomp.ogg")
        self.jump = self.load_ogg("../sfx/small_jump.ogg")
        self.death = wx.adv.Sound("../sfx/death.wav")
        self.kick = self.load_ogg("../sfx/kick.ogg")
        self.brick_bump = self.load_ogg("../sfx/brick-bump.ogg")
        self.powerup = self.load_ogg('../sfx/powerup.ogg')
        self.powerup_appear = self.load_ogg('../sfx/powerup_appears.ogg')
        self.pipe = self.load_ogg('../sfx/pipe.ogg')

    def play_sfx(self, sfx):
        if self.allow_sfx:
            sfx.Play(wx.adv.SOUND_ASYNC)

    def play_music(self, music):
        # 循环播放
        music.Play(wx.adv.SOUND_ASYNC | wx.adv.SOUND_LOOP)

    @staticmethod
    def pcm_to_wav(pcm_data, channels, frequency, bit_depth):
    	# channels 声道,1单声道 2立体声,frequency 采样率,bit_depth 样本位数,表示声音振幅,取值 8/16/24/32 越高质量越好
        # wav 文件头
        wav_header = struct.pack('<4sI4s4sIHHIIHH4sI', b'RIFF', 36 + len(pcm_data), b'WAVE', b'fmt ', 16, 1, channels,
                                 frequency, frequency * channels * bit_depth // 8, channels * bit_depth // 8, bit_depth,
                                 b'data', len(pcm_data))
        # 拼接 wav 文件头和 pcm 数据
        wav_data = wav_header + pcm_data
        return wav_data

    def load_ogg(self, file_path):
        ogg = pyogg.VorbisFile(file_path)
        wav_data = self.pcm_to_wav(ogg.buffer, ogg.channels, ogg.frequency, 16)
        sound = wx.adv.Sound()
        sound.CreateFromData(wav_data)
        return sound


if __name__ == "__main__":
    import wx
    app = wx.App()
    s = Sound()
    s.play_music(s.soundtrack)
    s.play_sfx(s.coin)

  • 19
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值