如何在UWP中将IntPtr写入文件?MP3解码器Bug排查PCM生成代码咨询
我来帮你搞定这两个问题~
UWP中IntPtr写入文件及生成PCM文件的解决方案
一、UWP平台将IntPtr写入文件的方法
IntPtr本身只是一个内存地址,要写入文件的核心是把它指向的内存块数据复制到托管数组,再通过UWP的文件API写入。具体步骤和示例如下:
- 先明确IntPtr指向的内存区域大小(比如从BASS获取的PCM数据字节长度);
- 用
Marshal.Copy把非托管内存的数据复制到托管字节数组; - 借助
StorageFile相关API完成文件写入。
示例代码:
using System.Runtime.InteropServices; using Windows.Storage; using Windows.Storage.Pickers; using Windows.Storage.Streams; // 传入IntPtr和对应内存字节长度,完成写入 private async Task WriteIntPtrToFile(IntPtr dataPtr, int dataLength) { // 创建托管数组存储数据 byte[] dataBytes = new byte[dataLength]; Marshal.Copy(dataPtr, dataBytes, 0, dataLength); // 选择保存的PCM文件 var savePicker = new FileSavePicker(); savePicker.SuggestedStartLocation = PickerLocationId.DocumentsLibrary; savePicker.FileTypeChoices.Add("PCM 文件", new List<string>() { ".pcm" }); savePicker.SuggestedFileName = "output_pcm"; StorageFile file = await savePicker.PickSaveFileAsync(); if (file != null) { using (var stream = await file.OpenStreamForWriteAsync()) { await stream.WriteAsync(dataBytes, 0, dataBytes.Length); await stream.FlushAsync(); } } }
二、结合BASS库生成PCM文件的优化方案
看你给出的代码片段,已经用到了BASS的DSP回调捕获PCM数据,这里需要完善回调逻辑,同时注意UWP的线程安全问题(DSP回调运行在BASS后台线程,不能直接在回调里执行UI线程的文件操作)。
完整优化代码示例:
using System; using System.IO; using System.Runtime.InteropServices; using System.Threading.Tasks; using Windows.Storage; using Windows.Storage.Pickers; using Un4seen.Bass; private IntPtr _handle; private Stream _pcmFileStream; private readonly object _streamLock = new object(); // 线程锁保证写入安全 private async Task NewMethod() { var sourceFile = await SelectPlaybackFile(); if (sourceFile == null) return; // 先选择要保存的PCM文件(UI线程操作) var savePicker = new FileSavePicker(); savePicker.SuggestedStartLocation = PickerLocationId.DocumentsLibrary; savePicker.FileTypeChoices.Add("PCM 文件", new List<string>() { ".pcm" }); savePicker.SuggestedFileName = "decoded_mp3_pcm"; StorageFile pcmFile = await savePicker.PickSaveFileAsync(); if (pcmFile == null) return; // 打开文件流 _pcmFileStream = await pcmFile.OpenStreamForWriteAsync(); await Task.Run(() => { // 创建MP3流,指定浮点型PCM输出 _handle = Bass.BASS_StreamCreateFile(sourceFile.Path, 0, 0, BASSFlag.BASS_STREAM_AUTOFREE | BASSFlag.BASS_SAMPLE_FLOAT); if (_handle == IntPtr.Zero) { // 可选:添加错误日志,比如输出Bass.BASS_ErrorGetCode() return; } // 设置DSP回调捕获PCM var dspProc = new DSPPROC(DspCaptureCallback); int dspHandle = Bass.BASS_ChannelSetDSP(_handle, dspProc, IntPtr.Zero, 0); // 开始播放触发DSP回调 Bass.BASS_ChannelPlay(_handle, false); // 等待播放结束 while (Bass.BASS_ChannelIsActive(_handle) == BASSActive.BASS_ACTIVE_PLAYING) { Task.Delay(100).Wait(); } // 清理资源 Bass.BASS_ChannelRemoveDSP(_handle, dspHandle); lock (_streamLock) { _pcmFileStream?.Flush(); _pcmFileStream?.Close(); _pcmFileStream = null; } }); } // DSP回调:捕获并写入PCM数据 private void DspCaptureCallback(IntPtr handle, int channel, IntPtr buffer, int length, IntPtr user) { if (_pcmFileStream == null) return; // 因为用了BASS_SAMPLE_FLOAT,buffer是float数组指针,转成float数组处理 float[] pcmFloatData = new float[length / sizeof(float)]; Marshal.Copy(buffer, pcmFloatData, 0, pcmFloatData.Length); // 转成字节数组准备写入 byte[] dataBytes = new byte[length]; Buffer.BlockCopy(pcmFloatData, 0, dataBytes, 0, length); // 线程安全写入文件 lock (_streamLock) { _pcmFileStream?.Write(dataBytes, 0, dataBytes.Length); } } // 选择要解码的MP3文件 private async Task<StorageFile> SelectPlaybackFile() { var openPicker = new FileOpenPicker(); openPicker.SuggestedStartLocation = PickerLocationId.MusicLibrary; openPicker.FileTypeChoices.Add("MP3 文件", new List<string>() { ".mp3" }); return await openPicker.PickSingleFileAsync(); }
关键注意点:
- 因为你用了
BASS_SAMPLE_FLOAT,生成的是32位浮点型PCM,开发者导入时需要选择对应格式(比如用Audacity导入RAW文件,设置为32位浮点、匹配原MP3的采样率和声道数); - 必须加线程锁,DSP回调在后台线程执行,文件写入不是线程安全操作,避免数据错乱;
- 播放结束后要及时清理DSP和文件流,防止资源泄漏。
内容的提问来源于stack exchange,提问作者jason_wun
相关产品推荐
相关产品推荐

