From 9dcf407ff7c76613a1575eff48198ea71c6068b1 Mon Sep 17 00:00:00 2001 From: shenjack <3695888@qq.com> Date: Fri, 27 Jan 2023 20:28:43 +0800 Subject: [PATCH] pyglet bump --- libs/pyglet/libs/darwin/cocoapy/cocoalibs.py | 7 + libs/pyglet/libs/darwin/coreaudio.py | 172 +++++++++++++++++ libs/pyglet/media/codecs/coreaudio.py | 182 ++++++++++++++++++ .../media/codecs/ffmpeg_lib/libavutil.py | 2 +- libs/pyglet/media/drivers/__init__.py | 18 +- 5 files changed, 372 insertions(+), 9 deletions(-) create mode 100644 libs/pyglet/libs/darwin/coreaudio.py create mode 100644 libs/pyglet/media/codecs/coreaudio.py diff --git a/libs/pyglet/libs/darwin/cocoapy/cocoalibs.py b/libs/pyglet/libs/darwin/cocoapy/cocoalibs.py index f1075c9..e6a26f6 100644 --- a/libs/pyglet/libs/darwin/cocoapy/cocoalibs.py +++ b/libs/pyglet/libs/darwin/cocoapy/cocoalibs.py @@ -19,6 +19,9 @@ kCFStringEncodingUTF8 = 0x08000100 CFAllocatorRef = c_void_p CFStringEncoding = c_uint32 +CFURLRef = c_void_p +CFStringRef = c_void_p +CFURLPathStyle = c_int cf.CFStringCreateWithCString.restype = c_void_p cf.CFStringCreateWithCString.argtypes = [CFAllocatorRef, c_char_p, CFStringEncoding] @@ -97,6 +100,10 @@ cf.CFNumberGetTypeID.argtypes = [] cf.CFGetTypeID.restype = CFTypeID cf.CFGetTypeID.argtypes = [c_void_p] +cf.CFURLCreateWithFileSystemPath.restype = CFURLRef +cf.CFURLCreateWithFileSystemPath.argtypes = [CFAllocatorRef, CFStringRef, CFURLPathStyle, c_bool] + + # CFNumber.h kCFNumberSInt8Type = 1 kCFNumberSInt16Type = 2 diff --git a/libs/pyglet/libs/darwin/coreaudio.py b/libs/pyglet/libs/darwin/coreaudio.py new file mode 100644 index 0000000..3f1a0bc --- /dev/null +++ b/libs/pyglet/libs/darwin/coreaudio.py @@ -0,0 +1,172 @@ +import ctypes +from ctypes import c_void_p, c_int, c_bool, Structure, c_uint32, util, cdll, c_uint, c_double, POINTER, c_int64 + +from pyglet.libs.darwin import CFURLRef + +lib = util.find_library('CoreAudio') + +if lib is None: + lib = '/System/Library/Frameworks/CoreAudio.framework/CoreAudio' + +ca = cdll.LoadLibrary(lib) + + +class AudioStreamPacketDescription(Structure): + _fields_ = [ + ('mStartOffset', c_int64), + ('mVariableFramesInPacket', c_uint32), + ('mDataByteSize', c_uint32) + ] + + +class AudioStreamBasicDescription(Structure): + _fields_ = [ + ('mSampleRate', c_double), + ('mFormatID', c_uint32), + ('mFormatFlags', c_uint32), + ('mBytesPerPacket', c_uint32), + ('mFramesPerPacket', c_uint32), + ('mBytesPerFrame', c_uint32), + ('mChannelsPerFrame', c_uint32), + ('mBitsPerChannel', c_uint32), + ('mReserved', c_uint32) + ] + + def __repr__(self): + return f"AudioStreamBasicDescription(sample_rate={self.mSampleRate}, channels={self.mChannelsPerFrame}, " \ + f"fmt={self.mFormatID}, bytes_per_packet={self.mBytesPerPacket}, bits={self.mBitsPerChannel}, " \ + f"frames_per_packet={self.mFramesPerPacket}, bytes_per_frame={self.mBytesPerFrame})" + + +class AudioBuffer(Structure): + _fields_ = [ + ("mNumberChannels", c_uint), + ("mDataByteSize", c_uint), + ("mData", c_void_p), + ] + + +class AudioBufferList(Structure): + _fields_ = [ + ("mNumberBuffers", c_uint), + ("mBuffers", AudioBuffer * 1), + ] + + +kCFURLPOSIXPathStyle = 0 +ExtAudioFilePropertyID = c_uint32 +OSStatus = c_int + +ExtAudioFileRef = c_void_p + +ca.ExtAudioFileOpenURL.restype = c_int +ca.ExtAudioFileOpenURL.argtypes = [CFURLRef, ExtAudioFileRef] + +ca.ExtAudioFileGetProperty.restype = c_int +ca.ExtAudioFileGetProperty.argtypes = [ExtAudioFileRef, ExtAudioFilePropertyID, POINTER(c_uint32), c_void_p] + +ca.ExtAudioFileSetProperty.restype = c_int +ca.ExtAudioFileSetProperty.argtypes = [ExtAudioFileRef, ExtAudioFilePropertyID, c_uint32, c_void_p] + +ca.ExtAudioFileOpenURL.restype = OSStatus +ca.ExtAudioFileOpenURL.argtypes = [CFURLRef, ExtAudioFileRef] + +AudioFileTypeID = c_uint32 +AudioFileID = c_void_p + +AudioFile_ReadProc = ctypes.CFUNCTYPE(c_int, c_void_p, ctypes.c_int64, c_uint32, c_void_p, POINTER(c_uint32)) +AudioFile_GetSizeProc = ctypes.CFUNCTYPE(ctypes.c_int64, c_void_p) + +ca.AudioFileOpenWithCallbacks.restype = OSStatus +ca.AudioFileOpenWithCallbacks.argtypes = [c_void_p, AudioFile_ReadProc, c_void_p, AudioFile_GetSizeProc, c_void_p, + AudioFileTypeID, POINTER(AudioFileID)] + +ca.ExtAudioFileWrapAudioFileID.restype = OSStatus +ca.ExtAudioFileWrapAudioFileID.argtypes = [AudioFileID, c_bool, POINTER(ExtAudioFileRef)] + +ca.ExtAudioFileRead.restype = OSStatus +ca.ExtAudioFileRead.argtypes = [ExtAudioFileRef, POINTER(c_uint32), POINTER(AudioBufferList)] + +ca.ExtAudioFileSeek.restype = OSStatus +ca.ExtAudioFileSeek.argtypes = [ExtAudioFileRef, c_int64] + +ca.ExtAudioFileDispose.restype = OSStatus +ca.ExtAudioFileDispose.argtypes = [ExtAudioFileRef] + +ca.AudioFileClose.restype = OSStatus +ca.AudioFileClose.argtypes = [AudioFileID] + +kCFAllocatorDefault = None + + +def c_literal(literal): + """Example 'xyz' -> 7895418. + Used for some CoreAudio constants.""" + num = 0 + for idx, char in enumerate(literal): + num |= ord(char) << (len(literal) - idx - 1) * 8 + return num + + +kAudioFilePropertyMagicCookieData = c_literal('mgic') +kExtAudioFileProperty_FileDataFormat = c_literal('ffmt') +kExtAudioFileProperty_ClientDataFormat = c_literal('cfmt') +kExtAudioFileProperty_FileLengthFrames = c_literal('#frm') +kAudioFormatLinearPCM = c_literal('lpcm') + +kAudioFormatFlagIsFloat = 1 << 0 +kAudioFormatFlagIsBigEndian = 1 << 1 +kAudioFormatFlagIsSignedInteger = 1 << 2 +kAudioFormatFlagIsPacked = 1 << 3 +kAudioFormatFlagsNativeEndian = 0 +kAudioFormatFlagsCanonical = kAudioFormatFlagIsFloat | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked +kAudioQueueProperty_MagicCookie = c_literal('aqmc') + +# ERRORS: +kAudio_UnimplementedError = -4 +kAudio_FileNotFoundError = -43 +kAudio_ParamError = -50 +kAudio_MemFullError = -108 + +kAudioFileUnspecifiedError = c_literal('wht?') # 0x7768743F, 2003334207 +kAudioFileUnsupportedFileTypeError = c_literal('typ?') # 0x7479703F, 1954115647 +kAudioFileUnsupportedDataFormatError = c_literal('fmt?') # 0x666D743F, 1718449215 +kAudioFileUnsupportedPropertyError = c_literal('pty?') # 0x7074793F, 1886681407 +kAudioFileBadPropertySizeError = c_literal('!siz') # 0x2173697A, 561211770 +kAudioFilePermissionsError = c_literal('prm?') # 0x70726D3F, 1886547263 +kAudioFileNotOptimizedError = c_literal('optm') # 0x6F70746D, 1869640813 +# file format specific error codes +kAudioFileInvalidChunkError = c_literal('chk?') # 0x63686B3F, 1667787583 +kAudioFileDoesNotAllow64BitDataSizeError = c_literal('off?') # 0x6F66663F, 1868981823 +kAudioFileInvalidPacketOffsetError = c_literal('pck?') # 0x70636B3F, 1885563711 +kAudioFileInvalidFileError = c_literal('dta?') # 0x6474613F, 1685348671 +kAudioFileOperationNotSupportedError = c_literal('op?') # 0x6F703F3F +# general file error codes +kAudioFileNotOpenError = -38 +kAudioFileEndOfFileError = -39 +kAudioFilePositionError = -40 +kAudioFileFileNotFoundError = -43 + +err_str_db = { + kAudioFileUnspecifiedError: "An unspecified error has occurred.", + kAudioFileUnsupportedFileTypeError: "The file type is not supported.", + kAudioFileUnsupportedDataFormatError: "The data format is not supported by this file type.", + kAudioFileUnsupportedPropertyError: "The property is not supported.", + kAudioFileBadPropertySizeError: "The size of the property data was not correct.", + kAudioFilePermissionsError: "The operation violated the file permissions.", + kAudioFileNotOptimizedError: "The chunks following the audio data chunk are preventing the extension of the audio data chunk. To write more data, you must optimize the file.", + kAudioFileInvalidChunkError: "Either the chunk does not exist in the file or it is not supported by the file.", + kAudioFileDoesNotAllow64BitDataSizeError: "The file offset was too large for the file type. The AIFF and WAVE file format types have 32-bit file size limits.", + kAudioFileInvalidPacketOffsetError: "A packet offset was past the end of the file, or not at the end of the file when a VBR format was written, or a corrupt packet size was read when the packet table was built.", + kAudioFileInvalidFileError: "The file is malformed, or otherwise not a valid instance of an audio file of its type.", + kAudioFileOperationNotSupportedError: "The operation cannot be performed.", + kAudioFileNotOpenError: "The file is closed.", + kAudioFileEndOfFileError: "End of file.", + kAudioFilePositionError: "Invalid file position.", + kAudioFileFileNotFoundError: "File not found.", +} + + +def err_check(err): + if err != 0: + raise Exception(err, err_str_db.get(err, "Unknown Error")) diff --git a/libs/pyglet/media/codecs/coreaudio.py b/libs/pyglet/media/codecs/coreaudio.py new file mode 100644 index 0000000..bad16d9 --- /dev/null +++ b/libs/pyglet/media/codecs/coreaudio.py @@ -0,0 +1,182 @@ +from ctypes import memmove, byref, c_uint32, sizeof, cast, c_void_p, create_string_buffer, POINTER, c_char, \ + c_long + +from pyglet.libs.darwin import cf, CFSTR +from pyglet.libs.darwin.coreaudio import kCFURLPOSIXPathStyle, AudioStreamBasicDescription, ca, ExtAudioFileRef, \ + kExtAudioFileProperty_FileDataFormat, kAudioFormatLinearPCM, kAudioFormatFlagIsSignedInteger, \ + kAudioFormatFlagIsPacked, kExtAudioFileProperty_ClientDataFormat, AudioFile_ReadProc, \ + AudioFile_GetSizeProc, AudioBufferList, kExtAudioFileProperty_FileLengthFrames, AudioFileID, err_check +from pyglet.media import StreamingSource, StaticSource +from pyglet.media.codecs import AudioFormat, MediaDecoder, AudioData + + +class MemoryFileObject: + + def __init__(self, file): + self.file = file + + if not getattr(self.file, 'seek', None) or not getattr(self.file, 'tell', None): + raise Exception("File object does not support seeking.") + + # Seek to end of file to get the filesize. + self.file.seek(0, 2) + self.file_size = self.file.tell() + self.file.seek(0) # Put cursor back at the beginning. + self.data = [] + + def read_data_cb(ref, offset, requested_length, buffer, actual_count): + self.file.seek(offset) + data = self.file.read(requested_length) + data_size = len(data) + memmove(buffer, data, data_size) + actual_count.contents.value = data_size # Actual read. + return 0 + + def getsize_cb(ref): + return self.file_size + + self.getsize_func = AudioFile_GetSizeProc(getsize_cb) + self.read_func = AudioFile_ReadProc(read_data_cb) + + +class CoreAudioSource(StreamingSource): + def __init__(self, filename, file=None): + self._bl = None + self._file = file + self._deleted = False + self._file_obj = None + self._audfile = None + self._audref = None + + audref = ExtAudioFileRef() + if file is None: + fn_str = CFSTR(filename) + url_ref = cf.CFURLCreateWithFileSystemPath(None, fn_str, kCFURLPOSIXPathStyle, False) + + err_check(ca.ExtAudioFileOpenURL(url_ref, byref(audref))) + else: + self.file_obj = MemoryFileObject(file) + + self._audfile = AudioFileID() + + err_check(ca.AudioFileOpenWithCallbacks( + None, self.file_obj.read_func, None, self.file_obj.getsize_func, None, + 0, + byref(self._audfile)) + ) + + err_check(ca.ExtAudioFileWrapAudioFileID(self._audfile, False, byref(audref))) + + self._audref = audref + + format_info = AudioStreamBasicDescription() + size = c_uint32(sizeof(format_info)) + err_check(ca.ExtAudioFileGetProperty(self._audref, + kExtAudioFileProperty_FileDataFormat, + byref(size), + byref(format_info))) + + self.convert_desc = self.convert_format(format_info) + + err_check(ca.ExtAudioFileSetProperty( + self._audref, + kExtAudioFileProperty_ClientDataFormat, + sizeof(self.convert_desc), + byref(self.convert_desc) + )) + + length = c_long() + size = c_uint32(sizeof(format_info)) + # File length. + err_check(ca.ExtAudioFileGetProperty( + self._audref, + kExtAudioFileProperty_FileLengthFrames, + byref(size), + byref(length) + )) + + self.audio_format = AudioFormat(channels=self.convert_desc.mChannelsPerFrame, + sample_size=self.convert_desc.mBitsPerChannel, + sample_rate=int(self.convert_desc.mSampleRate)) + + self._num_frames = length.value + self._bytes_per_frame = self.convert_desc.mBytesPerFrame + self._duration = self._num_frames / self.convert_desc.mSampleRate + self._duration_per_frame = self._duration / self._num_frames + + @staticmethod + def convert_format(original_desc, bitdepth=16): + adesc = AudioStreamBasicDescription() + adesc.mSampleRate = original_desc.mSampleRate + adesc.mFormatID = kAudioFormatLinearPCM + adesc.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked + adesc.mChannelsPerFrame = original_desc.mChannelsPerFrame + adesc.mBitsPerChannel = bitdepth + adesc.mBytesPerPacket = original_desc.mChannelsPerFrame * adesc.mBitsPerChannel // 8 + adesc.mFramesPerPacket = 1 + adesc.mBytesPerFrame = adesc.mBytesPerPacket + return adesc + + def __del__(self): + if self._file: + self._file.close() + self._file = None + + if self._audfile: + err_check(ca.AudioFileClose(self._audfile)) + self._audfile = None + + if self._audref: + err_check(ca.ExtAudioFileDispose(self._audref)) + self._audref = None + + def get_audio_data(self, num_bytes, compensation_time=0.0): + num_frames = c_uint32(num_bytes // self.convert_desc.mBytesPerFrame) + + if not self._bl: + buffer = create_string_buffer(num_bytes) + self._bl = AudioBufferList() + self._bl.mNumberBuffers = 1 + self._bl.mBuffers[0].mNumberChannels = self.convert_desc.mChannelsPerFrame + self._bl.mBuffers[0].mDataByteSize = num_bytes + self._bl.mBuffers[0].mData = cast(buffer, c_void_p) + + while True: + ca.ExtAudioFileRead(self._audref, byref(num_frames), byref(self._bl)) + + size = self._bl.mBuffers[0].mDataByteSize + if not size: + break + + data = cast(self._bl.mBuffers[0].mData, POINTER(c_char)) + slice = data[:size] + return AudioData(slice, size, 0.0, 0.0, []) + + def seek(self, timestamp): + timestamp = max(0.0, min(timestamp, self._duration)) + position = int(timestamp / self._duration_per_frame) + ca.ExtAudioFileSeek(self._audref, position) + + +######################################### +# Decoder class: +######################################### + +class CoreAudioDecoder(MediaDecoder): + + def get_file_extensions(self): + return '.aac', '.ac3', '.aif', '.aiff', '.aifc', '.caf', '.mp3', '.mp4', '.m4a', '.snd', '.au', '.sd2', '.wav' + + def decode(self, filename, file, streaming=True): + if streaming: + return CoreAudioSource(filename, file) + else: + return StaticSource(CoreAudioSource(filename, file)) + + +def get_decoders(): + return [CoreAudioDecoder()] + + +def get_encoders(): + return [] diff --git a/libs/pyglet/media/codecs/ffmpeg_lib/libavutil.py b/libs/pyglet/media/codecs/ffmpeg_lib/libavutil.py index 540bf23..6969a0c 100644 --- a/libs/pyglet/media/codecs/ffmpeg_lib/libavutil.py +++ b/libs/pyglet/media/codecs/ffmpeg_lib/libavutil.py @@ -13,7 +13,7 @@ _debug = debug_print('debug_media') avutil = pyglet.lib.load_library( 'avutil', win32=('avutil-57', 'avutil-56'), - darwin=('avutil.57', 'avutil-56') + darwin=('avutil.57', 'avutil.56') ) avutil.avutil_version.restype = c_int diff --git a/libs/pyglet/media/drivers/__init__.py b/libs/pyglet/media/drivers/__init__.py index 0b9b812..6de7461 100644 --- a/libs/pyglet/media/drivers/__init__.py +++ b/libs/pyglet/media/drivers/__init__.py @@ -34,15 +34,17 @@ def get_audio_driver(): _audio_driver = pulse.create_audio_driver() break elif driver_name == 'xaudio2': - from pyglet.libs.win32.constants import WINDOWS_8_OR_GREATER - if WINDOWS_8_OR_GREATER: - from . import xaudio2 - _audio_driver = xaudio2.create_audio_driver() - break + if pyglet.compat_platform in ('win32', 'cygwin'): + from pyglet.libs.win32.constants import WINDOWS_8_OR_GREATER + if WINDOWS_8_OR_GREATER: + from . import xaudio2 + _audio_driver = xaudio2.create_audio_driver() + break elif driver_name == 'directsound': - from . import directsound - _audio_driver = directsound.create_audio_driver() - break + if pyglet.compat_platform in ('win32', 'cygwin'): + from . import directsound + _audio_driver = directsound.create_audio_driver() + break elif driver_name == 'openal': from . import openal _audio_driver = openal.create_audio_driver()