当前位置:首页 > 开发教程 > .net教程 >

C#开发MP3录音工具

时间:2016-05-23 09:45 来源:互联网 作者:源码搜藏 收藏

介绍 下面是从MP3格式的声音卡记录的应用程序。 该实用程序增加了一个用户界面的控制台应用程序, mp3_stream.exe, 所描述 rtybase 一个在 文章 其中前一阵子出现在这里CodeProject上。 它还增加了新的功能,如设置录音时间长度自动结束一个漫长的录音片段

C#开发MP3录音工具

介绍

下面是从MP3格式的声音卡记录的应用程序。该实用程序增加了一个用户界面的控制台应用程序, mp3_stream.exe, 所描述rtybase一个在文章其中前一阵子出现在这里CodeProject上。它还增加了新的功能,如设置录音时间长度自动结束一个漫长的录音片段。其他功能包括节省退出当前用户设置,并在下次的工具启动重装这些设置。

从技术上看,文章介绍了如何在控制台的第三方应用程序可以通过产卵成一个单独的进程,并通过命令行参数和回调方法与之通信进行控制,同时使利用异步/等待 继续用户界面流畅,反应灵敏。

背景

我已经把这个工具一起很快为我用来驱动一些DOS批处理文件替换mp3_stream.exe用。然后我很快意识到,这样虽然电脑被搁置一旁录音可以结束它会增加一个持续时间计时器非常有用。我发现这记录更长的会议,其中一些可超过六小时之久的时候特别有用。

使用工具

该对话框示出了代表在命令行参数的设置mp3_stream.exe,如音量,比特率(以每秒千比特),例如128的设备和线路名称显示在下拉列表框。该记录的总持续时间可以任意指定,该记录是一个文件名 保存为可浏览使用标准的Windows文件另存为对话框。所有的配置设置都保存在逼抢的效用,并启动下一次会自动装载。

Windows 7中,8,8.1,和10注可用性

自从我第一次发表文章,越来越多的人开始后的Windows XP操作系统,并使用它,自然。使用更高版本的Windows时遇到的问题是,操作系统都禁止,默认情况下隐藏了立体声混音装置,即使它是可以通过声卡的驱动程序。克服这个问题相当简单,如下所示:

  1. 在系统托盘中的扬声器图标上单击右键,选择录音设备(你也可以打开通过Windows控制面板此对话框)
  2. 在对话框中单击鼠标右键,弹出快捷菜单,并选择显示禁用设备
  3. 同样,在对话框里单击鼠标右键,弹出右键菜单,这次选择显示断开的设备
  4. 以上两个步骤,将立体声混音可见,在其上单击鼠标右键,选择启用
  5. 按关闭对话框OK按钮

现在,下启动该实用程序时,您将能够选择立体声混音器和主音量线。

还值得一提的是,声卡驱动,如制造商(如:瑞昱)发出可能并不总是默认安装一个新购买的机器上。在许多情况下,只有标准的通用Windows驱动程序安装。这可以很容易地通过下载制造商的驱动和在机器上安装它来克服。然后可以进行步骤1到5的上方,并享受从声卡记录的好处。

如何代码工作

mp3_stream.exe本身是一个C ++应用程序,它使用了LAME开放源代码库进行MP3编码。通信使用.NET应用程序瘸子,人们可以去了解这几种方式。例如,一个可以托管C ++薄薄的界面层添加到mp3_stream.exe源代码,并重新编译它,这样它可以通过.NET进行访问。或者,人们可以在C#项目方加薄界面层(使用函数[DllImport] 来直接访问LAME DLL功能。

然而,mp3_stream.exe做实际上已经提供了一个API,它是完美的.NET访问。这或许不是人们通常认为的在传统意义上的API,因为它仅仅是由支持命令行参数mp3_stream.exe然而,这是可用于访问所期望的功能非常清楚的一个可编程接口。例如,列举由系统支持的声卡,mp3_stream.exe调用用-device参数。若要以编程方式做到这一点,的System.Diagnostics.Process是用来生成一个mp3_stream.exe过程中为其提供适当的参数。这是通过完成执行方法,进而调用InitiateMP3StreamProcess方法:


private static Process InitiateMp3StreamProcess(string arguments, EventHandler onExecutionCompleted)
{
    var recordingProc = new Process
    {
        StartInfo =
        {
            CreateNoWindow = true,
            WorkingDirectory = Application.StartupPath,
            FileName = "mp3_stream.exe",
            Arguments = arguments,
            UseShellExecute = false,
            RedirectStandardOutput = true,
            RedirectStandardError = true,
            RedirectStandardInput = true,
        },
        EnableRaisingEvents = true
    };

    if (onExecutionCompleted != null)
        recordingProc.Exited += onExecutionCompleted;

    recordingProc.Start();
    return recordingProc;
}

private static IEnumerable<string> Execute(string command)
{
    var process = InitiateMp3StreamProcess(command, null);
    process.WaitForExit();

    return ReadResponseLines(process);
}</string>

onExecutionCompleted  参数可以用来提供当外部过程完成并退出其称为回调事件。该回调是更长时间的操作,例如执行记录时为有用的,并且可以在动作中可以看出录制节。

其它操作,但是,实际上是瞬时的,并且不会阻塞的时间任何明显的期间用户界面。对于这样的操作,就没有必要提供一个回调,我们可以简单地使用Process.WaitForExit()  等待它将采取的过程,完成几毫秒。这种类型的相互作用可以被认为是直接执行的,并且可以通过调用执行  方法。

该方法,可执行是带一个字符串,它指定需要被传递到命令行参数mp3_stream.exe过程:


Execute("-devices");

这是使用,同时配合使用-device论证,反复地填充DeviceLines所有可用的设备和线路词典:


private void PopulateDevices()
{
    var devices = Execute("-devices");
    foreach (var device in devices)
    {
        Devices.Items.Add(device);

        DeviceLines.Add(device, new List<string>());
        var lines = Execute($"-device=\"{device}\"");

        foreach (var line in lines)
        {
            DeviceLines[device].Add(line);
        }
    }
}</string>

记录

一旦配置和记录按钮被按下,程序显示留给录制完成的时间。这是通过使用计时器,它更新进度条和剩余时间的文本显示完成:


private void UpdateTimeRemaining(TimeSpan timeSpan)
{
    TimeRemaining.Text =
        @"Time remaining: " +
        $"{timeSpan.Hours.ToString().PadLeft(2, '0')}:"+
        $"{timeSpan.Minutes.ToString().PadLeft(2, '0')}:"+
        $"{timeSpan.Seconds.ToString().PadLeft(2, '0')}";
}

private void Timer_Tick(object sender, EventArgs e)
{
    var timeNow = DateTime.Now;

    if (timeNow >= EndTime)
    {
        OnStopRecording();
    }
    else
    {
        UpdateProgressIndicators(timeNow);
    }
}

private void UpdateProgressIndicators(DateTime timeNow)
{
    var timeRemaining = EndTime - timeNow;
    UpdateTimeRemaining(timeRemaining);

    var timeElapsed = timeNow - StartTime;
    var totalDuration = EndTime - StartTime;
    var percentageElapsed = (timeElapsed.TotalMilliseconds/totalDuration.TotalMilliseconds)*100;

    if (percentageElapsed > 0)
    {
        Progress.Value = (int) percentageElapsed;
    }
    else
    {
        Progress.Value = 0;
    }
}

记录操作本身是由调用点击所述的事件处理程序记录按钮:


private async void StartRecording_Click(object sender, EventArgs e)
{
    StartRecording();

    await RecordingCompletedSignal.WaitAsync();

    StopRecording();
}

您可能已经注意到StartRecording_Click,不知道为什么会被作为实现异步方法。你可能也想知道做返回的方法的优点无效异步,至少没有考虑到收到的智慧来避免异步无效这是不是真的如太大的关注,因为它可能会首先出现,唯一的例外避免的统治异步虚空是当有问题的方法是事件处理程序,这正是我们在这里处理。StartRecording_Click是一个事件处理程序,这意味着它是一个无效的方法也许不是一个惊喜,并把它变成一个异步方法是绝对的罚款 。

的StartRecording()方法使用可选的回调函数,作为第二个参数所提供的InitiateMP3StreamProcess方法,在一个匿名方法的形式依次调用RecordingCompletedSignal.Release() 事件的顺序去如下:记录被视为当记录持续时间已经期满,或当用户手动点击已完成停止按钮。在任一情况下,外部过程回拨发送一个退出消息,这使得它能够完成录制和关闭摆好,并且还RecordingCompletedSignal.Release()在它的出口处。这里做的事情是,它激发RecordingCompletedSignal信号,其上我们等待异步使用WaitAsync() 


private void StartRecording()
{
    var configuration =
        $"-device=\"{Devices.SelectedItem}\" " +
        $"-line=\"{Lines.SelectedItem}\" " +
        $"-v={Volume.Text} " +
        $"-br={BitRate.Text} -sr=32000";

    RecordingProcess = 
        InitiateMp3StreamProcess(configuration, (s, e) => RecordingCompletedSignal.Release());

    StartRecordingButton.Enabled = false;
    StopRecordingButton.Enabled = true;

    UseTimedRecordingIfNecessary();
}

使用这种协调异步机械另一种方法是调用Process.Kill() 而不是直接,这实际上是这个工具的最初版本如何运作。这就出现了需要被工作周围的一些问题。比如,我发现使用Process.Kill()导致录制的总时间用做mp3_stream.exe要始终小于由大约3秒,我需要允许的预期。这可能是编码过程在它的缓冲区的末尾扔掉数据块小的后果,在突然被杀死了。


private void StopRecording()
{
    RecordingProcess.WaitForExit();
    RecordingProcess.Close();
    SaveRecording();

    UpdateTimeRemaining(new TimeSpan(0, 0, 0));

    Progress.Style = ProgressBarStyle.Continuous;
    Progress.Value = 100;

    StartRecordingButton.Enabled = true;
    StopRecordingButton.Enabled = false;
}

这是不可能的,以等待一个回调方法直接,只有一任务可真正期待。为了实现这一目标,回调函数使用RecordingCompletedSignal通过匿名的方法(S,E)=> RecordingCompletedSignal.Release() 这是在完成调用mp3_stream.exe过程。这意味着StartRecording_Click现在可以等待任务由返回的对象()SemaphoreSlim.WaitAsync

要做到这一点,有一些并发症,在克服  mp3_stream 外部应用程序本身,因为它是轮询的退出信号控制台输入,在“请按任意键退出”的风格。这里的问题是,它是不可能重定向控制台输入,只有标准输入可以重定向这样。要发送退出按键到外部应用程序将需要获得一个不安全的手柄和直接复制的关键字符到缓冲区中。我可以通过简单地重新编译,以避免所有这些并发症  mp3_stream  使得它取代了轻微修改后  _kbhit()  与读取标准输入,而不是拨打电话,使用  CIN >>

通过更新代码,以利用异步方法,回调函数,信号,等等,如上所述,现在有可能结束  mp3_stream.exe  更优雅,并等待一个回调接收其完成的通知。

保存和检索配置

为了方便使用,该工具会自动保存在退出其当前配置。该结构然后在下一次工具启动重新加载。这是通过处理表单的实现的FormClosing负载事件:


private void Record_FormClosing(object sender, FormClosingEventArgs e)
{
    SaveCurrentConfiguration();
}

private void Record_Load(object sender, EventArgs e)
{
    RestorePreviousConfiguration();
}

SaveCurrentConfigurationRestorePreviousConfiguration反过来方法使用设置类来访问应用程序的配置参数:


private void RestorePreviousConfiguration()
{
    var config = Settings.Default;
    Volume.Text = config.Volume;
    BitRate.Text = config.BitRate;
    FileName.Text = config.FileName;

    if (Devices.Items.Contains(config.Device))
    {
        Devices.SelectedItem = config.Device;
    }
    if (Lines.Items.Contains(config.Line))
    {
        Lines.SelectedItem = config.Line;
    }
}

private void SaveCurrentConfiguration()
{
    var config = Settings.Default;
    config.Volume = Volume.Text;
    config.BitRate = BitRate.Text;
    config.FileName = FileName.Text;

    if (Devices.SelectedItem != null)
    {
        config.Device = Devices.SelectedItem.ToString();
    }
    if (Lines.SelectedItem != null)
    {
        config.Line = Lines.SelectedItem.ToString();
    }
            
    config.Save();
}

功能要求

一些人取得了联系,因为这篇文章首次发表,并问该实用程序可以得到加强,增加了对新功能的支持。我会每一个新的功能或增强功能已合并时添加新这里小节。请随时保持您的要求来。我会尽力帮尽可能多的要求越好,就像只要我能。

自动文件名

一种流行的请求是要添加的增强,使得相同的文件名,可以使用一次以上。使用该实用程序的一种常见方法,它的出现,是保持公用事业和运行,连续执行多个记录。具有保持指定新的文件名,在记录点之间的,打破这种使用模式的流程。因此,许多增强请求是围绕如何使这一切成为可能居中。

一个建议是用于音频内容被附加到现有的文件,因此,它生长在长度。这个建议的一个潜在问题是一些音频播放器(如Windows媒体播放器)似乎缓存音频的长度,文件中第一次被打开,或添加到其内部库的方式。这似乎防止这样的球员从识别该音频文件的长度已改变,因此将停止播放磁道一旦其原始长度的已过。

另一项建议是覆盖与新的一个预先存在的文件,虽然这有很多的并发症。例如,用户可能没有用于这种情况发生。覆盖是故意甚至时,仍可能会产生问题。例如,现有的文件可能发生由另一播放器或实用程序被打开,因此它不可能在该时间点,以覆盖它。

一种解决许多这些并发症是自动生成一个新的文件名,根据原始文件的名称。也是有用以减少意外产生可能与一些其它现有的文件名冲突的文件名的机会。

C#开发MP3录音工具

该实用程序现在有一个新功能,自动生成一个新的文件名 ,如果由用户指定一个已经存在。例如,如果用户指定test7.mp3作为文件名 ,但该名称的文件已经存在,实用程序将现在新记录保存到一个名为文件,例如,test7_auto_named_2013_12_29_14_08_36_372.mp3自动生成的名称添加后缀_auto_named_后跟日期时间生成的名称。的日期和时间信息的顺序是:小时,和毫秒这将有助于最大限度地减少意外的文件名 冲突的机会。

国际语言支持

这个请求是约后aXu_AP  报道  没有找到music.mp3档案错误,我们则找到了一个事实,即该实用程序运行的机器使用的国际语言。有问题的措辞在这种情况下,芬兰。我发现,这个问题是不是在实用程序本身,而是在mp3_stream.exe 它调用。 

C#开发MP3录音工具

我改变的C ++代码  mp3_stream.exe 并重新编译它,所以它现在使用宽字符的所有字符串操作。这似乎改善的事情,和名称  现在被报告为Paavoimakkuus  ,而不是它正在之前报告的不可打印的字符。然而,问题仍然没有解决,因为在AA 中的字符  Paavoimakkuus  实际上是不正确。  的名称应已  。Päävoimakkuus  然后我发现 mp3_stream.exe   也硬编码俄罗斯作为编码语言,通过调用:


setlocale( LC_ALL, ".866");

我修改  mp3_stream.exe ,这一次,使其去接机的默认语言:


setlocale( LC_ALL, "");

这个固定的问题。  aXu_AP  同芬兰的机器上非常亲切地测试和发送本节中的截图,包括本这说明该实用程序现在不支持国际语言。 

C#开发MP3录音工具

最后...

我希望你觉得这个工具非常有用。我已经肯定已经做了很多利用了原有的mp3_stream.exe应用,并认为这将是很好的通过发布这些可用性方面的改进作出贡献回到CodeProject上。


.net教程阅读排行

最新文章