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