工具以及使用到的庫
- ffmpeg
- sox
- audacity
- pydub
- scipy
- librosa
- pyAudioAnalysis
- plotly
本文分為兩個部分:
P1:如何使用ffmpeg和sox處理音頻文件
P2:如何編程處理音頻文件并執行基本處理
P1 處理語音數據——命令行方式
格式轉換
ffmpeg -i video.mkv audio.mp3
使用ffmpeg將輸入mkv文件轉為mp3文件
降采樣、通道轉換
ffmpeg -i audio.wav -ar 16000 -ac 1 audio_16K_mono.wav
- ar:聲頻采樣率(audio rate)
- ac:聲頻通道(audio channel)
此處是將原來44.1kHz的雙通道wav文件轉為單通道wav文件
獲取音頻信息
ffmpeg -i audio_16K_mono.wav
將得到
Input #0, wav, from ‘audio_16K_mono.wav’:
Metadata:
encoder : Lavf57.71.100
Duration: 00:03:10.29, bitrate: 256 kb/s
Stream #0:0: Audio: pcm_s16le ([1][0][0][0] / 0x0001), 16000 Hz,
mono, s16, 256 kb/s
- #0表示只有一個通道
- encoder:為libavformat支持的一種容器
- Duration:時長
- bitrate:比特率256kb/s,表示音頻每秒傳輸的數據量,高質量音頻一般比較大
- Stram:流
- #0:0:單通道
- pcm_s16le:
- pcm(脈沖編碼調制,pulse-code modulation)
- signed integer 16:(16位有符號整型)格式采樣
- le表示小端(little endian),高位數據存地址高位,地位數據存地址地位,有如[1][0][0][0] / 0x0001。
- mono:單通道
小插曲
最近看到一道數據類型題
題目:為什么float類型 ( 1 e 10 + 3.14 ) ? 1 e 10 = 0 ? \mathbf{(1e10+3.14)-1e10=0?} (1e10+3.14)?1e10=0?
解題如下:
1 e 10 \mathbf{1e10} 1e10二進制表示為:
001 0 ′ 010 1 ′ 010 0 ′ 000 0 ′ 101 1 ′ 111 0 ′ 010 0 ′ 000 0 ′ 0000 \mathbf{0010'0101'0100'0000'1011'1110'0100'0000'0000} 0010′0101′0100′0000′1011′1110′0100′0000′0000
或者表示為
1.001 0 ′ 101 0 ′ 000 0 ′ 010 1 ′ 111 1 ′ 001 0 ′ 000 0 ′ 000 0 ′ 0 2 ? 2 33 \mathbf{1.0010'1010'0000'0101'1111'0010'0000'0000'0_2*2^{33}} 1.0010′1010′0000′0101′1111′0010′0000′0000′02??233
浮點數三要素:
- 首位:0表示正數,1表示負數
- 中間位,8位,為科學計數法指數部分,上例為33與偏置量(127)的和,此例為160,二進制為1010’0000
- 尾部:23位,二進制表示的小數部分的前23位,此例為0010’1010’0000’0101’1111’001
故 1 e 10 \mathbf{1e10} 1e10的浮點數為:
0 ′ 101 0 ′ 000 0 ′ 001 0 ′ 101 0 ′ 000 0 ′ 010 1 ′ 111 1 ′ 001 \mathbf{0'1010'0000'0010'1010'0000'0101'1111'001} 0′1010′0000′0010′1010′0000′0101′1111′001
到此為止,可知舍去了科學計數法中小數部分的后10位
小數的二進制表示兩個要素:
- 整數部分:正常表示,3.14整數部分為0011
- 小數部分:乘以2取整數部分,
- 0.14*2=0.28 取0
- 0.28*2=0.56 取0
- 0.56*2=1.12 取1
- 0.12*2=0.24 取0
- 0.24*2=0.48 取0
- 0.48*2=0.96 取0
- 0.96*2=1.92 取1
- …
3.14的二進制表示為:
11.0010001... \mathbf{11.0010001...} 11.0010001...
綜上, 1 e 10 + 3.14 \mathbf{1e10+3.14} 1e10+3.14的二進制表示為:
1.001 0 ′ 101 0 ′ 000 0 ′ 010 1 ′ 111 1 ′ 001 0 ′ 000 0 ′ 000 1 ′ 1001 ’ 000 1 2 ? 2 33 \mathbf{1.0010'1010'0000'0101'1111'0010'0000'0001'1001’0001_2*2^{33}} 1.0010′1010′0000′0101′1111′0010′0000′0001′1001’00012??233
轉為浮點數,為
0 ′ 101 0 ′ 000 0 ′ 001 0 ′ 101 0 ′ 000 0 ′ 010 1 ′ 111 1 ′ 001 \mathbf{0'1010'0000'0010'1010'0000'0101'1111'001} 0′1010′0000′0010′1010′0000′0101′1111′001
與 1 e 10 \mathbf{1e10} 1e10一樣,故float類型 ( 1 e 10 + 3.14 ) ? 3.14 = 0 \mathbf{(1e10+3.14)-3.14}=0 (1e10+3.14)?3.14=0
修剪音頻
ffmpeg -i audio.wav -ss 60 -t 20 audio_small.wav
- i:輸入音頻audio.wav
- ss: 截取起始秒
- t:截取段時長
- audio_small.wav:輸出文件
串聯視頻
新建一個list_of_files_to_concat的txt文檔,內容如下:
file 'file1.wav'
file 'file2.wav'
file 'file3.wav'
采用以下命令行,可將三個文件串聯輸出,編碼方式為復制
ffmpeg -f concat -i list_of_files_to_concat -c copy output.wav
分割視頻
以下命令行將輸入視頻分割為1s一個
ffmpeg -i output.wav -f segment -segment_time 1 -c copy out%05d.wav
交換聲道
ffmpeg -i stereo.wav -map_channel 0.0.1 -map_channel 0.0.0 stereo_inverted.wav
- 0.0.1輸入文件音頻流右聲道
- 0.0.0輸入文件音頻流左聲道
合并聲道
ffmpeg -i left.wav -i right.wav -filter_complex "[0:a][1:a]join=inputs=2:channel_layout=stereo[a]" -map "[a]" mix_channels.wav
- filter_complex:復雜音頻濾波器圖
- [0:a],[1:a]:第一個和第二個文件的音頻流
- join=inputs=2:表示兩個輸入流混合
- channel_layout=stereo:混合后輸出為立體聲
- [a]:輸出音頻流標簽
- map ”[a]":將‘[a]'標簽的音頻流映射到輸出文件
分割立體聲音頻為左右單聲道文件
ffmpeg -i stereo.wav -map_channel 0.0.0 left.wav -map_channel 0.0.1 right.wav
- map_channel 0.0.0:將左聲道映射到第一個輸出文件
- map_channel 0.0.1:將右聲道映射到第二個輸出文件
將某個聲道靜音
ffmpeg -i stereo.wav -map_channel -1 -map_channel 0.0.1 muted.wav
- map_channel -1:忽略某聲道
- map_channel 0.0.1:將右聲道映射到輸出文件
音量調節
ffmpeg -i data/music_44100.wav -filter:a “volume=0.5” data/music_44100_volume_50.wav
ffmpeg -i data/music_44100.wav -filter:a “volume=2.0” data/music_44100_volume_200.wav
- filter:a:使用音頻過濾器
- “volume=0.5”:將音頻音量變為原來一半
- “volume=2”:將音頻音量變為原來兩倍
sox音量調節
sox -v 0.5 data/music_44100.wav data/music_44100_volume_50_sox.wav
sox -v 2.0 data/music_44100.wav data/music_44100_volume_200_sox.wav
sox?-v?n \text{sox -v n} sox?-v?n 輸入文件路徑 輸出文件路徑
- v n:音量調節系數,n可理解為倍數。
P2 處理語音數據——編程方式
- wav: scipy.io.wavfile
- mp3:pydub
以數組形式加載音頻文件
# 以數組形式讀取wav和mp3
from pydub import AudioSegment
import numpy as np
from scipy.io import wavfile# 用 scipy.io.wavfile 讀取wav文件
fs_wav, data_wav = wavfile.read("resampled.wav")# 用 pydub 讀取mp3
audiofile = AudioSegment.from_file("resampled.mp3")
data_mp3 = np.array(audiofile.get_array_of_samples())
fs_mp3 = audiofile.frame_rateprint('Sq Error Between mp3 and wav data = {}'.format(((data_mp3 - data_wav)**2).sum()/len(data_wav)))
print('Signal Duration = {} seconds'.format(data_wav.shape[0] / fs_wav))
# 輸出,我使用ffmpeg將wav轉成MP3,比特率將為24kb
Sq Error Between mp3 and wav data = 3775.2859044790266
Signal Duration = 34.5513125 seconds
顯示左右聲道
import numpy as np
from scipy.io import wavfile
import matplotlib.pyplot as plt
fs,data=wavfile.read('resampled_double.wav')
time=np.arange(0,len(data))/fs
fig,axs=plt.subplots(2,1,figsize=(10,6),sharex=True)
axs[0].plot(time,data[:,0],label='Left Channel',color='blue')
axs[0].set_ylabel('Amplitude')
axs[0].legend()
axs[1].plot(time,data[:,1],label='Right Channel',color='orange')
axs[1].set_ylabel('Amplitute')
axs[1].set_xlabel('Time(seconds)')
axs[1].legend()
plt.suptitle("Stereo Audio Waveform")
plt.show()
正則化
import matplotlib.pyplot as plt
from scipy.io import wavfile
import numpy as np
fs,data = wavfile.read("resampled_double.wav")
time=np.arange(0,len(data))/fs
plt.figure(figsize=(10,4))
plt.plot(time,data[:,0]/2^15)
plt.xlabel('Time(seconds)')
plt.ylabel('Amplitude')
plt.title('Stereo Audio Waveform')
修剪音頻
# 顯示2到4秒的波形
import numpy as np
import matplotlib.pyplot as plt
from scipy.io import wavfile
fs,data=wavfile.read('resampled_double.wav')
time=np.arange(0,len(data[2*fs:4*fs]))/fs
plt.figure(figsize=(10,4))
plt.plot(time,data[2*fs:4*fs])
plt.xlabel('Time/s')
plt.ylabel('Amplitude')
plt.title('Stereo Audio Waveform')
plt.show()
分割為固定大小
import numpy as np
from scipy.io import wavfile
import IPython
fs,signal=wavfile.read("resampled.wav")
segment_size_t=1
segment_size=segment_size_t*fs
segments=[signal[x:x+segment_size]for x in range(0,len(signal),segment_size)]
for i,s in enumerate(segments):if len(s)<segment_size:s=np.pad(s,(0,(segment_size-len(s))),'constant') # 這里是為了每個clip都為1swavfile.write(f"resampled_segment_{i}_{i+1}.wav",fs,s)
IPython.display.display(IPython.display.Audio("resampled_segment_34_35.wav"))
# 輸出,成功輸出35個1s的wav文件
簡單算法——刪去無聲片段
import IPython
import matplotlib.pyplot as plt
import numpy as np
energies=[((s/2**15)**2).sum()/len(s) for s in segments] # 防止溢出
thres=np.percentile(energies,20)
indices_of_segments_to_keep=(np.where(energies>thres)[0])
segments2=np.array(segments)[indices_of_segments_to_keep]
new_signal=np.concatenate(segments2)
wavfile.write("processed_new.wav",fs,new_signal.astype(np.int16)) # 轉成int
plt.figure(figsize=(10,6))
plt.plot(energies,label="Energies",color="red")
plt.plot(np.ones(len(energies))*thres,label="Thresholds",color="blue")
plt.title("Energies VS Thresholds")
plt.legend()
plt.show()
IPython.display.display(IPython.display.Audio("processed_new.wav"))
IPython.display.display(IPython.display.Audio("resampled.wav"))
往單聲道音頻中加入節拍
import numpy as np
import scipy.io.wavfile as wavfile
import librosa
import IPython
import matplotlib.pyplot as plt# 加載文件并提取節奏和節拍:
[Fs, s] = wavfile.read('resampled.wav')
tempo, beats = librosa.beat.beat_track(y=s.astype('float'), sr=Fs, units="time")
beats -= 0.05# 在每個節拍的第二個聲道上添加小的220Hz聲音
s = s.reshape(-1, 1)
s = np.array(np.concatenate((s, np.zeros(s.shape)), axis=1))
for ib, b in enumerate(beats):t = np.arange(0, 0.2, 1.0 / Fs)amp_mod = 0.2 / (np.sqrt(t)+0.2) - 0.2amp_mod[amp_mod < 0] = 0x = s.max() * np.cos(2 * np.pi * t * 220) * amp_mods[int(Fs * b): int(Fs * b) + int(x.shape[0]), 1] = x.astype('int16')# 寫入一個wav文件,其中第二個聲道具有估計的節奏:
wavfile.write("tempo.wav", Fs, np.int16(s))# 在筆記本中播放生成的文件:
IPython.display.display(IPython.display.Audio("tempo.wav"))# 繪制波形圖
time = np.arange(0, len(s)) / Fs
fig, axs = plt.subplots(2, 1, figsize=(10, 6), sharex=True)
axs[0].plot(time, s[:, 0], label='左聲道', color='orange')
axs[0].set_ylabel('振幅')
axs[0].legend()
axs[1].plot(time, s[:, 1], label='右聲道', color='blue')
axs[1].set_xlabel("時間/秒")
axs[1].set_ylabel("振幅")
axs[1].legend()
plt.show()
實時錄制以及頻率分析
# paura_lite:
# 一個超簡單的命令行音頻錄制器,具有實時頻譜可視化import numpy as np
import pyaudio
import struct
import scipy.fftpack as scp
import termplotlib as tpl
import os# 獲取窗口尺寸
rows, columns = os.popen('stty size', 'r').read().split()buff_size = 0.2 # 窗口大小(秒)
wanted_num_of_bins = 40 # 要顯示的頻率分量數量# 初始化聲卡進行錄制:
fs = 8000
pa = pyaudio.PyAudio()
stream = pa.open(format=pyaudio.paInt16, channels=1, rate=fs,input=True, frames_per_buffer=int(fs * buff_size))while 1: # 對于每個錄制的窗口(直到按下Ctrl+C)# 獲取當前塊并將其轉換為short整數列表,block = stream.read(int(fs * buff_size))format = "%dh" % (len(block) / 2)shorts = struct.unpack(format, block)# 然后進行歸一化并轉換為numpy數組:x = np.double(list(shorts)) / (2**15)seg_len = len(x)# 獲取當前窗口的總能量并計算歸一化因子# 用于可視化最大頻譜圖值energy = np.mean(x ** 2)max_energy = 0.02 # 條形設置為最大的能量max_width_from_energy = int((energy / max_energy) * int(columns)) + 1if max_width_from_energy > int(columns) - 10:max_width_from_energy = int(columns) - 10# 獲取FFT的幅度和相應的頻率X = np.abs(scp.fft(x))[0:int(seg_len/2)]freqs = (np.arange(0, 1 + 1.0/len(X), 1.0 / len(X)) * fs / 2)# ... 并重新采樣為固定數量的頻率分量(用于可視化)wanted_step = (int(freqs.shape[0] / wanted_num_of_bins))freqs2 = freqs[0::wanted_step].astype('int')X2 = np.mean(X.reshape(-1, wanted_step), axis=1)# 將(頻率,FFT)作為水平直方圖繪制:fig = tpl.figure()fig.barh(X2, labels=[str(int(f)) + " Hz" for f in freqs2[0:-1]],show_vals=False, max_width=max_width_from_energy)fig.show()# 添加足夠多的新行以清除屏幕在下一次迭代中:print("\n" * (int(rows) - freqs2.shape[0] - 1))