【问题标题】:How to insert a silence of specific duration at an arbitrary position of a MP3 file?如何在 MP3 文件的任意位置插入特定持续时间的静音?
【发布时间】:2020-11-28 03:54:18
【问题描述】:

场景

我有一堆 MP3 文件,其中一些具有恒定比特率,另一些具有可变比特率,一些以 128 kbps 编码,一些以其他比特率编码,一些是立体声,一些是联合立体声。全部为 44,100 khz

为了自动化处理这数千个 MP3 文件的任务,我正在尝试开发一种算法,该算法应该在这些 MP3 文件中以不同的任意位置/持续时间插入任意持续时间的静音(例如,插入 500 毫秒的静音到一个 MP3 文件的位置 00:02:30,然后在另一个 MP3 文件的位置 00:40:02 插入 750 毫秒的静音)。

研究

我发现的唯一信息是在 MP3 文件的开头或结尾插入静音。这不是我想要的,因为我需要在任意位置插入静音。大多数情况下,对于大多数文件,我需要在 MP3 文件的中间附近添加静音,也许很少有时候我需要在 MP3 文件的开头添加它。我永远不需要在文件末尾添加静音。

有人建议使用 SOXFFMPEG 命令行应用程序在 MP3 文件的开头或结尾插入静音。我不知道这些应用程序是否可以满足我的目的,但无论如何我的目标是使用 C# 或 VB.NET 语言来做到这一点,而不依赖于任何第三方应用程序,所以这样我可以完全控制哪些修改我将在文件中进行处理,并以编程方式处理生成的修改文件以执行其他任务(因为插入静音只是我对这些 MP3 文件真正需要做的事情之一)。

但我赞成使用任何外部库,我记得 NAudio for .NET,一个很棒的音频处理库,我发现这个有趣的 sn-p 不是关于插入静音但连接文件:

https://markheath.net/post/concatenating-sample-providers-in-naudio

我认为通过 NAudio,我将有机会开发一种算法以在特定持续时间插入静音。

方法

很明显,我没有足够的知识来理解如何完成这项任务。

我想出的一种方法是尝试在流的特定位置插入/填充零,我知道该怎么做,但是......我应该如何翻译零(一个字节)以毫秒计算要插入 MP3 文件的静音持续时间?所以我不知道仅仅插入一个零序列是否会起到沉默的作用,如果它有效,我不知道如何将该零序列转换为时间,我也不知道这种方法是否对所有类型的 MP3 文件变体(CBR、VBR、ABR、单声道或立体声通道等)都是安全的。

我想到的第二种方法是使用任何音频编辑器软件生成一个包含 1 毫秒静音的 MP3 文件,然后在 MP3 的特定位置根据需要多次插入和连接该静音文件流。我想我需要为每个可能的 CBR 比特率生成这个 1 毫秒的 MP3 文件,但是 VBR 和 ABR 会发生什么?我坚持这个想法。

可能最终事情会比我想象的要容易得多,并且肯定 NAudio 可以帮助我完成这项任务,或者至少以更少的努力完成其中的大部分。

问题

如何在未确定的 MP3 文件格式(可能是 CBR、VBR、ABR、单声道或立体声通道、联合立体声、128 或 320 kbps 等)的特定位置/持续时间插入特定持续时间的静音C# 或 VB.NET 是否有 NAudio 或其他 .NET 库的帮助?

要求

  • 不使用第三方命令行应用程序也不自动化 GUI 应用程序。

  • 文件修改应在不丢失音频的情况下进行,即不重新编码文件。就像 MP3DirectCut 一样,您可以在其中插入静音或剪切和粘贴而无需重新编码。

  • 最好能实现一个可重用的通用函数,如下面的函数,使用这个我想尝试简化的参数原型:

     public static MemoryStream InsertSilence(
                     Stream inputFile, // pass the raw file stream data
                     TimeSpan startPosition, // eg: new TimeSpan(0, 2, 10)
                     TimeSpan silenceDuration // eg. TimeSpan.FromSeconds(10)
     ) {
    
         // Do the work, save the data into a new stream and return it.
    
     return null;
     }
    

【问题讨论】:

    标签: c# .net vb.net audio naudio


    【解决方案1】:

    当音频为 PCM 格式(也称为原始音频)时,会发生对数字音频的任何操作……每个音频编解码器(mp3 等)都可以解码为 PCM -> 进行操作 -> 然后将 PCM 编码为任何音频编解码器

    一旦在 PCM 格式中识别音频曲线摆动的范围以确定其过零...在 PCM 中,每个音频样本(音频曲线上的点)通常是一个整数(可以是 16 位 int,或 24 位或32 位等)......所以如果它是一个无符号的 16 位整数,它的值从 0 到 2^16 - 1(0 到 65535)变化,在这种情况下,它的零交叉是该范围的中间值......注意你是否有有符号或无符号整数......无符号是最流行的,只能从零开始,而有符号整数可以存储负值......如果你有有符号整数,你的零交叉值很可能为零。 .. 在任何一种情况下,零交叉始终是整数最大可能范围的中间值

    要添加静音,您可以将一系列值添加到 PCM 数组中,无论您的过零值恰好是通过知道样本位深度来驱动的

    注意字节顺序的概念 ... WAV 文件有一个 44 字节的标头部分,后跟一个 PCM 格式的有效负载 ...当您遍历有效负载以解析下一个音频样本时,如果您的位深度(如识别在标题部分)是 16 位,那么一个音频样本需要两个字节,字节序将确定最重要的字节是在这组字节中出现在第一个还是最后一个

    最容易使用单声道,我强烈建议您只使用单声道而不是多声道(如立体声)来让您的代码工作......只添加多声道,您就可以通过单声道获得成功

    顶部提示首先将您的 mp3 转换为 WAV,然后执行 manip 然后编码回 mp3

    【讨论】:

    • 非常感谢您的帮助和您写这篇文章所花费的时间,它充满了有用的信息,但是关于 PCM/WAVE 格式以及如何按照您解释的方式进行手动修改的所有内容也很重要先进我的知识。
    【解决方案2】:

    最后,我通过阅读 this 文档演示了 OffsetSampleProvider 类的用法,并弄清楚如何根据我的需要调整它的用法,从而设法使用 NAudio 做到了这一点。

    最大的缺点是我想出的解决方案不直接修改 MP3 文件(例如 MP3DirectCut 程序),我的意思是我需要将修改保存为 WAVE 格式然后将其编码为 MP3。

    我不知道 NAudio 是否可以进行这种直接修改以将修改后的流直接保存为 MP3 文件格式而无需重新编码,但另一方面我认为我想出了解决方案out 可能是我的问题最接近的解决方案,至少使用第三方库。也许事情不可能完美。

    出于这个原因,我将此答案发布为一种解决方法,而不是作为最终解决方案,以防有人可以使用 NAudio 发布可以满足我之前要求的答案:

    文件修改应在不丢失音频的情况下进行,即 无需重新编码文件。以与例如相同的方式 MP3DirectCut 可以,您可以在其上插入静音或剪切和粘贴 无需重新编码。

    这就是我在 VB.NET 中所做的:

    ''' <summary>Inserts a silence starting at a specific position 
    ''' in the source <see cref="AudioFileReader"/>.</summary>
    ''' <param name="fileReader">The source <see cref="AudioFileReader"/>.</param>
    ''' <param name="startPosition">Start position where to insert the silence.</param>
    ''' <param name="silenceDuration">Duration of the silence.</param>
    ''' <returns>The resulting <see cref="ISampleProvider"/>.</returns>
    <DebuggerStepThrough>
    Public Shared Function InsertSilence(fileReader As AudioFileReader, 
                                        startPosition As TimeSpan, 
                                        silenceDuration As TimeSpan) As ISampleProvider
    
        Dim currentPosition As Long = fileReader.Position ' Save stream position
    
        ' Take audio from the beginning of file until {startPosition}
        Dim first As New OffsetSampleProvider(fileReader) With {
            .Take = startPosition
        }
    
        ' Take audio after {startPosition} until the end of file
        Dim second As New OffsetSampleProvider(fileReader) With {
            .Take = TimeSpan.Zero
        }
    
        fileReader.Position = currentPosition ' Restore stream position
    
        Return first.FollowedBy(silenceDuration, second)
    
    End Function
    

    使用示例:

    Dim sourceFile As String = "C:\File.mp3"
    Dim outputFile As String = "C:\Output.wav" ' It must be a WAVE file
    
    Dim reader As New AudioFileReader(sourceFile)
    Dim position As TimeSpan = New TimeSpan(0, 0, 0, 1, 500) ' 1.5 seconds
    Dim duration As TimeSpan = TimeSpan.FromMilliseconds(1000)
    Dim result As ISampleProvider = InsertSilence(reader, position, duration)
    
    WaveFileWriter.CreateWaveFile16(outputFile, resultProvider)
    

    更新

    小幅改进的功能:

    Public Shared Function InsertSilence(wave As WaveStream, startPosition As TimeSpan, duration As TimeSpan) As IWaveProvider
    
        If (duration.TotalMilliseconds < 0) Then
            Throw New ArgumentException(message:=$"'{NameOf(duration)}' time can't be negative.", paramName:=NameOf(startPosition))
        End If
    
        If (startPosition.TotalMilliseconds < 0) Then
            Throw New ArgumentException(message:=$"'{NameOf(startPosition)}' time can't be negative.", paramName:=NameOf(startPosition))
        End If
    
        If (startPosition > wave.TotalTime) Then
            Throw New ArgumentException(message:=$"'{NameOf(startPosition)}' time can't be longer than source wave total time.", paramName:=NameOf(startPosition))
        End If
    
        Dim sourceProvider As ISampleProvider = wave.ToSampleProvider()
        Dim currentPosition As Long = wave.Position ' Save stream position
    
        ' Take audio from the beginning of file until {startPosition}
        Dim offset1 As New OffsetSampleProvider(sourceProvider) With {
            .Take = startPosition
        }
    
        ' Take audio after {startPosition} until the end of file
        Dim offset2 As New OffsetSampleProvider(sourceProvider) With {
            .Take = TimeSpan.Zero
        }
    
        wave.Position = currentPosition ' Restore stream position
        Return (offset1.FollowedBy(duration, offset2)).ToWaveProvider()
    
    End Function
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-09-13
      • 1970-01-01
      • 2013-05-12
      • 2022-01-22
      • 2012-06-23
      • 2011-02-25
      • 1970-01-01
      • 2011-08-11
      相关资源
      最近更新 更多