为什么SoundEffect.FromStream()在读取“有效”WAV文件时会抛出InvalidOperationException?

时间:2015-04-09 21:54:08

标签: c# xna

我正在尝试将声音效果加载到我的游戏中(C#/ XNA 4.0,Win8.1上的Visual Studio 2013)。该游戏是现有MMORPG客户端的“克隆”,应与现有目录结构兼容 - 这意味着所有声音效果都存储在游戏工作目录中名为“sfx”的目录中。

我在尝试完成此任务时遇到了SoundEffect.FromStream。它适用于我拥有的大多数文件,但有些文件会抛出InvalidOperationException。堆栈跟踪显示错误位于XNA DLL内的内部Wav文件构造函数中。

我很清楚SoundEffect.FromStream方法的restrictions on wav files:8或16位,单声道或立体声,PCM,采样率介于8kHz和48kHz之间。我遇到的问题是,据我所知,无法加载的特定Wav文件满足所有这些要求。

以下是我加载wav文件的方法:

//SoundInfo is a wrapper around SoundEffect
//  implementation details are not relevant to my question
private readonly List<SoundInfo> m_guitarSounds;
private readonly List<SoundInfo> m_harpSounds;
private readonly List<SoundInfo> m_sounds;

//... additional initialization

foreach (string sfx in soundFiles)
{
    using (FileStream fs = new FileStream(sfx, FileMode.Open,
            FileAccess.Read, FileShare.Read))
    {
        int samples = getSamplesPerSecond(sfx);

        SoundEffect nextEffect;
        try
        {
            nextEffect = SoundEffect.FromStream(fs);
        }
        catch (InvalidOperationException)
        {
            //I got this idea from another StackOverflow answer
            nextEffect = new SoundEffect(File.ReadAllBytes(sfx), samples,
                                          AudioChannels.Mono);
        }

        if (sfx.ToLower().Contains("gui"))
            m_guitarSounds.Add(new SoundInfo(nextEffect));
        else if (sfx.ToLower().Contains("har"))
            m_harpSounds.Add(new SoundInfo(nextEffect));
        else
            m_sounds.Add(new SoundInfo(nextEffect));
    }
}

失败时,效果由从文件读取的数据的字节数组构成,并强制为Mono。这似乎适用于InvalidOperationException引起的所有错误。在这些情况下,声音通常会非常扭曲。

以下是getSamplesPerSecond的代码(在上面的代码中引用):

private int getSamplesPerSecond(string filename)
{
    //this method was in place for debugging purposes but is used to get
    //  the samplesPerSec for the audio files that can't be
    //  loaded using FromStream
    byte[] wav = File.ReadAllBytes(filename);

    // Get past all the other sub chunks to get to the data subchunk:
    int pos = 12;   // First Subchunk ID from 12 to 16

    //get fmt chunk
    while (!((char)wav[pos] == 'f' && (char)wav[pos + 1] == 'm' && 
            (char)wav[pos + 2] == 't' && (char)wav[pos + 3] == ' '))
    {
        pos += 4;
        int chunkSize = wav[pos] + wav[pos + 1] * 256 + 
                        wav[pos + 2] * 65536 + wav[pos + 3] * 16777216;
        pos += 4 + chunkSize;
    }
    pos += 8;

    int format = wav[pos] + wav[pos + 1]*256;
    pos += 2;
    int channels = wav[pos] + wav[pos + 1]*256;
    pos += 2;
    int samplesPerSec = wav[pos] + wav[pos + 1]*256 + wav[pos + 2]*65536
                        + wav[pos + 3]*16777216;
    pos += 4;
    int avgBytesPerSec = wav[pos] + wav[pos + 1]*256 + wav[pos + 2]*65536
                        + wav[pos + 3] * 16777216;
    pos += 4;
    int blockAlign = wav[pos] + wav[pos + 1]*256;
    pos += 2;

    int bitsPerSample = 0;
    if (format == 1)
    {
        bitsPerSample = wav[pos] + wav[pos + 1]*256;
        //pos += 2; //necessary if we want to look at other chunks
    }

    return samplesPerSec;
}

抛出一些调试日志语句,我得到了这些文件内容。这是唯一一个失败的部分,总共只有9个失败(滚动到右边!):

sfx\sfx001.wav: Format=1, Channels=1, SamplesPerSec=22050, AvgBytesPerSec=44100, BlockAlign=2, BitsPerSample=16  
sfx\sfx002.wav: Format=1, Channels=1, SamplesPerSec=22050, AvgBytesPerSec=44100, BlockAlign=2, BitsPerSample=16    [ FAILED ]
sfx\sfx003.wav: Format=1, Channels=1, SamplesPerSec=22050, AvgBytesPerSec=44100, BlockAlign=2, BitsPerSample=16    [ FAILED ]
sfx\sfx004.wav: Format=1, Channels=1, SamplesPerSec=22050, AvgBytesPerSec=22050, BlockAlign=1, BitsPerSample=8  
sfx\sfx005.wav: Format=1, Channels=1, SamplesPerSec=22050, AvgBytesPerSec=44100, BlockAlign=2, BitsPerSample=16    [ FAILED ]
sfx\sfx006.wav: Format=1, Channels=1, SamplesPerSec=22050, AvgBytesPerSec=44100, BlockAlign=2, BitsPerSample=16    [ FAILED ]
sfx\sfx007.wav: Format=1, Channels=1, SamplesPerSec=22050, AvgBytesPerSec=44100, BlockAlign=2, BitsPerSample=16  
sfx\sfx008.wav: Format=1, Channels=1, SamplesPerSec=22050, AvgBytesPerSec=22050, BlockAlign=1, BitsPerSample=8  
sfx\sfx009.wav: Format=1, Channels=1, SamplesPerSec=22050, AvgBytesPerSec=44100, BlockAlign=2, BitsPerSample=16  
sfx\sfx010.wav: Format=1, Channels=2, SamplesPerSec=22050, AvgBytesPerSec=88200, BlockAlign=4, BitsPerSample=16    [ FAILED ]
sfx\sfx011.wav: Format=1, Channels=1, SamplesPerSec=22050, AvgBytesPerSec=44100, BlockAlign=2, BitsPerSample=16  
sfx\sfx012.wav: Format=1, Channels=1, SamplesPerSec=22050, AvgBytesPerSec=44100, BlockAlign=2, BitsPerSample=16    [ FAILED ]
sfx\sfx013.wav: Format=1, Channels=1, SamplesPerSec=22050, AvgBytesPerSec=44100, BlockAlign=2, BitsPerSample=16  
sfx\sfx014.wav: Format=1, Channels=1, SamplesPerSec=22050, AvgBytesPerSec=22050, BlockAlign=1, BitsPerSample=8  
sfx\sfx015.wav: Format=1, Channels=1, SamplesPerSec=22050, AvgBytesPerSec=44100, BlockAlign=2, BitsPerSample=16  
sfx\sfx016.wav: Format=1, Channels=1, SamplesPerSec=22050, AvgBytesPerSec=44100, BlockAlign=2, BitsPerSample=16    [ FAILED ]
sfx\sfx017.wav: Format=1, Channels=1, SamplesPerSec=22050, AvgBytesPerSec=44100, BlockAlign=2, BitsPerSample=16    [ FAILED ]
sfx\sfx018.wav: Format=1, Channels=1, SamplesPerSec=22050, AvgBytesPerSec=44100, BlockAlign=2, BitsPerSample=16    [ FAILED ]
sfx\sfx019.wav: Format=1, Channels=1, SamplesPerSec=22050, AvgBytesPerSec=44100, BlockAlign=2, BitsPerSample=16  

在读取WAV文件头('fmt'块)后,直接从getSamplesPerSecond方法打印打印到上述日志的值。我使用http://www.neurophys.wisc.edu/auditory/riff-format.txt作为参考来确定WAV文件中数据的含义。

根据我的日志,每个文件都是PCM,8或16位,22050Hz采样率,以及Mono或Stereo(基于通道数)。所有文件在原始游戏客户端中打开都很好,并且在Audacity和Windows Media Player中工作,因此我可以排除文件本身的问题。

我在这里缺少什么?如果它与我的源音频文件不兼容,是否有另一种方法可以加载它们并播放它们,而不使用内容管道?

1 个答案:

答案 0 :(得分:1)

由于文件格式错误,无法加载的文件每个都会抛出InvalidOperationException,但这并不是因为任何WAV格式限制。

文件中出现错误,其中文件的字节4,5,6和7中报告的长度与RIFF数据的实际长度不同。

Windows Media Player和Audacity正在补偿此错误,但SoundEffect.FromStream()正在检测它并抛出异常。

这是我用来解决问题的一种工作方法:

private void _correctTheFileLength(string filename)
{
    byte[] wav = File.ReadAllBytes(filename);

    string riff = Encoding.ASCII.GetString(wav.SubArray(0, 4));
    if (riff != "RIFF" || wav.Length < 8) //check for RIFF tag and length
        return;

    int reportedLength = wav[4] + wav[5]*256 + wav[6]*65536 + wav[7]*16777216;
    int actualLength = wav.Length - 8;
    if (reportedLength != actualLength)
    {
        wav[4] = (byte) (actualLength & 0xFF);
        wav[5] = (byte) ((actualLength >> 8) & 0xFF);
        wav[6] = (byte) ((actualLength >> 16) & 0xFF);
        wav[7] = (byte) ((actualLength >> 24) & 0xFF);
        File.WriteAllBytes(filename, wav);
    }
}