NAudio播放进度拖动时COM对象转换异常问题排查
NAudio播放时拖动进度条触发InvalidCastException异常的原因及解决
场景说明
我用NAudio开发GUI音频播放器,实现逻辑为:创建AudioFileReader,将其作为源传入VolumeSampleProvider构造函数,再用该VolumeSampleProvider初始化WaveOut——目的是实现播放时调整音量,甚至支持音量值大于1(比如给未归一化的音频实时做归一化处理)。
播放器包含进度条,播放过程中用户点击或拖动进度条时,执行流程是:先调用WaveOut.Stop(),修改AudioFileReader.Position,再调用WaveOut.Play()恢复播放。但这个操作频繁触发InvalidCastException,奇怪的是暂停或停止状态下拖动进度条从未出现异常,哪怕用的是相同的WaveOut.Pause()和WaveOut.Stop()方法。
我已尝试修改Position前先调用Stop(),也限制Position修改频率在100ms以上排查同步问题,但均未彻底解决。
异常信息
System.InvalidCastException HResult=0x80004002 Message=无法将类型为“System.__ComObject”的 COM 对象强制转换为接口类型“NAudio.MediaFoundation.IMFSourceReader”。此操作失败的原因是对 COM 组件执行 QueryInterface 调用,以获取具有 IID“{70AE66F2-C809-4E4F-8915-BDCB406B7993}”的接口时,出现以下错误: 不支持的接口 (0x80004002 (E_NOINTERFACE))。 Source=System.Private.CoreLib StackTrace: at System.StubHelpers.StubHelpers.GetCOMIPFromRCW(Object objSrc, IntPtr pCPCMD, IntPtr& ppTarget, Boolean& pfNeedsRelease) at NAudio.MediaFoundation.IMFSourceReader.SetCurrentPosition(Guid guidTimeFormat, IntPtr varPosition) at NAudio.Wave.MediaFoundationReader.Reposition(Int64 desiredPosition) in NAudio.Wave\MediaFoundationReader.cs:line 264 at NAudio.Wave.MediaFoundationReader.Read(Byte[] buffer, Int32 offset, Int32 count) in NAudio.Wave\MediaFoundationReader.cs:line 208 at NAudio.Wave.SampleProviders.Pcm16BitToSampleProvider.Read(Single[] buffer, Int32 offset, Int32 count) in NAudio.Wave.SampleProviders\Pcm16BitToSampleProvider.cs:line 16 at NAudio.Wave.SampleProviders.MeteringSampleProvider.Read(Single[] buffer, Int32 offset, Int32 count) in NAudio.Wave.SampleProviders\MeteringSampleProvider.cs:line 42 at NAudio.Wave.SampleProviders.VolumeSampleProvider.Read(Single[] buffer, Int32 offset, Int32 sampleCount) in NAudio.Wave.SampleProviders\VolumeSampleProvider.cs:line 19 at NAudio.Wave.AudioFileReader.Read(Single[] buffer, Int32 offset, Int32 count) in NAudio.Wave\AudioFileReader.cs:line 107 at NAudio.Wave.AudioFileReader.Read(Byte[] buffer, Int32 offset, Int32 count) in NAudio.Wave\AudioFileReader.cs:line 99 at NAudio.Wave.SampleProviders.WaveToSampleProvider.Read(Single[] buffer, Int32 offset, Int32 count) in NAudio.Wave.SampleProviders\WaveToSampleProvider.cs:line 21 at NAudio.Wave.SampleProviders.VolumeSampleProvider.Read(Single[] buffer, Int32 offset, Int32 sampleCount) in NAudio.Wave.SampleProviders\VolumeSampleProvider.cs:line 19 at NAudio.Wave.SampleProviders.SampleToWaveProvider.Read(Byte[] buffer, Int32 offset, Int32 count) in NAudio.Wave.SampleProviders\SampleToWaveProvider.cs:line 24 at NAudio.Wave.WaveOutBuffer.OnDone() in NAudio.Wave\WaveOutBuffer.cs:line 91 at NAudio.Wave.WaveOut.EnqueueBuffers() in NAudio.Wave\WaveOut.cs:line 122 at NAudio.Wave.WaveOut.Play() in NAudio.Wave\WaveOut.cs:line 111 at DropNormalizer.AudioPlayer.set_PositionInPercentage(Single value) in C:\CSharp\DropNormalizer\DropNormalizer\AudioPlayer\AudioPlayer.cs:line 184 at DropNormalizerGUI_Avalonia.Views.MainWindow.PlayerProgressBar_PointerPressed(Object sender, PointerPressedEventArgs e) in C:\CSharp\DropNormalizer\DropNormalizerGUI_Avalonia\Views\MainWindow.axaml.cs:line 253 at Avalonia.Interactivity.EventRoute.RaiseEventImpl(RoutedEventArgs e) in /_/src/Avalonia.Interactivity/EventRoute.cs:line 152 at Avalonia.Interactivity.EventRoute.RaiseEvent(IInteractive source, RoutedEventArgs e) in /_/src/Avalonia.Interactivity/EventRoute.cs:line 102 at Avalonia.Interactivity.Interactive.RaiseEvent(RoutedEventArgs e) in /_/src/Avalonia.Interactivity/Interactive.cs:line 122 at Avalonia.Input.MouseDevice.MouseDown(IMouseDevice device, UInt64 timestamp, IInputElement root, Point p, PointerPointProperties properties, KeyModifiers inputModifiers) in /_/src/Avalonia.Input/MouseDevice.cs:line 263 at Avalonia.Input.MouseDevice.ProcessRawEvent(RawPointerEventArgs e) in /_/src/Avalonia.Input/MouseDevice.cs:line 157 at Avalonia.Input.InputManager.ProcessInput(RawInputEventArgs e) in /_/src/Avalonia.Input/InputManager.cs:line 36 at Avalonia.Win32.WindowImpl.AppWndProc(IntPtr hWnd, UInt32 msg, IntPtr wParam, IntPtr lParam) in /_/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs:line 526 at Avalonia.Win32.WindowImpl.WndProc(IntPtr hWnd, UInt32 msg, IntPtr wParam, IntPtr lParam) in /_/src/Windows/Avalonia.Win32/WindowImpl.WndProc.cs:line 33 at Avalonia.Win32.Interop.UnmanagedMethods.DispatchMessage(MSG& lpmsg) at Avalonia.Win32.Win32Platform.RunLoop(CancellationToken cancellationToken) in /_/src/Windows/Avalonia.Win32/Win32Platform.cs:line 206 at Avalonia.Threading.Dispatcher.MainLoop(CancellationToken cancellationToken) in /_/src/Avalonia.Base/Threading/Dispatcher.cs:line 66 at Avalonia.Controls.ApplicationLifetimes.ClassicDesktopStyleApplicationLifetime.Start(String[] args) in /_/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs:line 121 at DropNormalizerGUI_Avalonia.Program.Main(String[] args) in C:\CSharp\DropNormalizer\DropNormalizerGUI_Avalonia\Program.cs:line 15
原因分析
异常核心是MediaFoundationReader的COM对象被提前释放,或在多线程环境下被非法访问:
- 播放状态下调用
WaveOut.Stop()时,WaveOut的后台线程可能仍在读取MediaFoundationReader(AudioFileReader内部依赖该组件)的数据,此时直接修改Position会触发Reposition操作,而后台线程可能已释放COM接口引用,导致QueryInterface调用失败。 - 暂停/停止状态下无后台线程读取数据,修改
Position不会触发冲突;但播放中执行Stop()后,后台线程的回调可能仍在执行,此时操作COM对象就会出现类型转换失败。
解决办法
- 等待PlaybackStopped事件触发后再操作:调用
WaveOut.Stop()后,不要立即修改Position,而是等待PlaybackStopped事件确认播放完全停止后,再执行位置修改和恢复播放:private bool _isUserSeeking = false; private void PlayerProgressBar_PointerPressed(object sender, PointerPressedEventArgs e) { _isUserSeeking = true; _waveOut.Stop(); } private void WaveOut_PlaybackStopped(object sender, StoppedEventArgs e) { if (_isUserSeeking) { _isUserSeeking = false; // 计算并设置目标位置 _audioFileReader.Position = CalculateTargetPosition(); _waveOut.Play(); } } - 加锁同步操作:在修改
AudioFileReader.Position和调用WaveOut方法时,用锁锁住相关对象,避免多线程冲突:private readonly object _playerLock = new object(); private void SeekToTarget(long targetPosition) { lock (_playerLock) { _waveOut.Stop(); // 短暂等待后台线程完成读取 Thread.Sleep(50); _audioFileReader.Position = targetPosition; _waveOut.Play(); } } - 替换MediaFoundationReader:如果问题仍存在,可针对WAV文件改用
WaveFileReader,或选择其他非MediaFoundation依赖的读取器,避开COM对象的线程问题。
内容的提问来源于stack exchange,提问作者Michael Švejcar
相关产品推荐
相关产品推荐

