簡體   English   中英

如何在 MP3 文件的任意 position 處插入特定持續時間的靜音?

[英]How to insert a silence of specific duration at an arbitrary position of a MP3 file?

設想

我有一堆 MP3 文件,其中一些具有恆定比特率,另一些具有可變比特率,有些以 128 kbps 編碼,有些以其他比特率編碼,有些是立體聲,有些是聯合立體聲。 全部為 44,100 khz

為了自動化處理這數千個 MP3 文件的任務,我正在嘗試開發一種算法,該算法應該在這些 MP3 文件中以不同的任意位置/持續時間插入任意持續時間的靜音(例如,將 500 毫秒的靜音插入一個MP3 文件位於 position 00:02:30,然后在 position 00:40:02 的其他 MP3 文件中插入 750 毫秒的靜音。

研究

我發現的唯一信息是在 MP3 文件的開頭或結尾插入靜音。 這不是我想要的,因為我需要在任意 position 處插入靜音。 大多數情況下,對於大多數文件,我需要在 MP3 文件的中間附近添加一個靜音,而我可能很少需要在 MP3 文件的開頭添加它。 我永遠不需要在文件末尾添加靜音。

有人建議使用SOXFFMPEG命令行應用程序在 MP3 文件的開頭或結尾插入靜音。 我不知道這些應用程序是否可以滿足我的目的,但無論如何我的目標是使用 C# 或 VB.NET 語言來做到這一點,不依賴於任何第三方應用程序,所以這樣我可以完全控制我將要進行的修改在文件中執行,並以編程方式處理生成的修改文件以執行其他任務(因為插入靜音只是我真正需要對這些 MP3 文件執行的操作之一)。

但我贊成使用任何外部庫,我記得NAudio for .NET,一個很棒的音頻處理庫,我發現這個有趣的片段不是關於插入靜音而是連接文件:

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

我認為使用NAudio ,我將有機會開發一種算法來在特定時間插入靜音。

方法

很明顯,我沒有足夠的知識來理解如何完成這項任務。

我想出的一種方法只是嘗試在 stream 的特定 position 處插入/填充零,我知道該怎么做,但是......我應該如何將零(一個字節)轉換為毫秒計算要插入 MP3 文件的靜音持續時間? 所以我不知道僅僅插入一個零序列是否會起到沉默的作用,如果它起作用我不知道如何將這個零序列轉換為時間,我也不知道這種方法是否對於所有類型的 MP3 文件變體(CBR、VBR、ABR、mono 或立體聲通道等)都是安全的。

我想到的第二種方法是使用任何音頻編輯器軟件生成一個包含 1 毫秒靜音的 MP3 文件,然后在 MP3 文件 stream 的特定 position 中根據需要多次插入和連接該靜音。 我想我需要為每個可能的 CBR 比特率生成這個 1 毫秒的 MP3 文件,但是 VBR 和 ABR 會發生什么?我堅持這個想法。

可能最終事情會比我想象的要容易得多,並且肯定NAudio可以幫助我完成這項任務,或者至少以更少的努力完成其中的很大一部分。

問題

How can I insert a silence of specific duration at a specific position / duration of a undetermined MP3 file format ( which could be CBR, VBR, ABR, single or stereo channel, joint stereo, 128 or 320 kbps, etc) using C# or VB.NET有或沒有NAudio或 .NET 的其他庫的幫助?

要求

  • 不使用第三方命令行應用程序,也不自動化 GUI 應用程序。

  • 文件修改應該在沒有音頻丟失的情況下完成,即不重新編碼文件。 與例如MP3DirectCut一樣,您可以在其上插入靜音或剪切和粘貼而無需重新編碼。

  • 最好使用可重復使用的通用 function 的實現,如下所示,使用我想嘗試簡化的參數原型:

     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; }

當音頻為 PCM 格式(也稱為原始音頻)時,會發生對數字音頻的任何操作……每個音頻編解碼器(mp3 等)都可以解碼為 PCM -> 進行操作 -> 然后將 PCM 編碼為任何音頻編解碼器

一旦以 PCM 格式識別音頻曲線擺動的范圍以確定其過零...在 PCM 中,每個音頻樣本(音頻曲線上的點)通常是 integer(可以是 16 位 int,或 24 位或 32 位,等)...所以如果它是一個無符號的 16 位 integer 它的值從 0 到 2^16 - 1 ( 0 到 65535 )在這種情況下它的過零是該范圍的中間值......還要注意無論您有帶符號整數還是無符號整數...無符號是最流行的,並且只能從零開始具有值,而有符號整數可以存儲負值...如果您有帶符號整數,則很可能您的過零值為零...在無論哪種情況,零交叉始終是整數最大可能范圍的中間值

要添加靜音,您可以向 PCM 數組添加一系列值,無論您的過零值恰好是通過知道樣本位深度來驅動的

注意字節序的概念... WAV 文件有一個 44 字節的 header 部分,后跟 PCM 格式的有效負載...當您穿過有效負載以解析下一個音頻樣本時,如果您的位深度(如 header section ) 是 16 位,那么一個音頻樣本需要兩個字節,字節序將確定最高有效字節在這組字節中是第一個還是最后一個

最容易使用 mono 並且我強烈建議您僅使用 mono 而不是像立體聲這樣的多通道...僅添加多通道您可以通過 mono 獲得成功

頂部提示首先將您的 mp3 轉換為 WAV,然后執行 manip 然后編碼回 mp3

最后,我通過閱讀文檔演示了OffsetSampleProvider class 的用法,並弄清楚如何根據我的需要調整其用法,從而設法使用NAudio做到了這一點。

最大的缺點是我想出的解決方案不會直接修改 MP3 文件(例如MP3DirectCut程序),我的意思是我需要將修改保存為 WAVE 格式,然后將其編碼為 MP3。

我不知道NAudio是否可以進行此類直接修改以將修改后的 stream 直接保存為 MP3 文件格式而不對其進行重新編碼,但另一方面,我認為我想出的解決方案可能是最接近我的問題的解決方案,至少使用第三方庫。 也許事情不可能完美。

出於這個原因,我將此答案發布為一種解決方法,而不是作為最終解決方案,以防有人可以使用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)

更新

function 有一些小的改進:

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM