NAudio - 如何在结束后立即播放音频而不会有任何延迟?

Posted

技术标签:

【中文标题】NAudio - 如何在结束后立即播放音频而不会有任何延迟?【英文标题】:NAudio - How do I play an audio right after end without any delay? 【发布时间】:2020-09-02 22:36:22 【问题描述】:

这是我的简单代码

var reader = new Mp3FileReader("Audio/Battle Wild Intro.mp3");
var reader2 = new Mp3FileReader("Audio/Battle Wild Loop.mp3");
var waveOut = new DirectSoundOut(); // or WaveOutEvent()
var waveOut2 = new DirectSoundOut();
waveOut.Init(reader);
waveOut2.Init(reader2);
waveOut.Play();
while (waveOut.PlaybackState != PlaybackState.Stopped) ;
waveOut2.Play();

它基本上是一个线程?播放音频直到结束,然后播放另一个...它正在工作,但是在下一个播放之前有 0.1 秒的延迟。

我想做的是播放一段战斗前奏,然后在前奏完成后,开始音乐循环。基本上就像神奇宝贝:3(例如:https://www.youtube.com/watch?v=aQrmCB4ZGGo)

我对线程有点陌生,非常感谢您的帮助! ^^

另外,如果我可以在 Midi 中使用自定义 SF 而不是 mp3/wav,那就太棒了:D

【问题讨论】:

这是winforms应用还是控制台应用? 如果您尝试在控制台应用程序中执行此操作,我建议您将音频文件位置放在一个列表中并从该列表中播放。就像在这个例子中:gist.github.com/osbornm/4431513 所以我尝试了您的解决方案,但延迟仍然相同:c 也许通过一段时间找出播放头何时接近第一个文件的结尾,然后在适当的时间开始下一个文件? 【参考方案1】:

这可能不是最好的解决方案,但我会做的是将播放列表连接到一个 mp3 文件中,然后播放该 mp3 文件。当一个接一个地播放一首歌曲时,会有一个毫秒的暂停,因为下一首歌曲正在加载。

首先,创建一个名为LoopStream.cs 的新类文件并将此代码放入其中:

public class LoopStream : WaveStream

    WaveStream sourceStream;

    /// <summary>
    /// Creates a new Loop stream
    /// </summary>
    /// <param name="sourceStream">The stream to read from. Note: the Read method of this stream should return 0 when it reaches the end
    /// or else we will not loop to the start again.</param>
    public LoopStream(WaveStream sourceStream)
    
        this.sourceStream = sourceStream;
        this.EnableLooping = true;
    

    /// <summary>
    /// Use this to turn looping on or off
    /// </summary>
    public bool EnableLooping  get; set; 

    /// <summary>
    /// Return source stream's wave format
    /// </summary>
    public override WaveFormat WaveFormat
    
        get  return sourceStream.WaveFormat; 
    

    /// <summary>
    /// LoopStream simply returns
    /// </summary>
    public override long Length
    
        get  return sourceStream.Length; 
    

    /// <summary>
    /// LoopStream simply passes on positioning to source stream
    /// </summary>
    public override long Position
    
        get  return sourceStream.Position; 
        set  sourceStream.Position = value; 
    

    public override int Read(byte[] buffer, int offset, int count)
    
        int totalBytesRead = 0;

        while (totalBytesRead < count)
        
            int bytesRead = sourceStream.Read(buffer, offset + totalBytesRead, count - totalBytesRead);
            if (bytesRead == 0)
            
                if (sourceStream.Position == 0 || !EnableLooping)
                
                    // something wrong with the source stream
                    break;
                
                // loop
                sourceStream.Position = 0;
            
            totalBytesRead += bytesRead;
        
        return totalBytesRead;
    

那么这是你的主Program.cs,循环播放mp3文件,我们将使用LoopStream类循环播放mp3文件:

    static void Main(string[] args)
    
        var playlist = new List<string> @"c:\Users\User\Music\sound1.mp3",
            @"c:\Users\User\Music\sound2.mp3" ;

        var mp3OutputFile = File.Create(@"c:\Users\User\Music\soundnew3.mp3");

        Concatenate(playlist, mp3OutputFile);
        mp3OutputFile.Close();

        var combinedList = new List<string>  @"c:\Users\User\Music\soundnew3.mp3" ;

        var playr = new playr(combinedList);
        playr.PlaySong();

        Console.WriteLine("Playing your songs..");
        Console.ReadLine();
    

    public static void Concatenate(List<string> mp3FileNames, Stream output)
    
        foreach (string file in mp3FileNames)
        
            Mp3FileReader reader = new Mp3FileReader(file);
            if ((output.Position == 0) && (reader.Id3v2Tag != null))
            
                output.Write(reader.Id3v2Tag.RawData,
                             0,
                             reader.Id3v2Tag.RawData.Length);
            
            Mp3Frame frame;
            while ((frame = reader.ReadNextFrame()) != null)
            
                output.Write(frame.RawData, 0, frame.RawData.Length);
            
        
            

    public class playr
    
        private Queue<string> playlist;
        private IWavePlayer player;
        private WaveStream fileWaveStream;

        public playr(List<string> startingPlaylist)
        
            playlist = new Queue<string>(startingPlaylist);
        

        public void PlaySong()
        
            if (fileWaveStream != null)
            
                fileWaveStream.Dispose();
            

            if (playlist.Count < 1)
            
                return;
            

            if (player != null && player.PlaybackState != PlaybackState.Stopped)
            
                player.Stop();
            

            if (player != null)
            
                player.Dispose();
                player = null;
            


            player = new WaveOutEvent();

            fileWaveStream = new AudioFileReader(playlist.Dequeue());
            LoopStream loop = new LoopStream(fileWaveStream);
            player.Init(loop);                
            player.PlaybackStopped += (sender, evn) =>  PlaySong(); ;
            player.Play();

        
    

如果你想合并 WAV 文件,你可以这样做:

public static void Concatenate(List<string> sourceFiles)
    
        byte[] buffer = new byte[1024];
        WaveFileWriter waveFileWriter = null;
        string outputFile = @"c:\Users\User\Music\soundnew3.wav";

        try
        
            foreach (string sourceFile in sourceFiles)
            
                using (WaveFileReader reader = new WaveFileReader(sourceFile))
                
                    if (waveFileWriter == null)
                    
                        // first time in create new Writer
                        waveFileWriter = new WaveFileWriter(outputFile, reader.WaveFormat);
                    
                    else
                    
                        if (!reader.WaveFormat.Equals(waveFileWriter.WaveFormat))
                        
                            throw new InvalidOperationException("Can't concatenate WAV Files that don't share the same format");
                        
                    

                    int read;
                    while ((read = reader.Read(buffer, 0, buffer.Length)) > 0)
                    
                        waveFileWriter.WriteData(buffer, 0, read);
                    
                
            
        
        finally
        
            if (waveFileWriter != null)
            
                waveFileWriter.Dispose();
            
        

    

【讨论】:

合并音频是个好主意:3 但它对我的问题没有帮助,因为我想要一个介绍和一个循环。介绍结束后,循环播放另一个 .mp3/wav/ogg 我希望我能拥有像“player.RepositionTime(int milliseconds);”这样简单的东西 我更新为 mp3 文件循环。 LoopStream 构造函数类确实允许 List,但就测试而言,它只会读取第一个文件并循环该文件。这是我将给出的解决方案,但也许其他人可以加入并提供更好的解决方案。 所以,我尝试了您的解决方案,但两个音频之间仍然存在这种烦人的 0.05 秒故障声音 >~youtu.be/70NV_S8eslE 我再次更新了代码以修复音频故障。问题是要合并一个 mp3 文件,有一种特定的方法可以将它与二进制文件合并和合并,这会导致此故障。 没有故障声音 :3 但它循环组合文件 :c 就像应该只播放一次的介绍会再次循环

以上是关于NAudio - 如何在结束后立即播放音频而不会有任何延迟?的主要内容,如果未能解决你的问题,请参考以下文章

AVPlayer 不会在 iOS 10 上立即播放视频,而仅播放音频

是否可以使用 NAudio 从短 [] 而不是字节 [] 播放音频?

音频文件以错误的速度播放

无法播放用 naudio 录制的音频流

来自流的NAudio?播放音频

NAudio - 从 RTP 数据包播放音频有效负载