C# 音频分离(MP3伴奏)

编程语言:C#

库:NAudio

NAudio 是一个开源的 .NET 音频处理库,它为开发者提供了丰富的功能,能在 Windows 平台上方便地进行音频的录制、播放、处理等操作。以下是关于 NAudio 库的详细介绍:

主要特性

  1. 多格式支持:支持多种常见的音频文件格式,如 WAV、MP3、OGG 等。这意味着你可以使用 NAudio 来播放或处理这些格式的音频文件。
  2. 音频录制:能够从系统的音频输入设备(如麦克风)录制音频,并保存为指定格式的文件。
  3. 音频播放:可以播放本地音频文件,也可以通过网络流式播放音频。同时,还支持对播放进行控制,如暂停、继续、停止等。
  4. 音频处理:提供了一些音频处理功能,如音量控制、音调调整、音频混合等。
  5. 低延迟:对于需要实时音频处理的应用场景,NAudio 能够保证较低的延迟。

常见使用场景

  1. 音频播放器开发:可以使用 NAudio 快速开发一个简单的音频播放器,支持多种音频格式的播放。

  2. 语音录制应用:开发语音备忘录、会议录音等应用程序。

  3. 音频处理工具:实现音频的剪辑、混音、格式转换等功能。

    //核心代码
    private void ProcessingWorker_DoWork(object sender, DoWorkEventArgs e)
    {
    processedSamples = 0;
    var parameters = e.Argument as ProcessingParameters;
    if (parameters == null) return;

    复制代码
             try
             {
                 using (var reader = CreateAudioReader(parameters.InputPath))
                 {
                     var sampleProvider = reader.ToSampleProvider();
                     var format = sampleProvider.WaveFormat;
                     sampleRate = format.SampleRate; // 使用文件实际采样率
                     var channelCount = format.Channels;
                     var outputPaths = GetOutputPaths(parameters.InputPath, parameters.OutputDirectory);
    
                     // 优化缓冲区:4倍采样率缓冲区减少I/O次数
                     int bufferSize = format.SampleRate * channelCount * 4;
                     var buffer = new float[bufferSize];
    
                     using (var vocalsWriter = new WaveFileWriter(outputPaths.Item1, format))
                     using (var accompWriter = new WaveFileWriter(outputPaths.Item2, format))
                     {
                         long totalSamples = reader.Length / (format.BitsPerSample / 8 * channelCount);
                         int samplesRead;
                        // var buffer = new float[bufferSize];
                         var newBuffer = new float[bufferSize];
                         while ((samplesRead = sampleProvider.Read(buffer, 0, bufferSize)) > 0)
                         {
                             if (processingWorker.CancellationPending)
                             {
                                 e.Cancel = true;
                                 return;
                             }
    
                             // 按声道对处理(支持单/双声道)
                             for (int i = 0; i < samplesRead; i += channelCount)
                             {
                                 float left = buffer[i];
                                 float right = channelCount == 2 ? (i + 1 < samplesRead ? buffer[i + 1] : 0) : left;
    
                                 if (parameters.SeparationMode == SeparationMode.KaraokeMode)
                                 {
                                     // 卡拉OK模式核心处理:左右声道相减 + 带通滤波
                                     float karaoke = left - right;
                                     float filtered = ApplyBandPassFilter(karaoke, channelCount == 1 ? leftFilterState : (i % 2 == 0 ? leftFilterState : rightFilterState));
                                     left = right = filtered * gain; // 双声道保持一致
                                 }
    
                                 buffer[i] = left;
                                 if (channelCount == 2 && i + 1 < samplesRead)
                                 {
                                     buffer[i + 1] = right;
                                 }
                             }
    
                             if (parameters.SeparationMode == SeparationMode.KaraokeMode)
                             {
                                 // 对伴奏轨道应用低音增强(仅处理双声道)
                                 for (int i = 0; i < samplesRead; i += channelCount)
                                 {
                                     float left = buffer[i];
                                     float right = channelCount == 2 ? buffer[i + 1] : left;
    
                                     // 应用低音增强
                                     left = ApplyBassBoost(left, sampleRate);
                                     right = ApplyBassBoost(right, sampleRate);
    
                                     buffer[i] = left;
                                     if (channelCount == 2) buffer[i + 1] = right;
                                 }
                             }
    
                             WriteSeparatedTracks(buffer, samplesRead, vocalsWriter, accompWriter, channelCount, parameters.SeparationMode);
                             processedSamples += samplesRead;
    
                             //processedSamples += samplesRead;
                             processingWorker.ReportProgress((int)(processedSamples * 100 / totalSamples));
                         }
                     }
                 }
             }
             catch (Exception ex)
             {
                 e.Result = ex;
                 MessageBox.Show($"An error occurred during the processing: {ex.Message}",this .Text, MessageBoxButtons.OK, MessageBoxIcon.Error);
             }
         }
    
    
         private float ApplyBandPassFilter(float input, FilterState state)
         {
             // 带通滤波器系数计算(IIR滤波器,改进型Sallen-Key结构)
             double w0 = 2 * Math.PI * ((lowerCutoff + upperCutoff) / 2) / sampleRate;
             double bw = upperCutoff - lowerCutoff;
             double q = (lowerCutoff + upperCutoff) / (2 * bw); // 品质因数
             double alpha = Math.Sin(w0) / (2 * q);
    
             // 滤波器系数
             double a0 = alpha;
             double a1 = 0;
             double a2 = -alpha;
             double b0 = 1 + alpha;
             double b1 = -2 * Math.Cos(w0);
             double b2 = 1 - alpha;
    
             // 移位寄存器更新
             state.x[2] = state.x[1];
             state.x[1] = state.x[0];
             state.x[0] = input;
             state.y[2] = state.y[1];
             state.y[1] = state.y[0];
    
             // 差分方程计算
             state.y[0] = (a0 * state.x[0] + a1 * state.x[1] + a2 * state.x[2]
                         - b1 * state.y[1] - b2 * state.y[2]) / b0;
    
             return (float)state.y[0];
         }
         private void ProcessSample(float[] buffer, int n, ProcessingParameters parameters, int channelCount)
         {
             if (parameters.SeparationMode != SeparationMode.KaraokeMode) return;
    
             float left = buffer[n];
             float right = 0;
             if (channelCount == 2 && n + 1 < buffer.Length)
             {
                 right = buffer[n + 1];
             }
    
             // 基础消人声算法
             float karaoke = left - right;
    
             // 优化滤波器:直接计算,避免类实例开销
             double w0 = 2 * Math.PI * ((lowerCutoff + upperCutoff) / 2) / sampleRate;
             double alpha = Math.Sin(w0) * Math.Sinh(Math.Log(2) / 2 * (upperCutoff - lowerCutoff) * w0 / Math.Sin(w0));
             double a0 = 1 / (1 + alpha);
             double a1 = -2 * Math.Cos(w0) * a0;
             double a2 = (1 - alpha) * a0;
    
             // 直接计算滤波后的样本(示例,可替换为更高效的滤波器实现)
             double filtered = a0 * karaoke + a1 * left + a2 * (n > 0 ? buffer[n - 1] : 0);
             buffer[n] = (float)(filtered * gain);
    
             if (channelCount == 2 && n + 1 < buffer.Length)
             {
                 buffer[n + 1] = buffer[n]; // 保持双声道一致
             }
    
             // 避免输出为零
             if (Math.Abs(buffer[n]) < 0.00001f)
             {
                 buffer[n] = 0.00001f;
             }
             if (channelCount == 2 && n + 1 < buffer.Length && Math.Abs(buffer[n + 1]) < 0.00001f)
             {
                 buffer[n + 1] = 0.00001f;
             }
         }
    
    
         private void WriteSeparatedTracks(float[] buffer, int samplesRead,
            WaveFileWriter vocalsWriter, WaveFileWriter accompWriter,
            int channelCount, SeparationMode mode)
         {
             try
             {
                 if (mode == SeparationMode.VocalIsolation)
                 {
                     // 人声隔离模式:写入人声轨道(需要其他算法,此处简化)
                     vocalsWriter.WriteSamples(buffer, 0, samplesRead);
                     accompWriter.WriteSamples(buffer, 0, samplesRead); // 示例写法,需根据实际算法调整
                 }
                 else if (mode == SeparationMode.KaraokeMode)
                 {
                     // 卡拉OK模式:伴奏轨道为处理后信号,人声轨道为原始信号相减结果(或静音)
                     accompWriter.WriteSamples(buffer, 0, samplesRead);
                     // 人声轨道(可选:写入差值信号或静音)
                     for (int i = 0; i < samplesRead; i += 2)  //i ++
                     {
                        // buffer[i] = 0; // 消除人声后的人声轨道设为静音(可选逻辑)
                         //=========================================================================
                         float left = buffer[i];
                         float right = buffer[i + 1];
                         float difference = (left - right) * differenceGain;
    
                         buffer[i] = difference;
                         buffer[i + 1] = difference;
                     }
                     vocalsWriter.WriteSamples(buffer, 0, samplesRead);
                 }
             }
             catch (Exception ex)
             {
                 MessageBox.Show($"Error occurred while writing audio file: {ex.Message}",this.Text, MessageBoxButtons.OK, MessageBoxIcon.Error);
             }
         }
         private void WriteSeparatedTracks(float[] buffer, int samplesRead,
             WaveFileWriter vocalsWriter, WaveFileWriter accompWriter, int channelCount)
         {
             try
             {
                 // 直接写入缓冲区,避免额外内存分配
                 vocalsWriter.WriteSamples(buffer, 0, samplesRead);
                 accompWriter.WriteSamples(buffer, 0, samplesRead);
             }
             catch (Exception ex)
             {
                 MessageBox.Show($"写入音频文件时出现错误: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
             }
         }
         #endregion
    
         // 实现缺失的事件处理方法
         //private void ProcessingWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
         //{
         //    int progressValue = Math.Max(0, Math.Min(100, e.ProgressPercentage));
         //    if (progressBar.InvokeRequired)
         //    {
         //        progressBar.Invoke(new Action(() => progressBar.Value = progressValue));
         //    }
         //    else
         //    {
         //        progressBar.Value = progressValue;
         //    }
    
         //    if (lblStatus.InvokeRequired)
         //    {
         //        lblStatus.Invoke(new Action(() => lblStatus.Text = $"处理中... {progressValue}%"));
         //    }
         //    else
         //    {
         //        lblStatus.Text = $"处理中... {progressValue}%";
         //    }
         //}
         private float ApplyBassBoost(float input, double sampleRate)
         {
             // 低频搁架滤波器参数(固定截止频率100Hz,可添加TrackBar调节截止频率)
             double w0 = 2 * Math.PI * bassCutoff / sampleRate;
             double q = 0.707; // 临界阻尼系数
             double gainDb = 20 * Math.Log10(bassGain); // 转换为分贝
    
             // 计算滤波器系数(正向增益)
             double a0, a1, a2, b0, b1, b2;
             double V = Math.Pow(10, gainDb / 20);
             double alpha = Math.Sin(w0) / (2 * q);
    
             if (gainDb > 0) // 增益模式(增强低音)
             {
                 b0 = 1 + alpha * V;
                 b1 = -2 * Math.Cos(w0);
                 b2 = 1 - alpha * V;
                 a0 = (1 + alpha / V) / b0;
                 a1 = (-2 * Math.Cos(w0)) / b0;
                 a2 = (1 - alpha / V) / b0;
             }
             else // 衰减模式(可省略,当前需求仅增强)
             {
                 // 衰减系数计算(略,当前功能暂不考虑)
                 a0 = a1 = a2 = b0 = b1 = b2 = 0;
             }
    
             // 示例:简单低音增强(直接提升低频信号,临时实现用于快速验证)
             // 实际应用建议使用IIR滤波器或FFT频域处理
             if (input < 0) return (float)(input * bassGain);
             return input;
         }
相关推荐
MaYuKang2 分钟前
「ES数据迁移可视化工具(Python实现)」支持7.x索引数据互传
大数据·数据库·python·mysql·elasticsearch
18538162800余。9 分钟前
碰一碰发视频源码,碰一碰发视频OEM
音视频
Bardb14 分钟前
04-stm32的标准外设库
stm32·c#
用户62799471826228 分钟前
南大通用GBase 8a数据库to_number关联报错问题处理方法
数据库
风,停下40 分钟前
C#基于Sunnyui框架和MVC模式实现用户登录管理
设计模式·c#·mvc
钢铁男儿40 分钟前
C# 实战_RichTextBox选中某一行条目高亮,离开恢复
开发语言·c#
李小白6643 分钟前
数据库进阶之MySQL 程序
数据库·mysql
小博测试成长之路1 小时前
MongoDB技巧:快速找出重复字段记录
数据库·mongodb
Yushan Bai1 小时前
ORACLE DATAGUARD遇到GAP增量恢复方式修复RAC环境备机的实践
数据库·oracle
搬砖天才、2 小时前
日常记录-redis主从复制(master-slave)+ Sentinel 哨兵(高可用)
数据库·redis·sentinel