基于pycaw、ctypes、comtypes的Windows音频检测程序崩溃排查
音频播放检测程序崩溃问题修复
问题背景
Windows 11系统、Python 3.11.3环境下,编写的音频播放检测程序可正常运行数次迭代,但1-2分钟内会静默崩溃。尝试添加CoInitialize和CoUninitialize初始化/反初始化函数无效果,控制台无错误输出,但事件查看器记录了访问违规错误。
原程序代码
import time import logging from pycaw.pycaw import AudioUtilities, IAudioSessionControl2 from comtypes import POINTER, CoInitialize, CoUninitialize from ctypes import cast # Setup logging logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s') # Define the AudioSessionState enumeration class AudioSessionState: AudioSessionStateInactive = 0 AudioSessionStateActive = 1 AudioSessionStateExpired = 2 def is_audio_playing(): CoInitialize() try: sessions = AudioUtilities.GetAllSessions() for session in sessions: try: audio_control = cast(session._ctl, POINTER(IAudioSessionControl2)) state = audio_control.GetState() if state == AudioSessionState.AudioSessionStateActive: return True except Exception as e: logging.error(f"Exception: {e}") raise e except Exception as e: logging.critical(f"Critical error in is_audio_playing: {e}") raise e finally: CoUninitialize() return False iteration = 0 while True: try: iteration += 1 logging.info(f"Iteration {iteration}: {is_audio_playing()}") time.sleep(1) except Exception as e: logging.error(f"Exception: {e}") raise e
事件查看器崩溃报告
- System - Provider [ Name] Application Error [ Guid] {a0e9b465-b939-57d7-b27d-95d8e925ff57} EventID 1000 Version 0 Level 2 Task 100 Opcode 0 Keywords 0x8000000000000000 - TimeCreated [ SystemTime] 2023-12-13T00:25:26.3818329Z EventRecordID 26243 Correlation - Execution [ ProcessID] 32952 [ ThreadID] 11272 Channel Application Computer Ikon - Security [ UserID] S-1-5-21-3641799435-1895349376-3777817403-1001 - EventData AppName python.exe AppVersion 3.11.3150.1013 AppTimeStamp 642cb853 ModuleName _ctypes.pyd ModuleVersion 3.11.3150.1013 ModuleTimeStamp 642cb828 ExceptionCode c0000005 FaultingOffset 00000000000074b1 ProcessId 0x5eb4 ProcessCreationTime 0x1da2d5ab79efffc AppPath C:\Python311\python.exe ModulePath C:\Python311\DLLs\_ctypes.pyd IntegratorReportId a15a3e44-164e-4d8e-a0fa-bdb530c0771e PackageFullName PackageRelativeAppId
调试日志片段
2023-12-12 19:42:37,098 - DEBUG - CoInitializeEx(None, 2) 2023-12-12 19:42:37,104 - DEBUG - Release <POINTER(IMMDeviceEnumerator) ptr=0x22630b64a90 at 22630a26950> 2023-12-12 19:42:37,106 - DEBUG - Release <POINTER(IMMDevice) ptr=0x226301d77d0 at 22632a9bbd0> 2023-12-12 19:42:37,106 - DEBUG - Release <POINTER(IUnknown) ptr=0x22630af5f00 at 22630a26950> 2023-12-12 19:42:37,107 - DEBUG - Release <POINTER(IAudioSessionControl) ptr=0x22630af3830 at 22632a9bbd0> 2023-12-12 19:42:37,107 - DEBUG - Release <POINTER(IAudioSessionManager2) ptr=0x22630af5f00 at 22632a9acd0> 2023-12-12 19:42:37,107 - DEBUG - Release <POINTER(IAudioSessionEnumerator) ptr=0x22632ce8040 at 22630a26950> 2023-12-12 19:42:37,107 - DEBUG - Release <POINTER(IAudioSessionControl) ptr=0x22632cfc620 at 22632a9bcd0> 2023-12-12 19:42:37,108 - DEBUG - Release <POINTER(IAudioSessionControl2) ptr=0x22630af3838 at 22630a26950> 2023-12-12 19:42:37,108 - DEBUG - CoUninitialize() 2023-12-12 19:42:37,108 - DEBUG - Release <POINTER(IAudioSessionControl2) ptr=0x22632cfc628 at 22632a9bcd0> 2023-12-12 19:42:37,109 - INFO - Iteration 25: False 2023-12-12 19:42:38,109 - DEBUG - CoInitializeEx(None, 2) 2023-12-12 19:42:38,119 - DEBUG - Release <POINTER(IMMDeviceEnumerator) ptr=0x22630b64a90 at 22630a26950> 2023-12-12 19:42:38,119 - DEBUG - Release <POINTER(IMMDevice) ptr=0x22630af48d0 at 22632a9bcd0> 2023-12-12 19:42:38,119 - DEBUG - Release <POINTER(IUnknown) ptr=0x22630af5f00 at 22630a26950> 2023-12-12 19:42:38,120 - DEBUG - Release <POINTER(IAudioSessionControl2) ptr=0x22630af39a8 at 22632a9a450>
问题分析
- 崩溃错误码
c0000005是访问违规,说明程序尝试访问了无效的内存地址,根源是COM对象生命周期管理混乱 - 每次调用
is_audio_playing都执行CoInitialize/CoUninitialize,与pycaw内部的COM初始化逻辑冲突,导致COM引用计数紊乱 - 手动用
cast转换COM接口后未主动释放,加上提前return True跳过后续清理,导致残留的COM引用变为野指针 - 异常直接
raise会终止整个程序,没有容错机制
修复方案及代码
以下是修复后的稳定版本代码:
import time import logging from pycaw.pycaw import AudioUtilities, IAudioSessionControl2 from comtypes import POINTER, CoInitialize, CoUninitialize from comtypes.client import QueryInterface # 配置日志 logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s') # 音频会话状态枚举 class AudioSessionState: AudioSessionStateInactive = 0 AudioSessionStateActive = 1 AudioSessionStateExpired = 2 def is_audio_playing(): try: sessions = AudioUtilities.GetAllSessions() for session in sessions: try: # 使用QueryInterface安全获取IAudioSessionControl2接口,符合COM规范 audio_control = QueryInterface(session._ctl, POINTER(IAudioSessionControl2)) state = audio_control.GetState() # 手动释放COM接口引用,避免内存泄漏 audio_control.Release() if state == AudioSessionState.AudioSessionStateActive: return True except Exception as e: logging.error(f"处理音频会话出错: {e}") # 单个会话出错不影响全局,继续处理下一个 continue except Exception as e: logging.critical(f"音频检测逻辑出错: {e}") return False if __name__ == "__main__": # 程序启动时仅初始化一次COM,无需每次调用函数重复操作 CoInitialize() iteration = 0 try: while True: iteration += 1 try: playing_status = is_audio_playing() logging.info(f"Iteration {iteration}: {playing_status}") time.sleep(1) except Exception as e: logging.error(f"迭代执行出错: {e}") # 出错后继续循环,不终止程序 time.sleep(1) except KeyboardInterrupt: # 捕获Ctrl+C,实现优雅终止 logging.info("程序被手动终止") finally: # 程序退出时统一反初始化COM CoUninitialize()
关键修复点
- 全局COM初始化:仅在程序启动时执行一次
CoInitialize,退出时执行CoUninitialize,避免重复初始化导致的冲突 - 安全获取COM接口:用
QueryInterface替代ctypes.cast,符合COM接口查询规范,减少内存错误风险 - 主动释放COM引用:每次获取
IAudioSessionControl2接口后调用Release(),确保资源及时回收 - 容错机制优化:单个会话处理出错时
continue而非raise,避免局部错误导致整个程序崩溃 - 优雅终止支持:捕获
KeyboardInterrupt信号,方便手动终止程序时清理资源
内容的提问来源于stack exchange,提问作者David Hite
相关产品推荐
相关产品推荐

