跳至主要内容

捕捉音頻 - 樹莓派

在這部分課程中,你將編寫代碼來在樹莓派上捕捉音頻。音頻捕捉將由按鈕控制。

硬體

樹莓派需要一個按鈕來控制音頻捕捉。

你將使用的按鈕是一個Grove按鈕。這是一個數字傳感器,可以打開或關閉信號。這些按鈕可以配置為在按下按鈕時發送高信號,未按下時發送低信號,或按下時發送低信號,未按下時發送高信號。

如果你使用的是ReSpeaker 2-Mics Pi HAT作為麥克風,那麼不需要連接按鈕,因為這個HAT已經有一個按鈕。跳到下一節。

連接按鈕

按鈕可以連接到Grove基座HAT。

任務 - 連接按鈕

一個Grove按鈕

  1. 將Grove電纜的一端插入按鈕模塊上的插座。它只能以一種方式插入。

  2. 在樹莓派關機的情況下,將Grove電纜的另一端連接到Grove基座HAT上標有D5的數字插座。這個插座在GPIO引腳旁邊的插座行中從左數第二個。

Grove按鈕連接到D5插座

捕捉音頻

你可以使用Python代碼從麥克風捕捉音頻。

任務 - 捕捉音頻

  1. 啟動樹莓派並等待其啟動

  2. 啟動VS Code,可以直接在樹莓派上啟動,也可以通過Remote SSH擴展連接。

  3. PyAudio Pip包具有錄製和播放音頻的功能。此包依賴於一些需要先安裝的音頻庫。在終端中運行以下命令來安裝這些庫:

    sudo apt update
    sudo apt install libportaudio0 libportaudio2 libportaudiocpp0 portaudio19-dev libasound2-plugins --yes
  4. 安裝PyAudio Pip包。

    pip3 install pyaudio
  5. 創建一個名為smart-timer的新文件夾,並在此文件夾中添加一個名為app.py的文件。

  6. 在此文件的頂部添加以下導入:

    import io
    import pyaudio
    import time
    import wave

    from grove.factory import Factory

    這將導入pyaudio模塊,一些處理波形文件的標準Python模塊,以及grove.factory模塊以導入一個Factory來創建按鈕類。

  7. 在此之下,添加代碼來創建一個Grove按鈕。

    如果你使用的是ReSpeaker 2-Mics Pi HAT,請使用以下代碼:

    # ReSpeaker 2-Mics Pi HAT上的按鈕
    button = Factory.getButton("GPIO-LOW", 17)

    這會在端口D17上創建一個按鈕,該端口是ReSpeaker 2-Mics Pi HAT上按鈕連接的端口。此按鈕設置為在按下時發送低信號。

    如果你沒有使用ReSpeaker 2-Mics Pi HAT,而是使用連接到基座HAT的Grove按鈕,請使用此代碼。

    button = Factory.getButton("GPIO-HIGH", 5)

    這會在端口D5上創建一個按鈕,該按鈕設置為在按下時發送高信號。

  8. 在此之下,創建一個PyAudio類的實例來處理音頻:

    audio = pyaudio.PyAudio()
  9. 聲明麥克風和揚聲器的硬體卡號。這將是你在本課程前面運行arecord -laplay -l時找到的卡號。

    microphone_card_number = <microphone card number>
    speaker_card_number = <speaker card number>

    <microphone card number>替換為你的麥克風卡號。

    <speaker card number>替換為你的揚聲器卡號,即你在alsa.conf文件中設置的相同號碼。

  10. 在此之下,聲明用於音頻捕捉和播放的採樣率。根據你使用的硬體,你可能需要更改此值。

    rate = 48000 #48KHz

    如果在運行此代碼時遇到採樣率錯誤,請將此值更改為4410016000。值越高,音質越好。

  11. 在此之下,創建一個名為capture_audio的新函數。這將被調用來從麥克風捕捉音頻:

    def capture_audio():
  12. 在此函數內,添加以下內容來捕捉音頻:

    stream = audio.open(format = pyaudio.paInt16,
    rate = rate,
    channels = 1,
    input_device_index = microphone_card_number,
    input = True,
    frames_per_buffer = 4096)

    frames = []

    while button.is_pressed():
    frames.append(stream.read(4096))

    stream.stop_stream()
    stream.close()

    此代碼使用PyAudio對象打開一個音頻輸入流。此流將以16KHz的速率從麥克風捕捉音頻,並以4096字節大小的緩衝區捕捉音頻。

    然後代碼在Grove按鈕按下時循環,每次將這些4096字節的緩衝區讀入數組中。

    💁 你可以在PyAudio文檔中閱讀更多關於傳遞給open方法的選項。

    一旦按鈕釋放,流將停止並關閉。

  13. 在此函數的末尾添加以下內容:

    wav_buffer = io.BytesIO()
    with wave.open(wav_buffer, 'wb') as wavefile:
    wavefile.setnchannels(1)
    wavefile.setsampwidth(audio.get_sample_size(pyaudio.paInt16))
    wavefile.setframerate(rate)
    wavefile.writeframes(b''.join(frames))
    wav_buffer.seek(0)

    return wav_buffer

    此代碼創建一個二進制緩衝區,並將所有捕捉的音頻作為WAV文件寫入其中。這是一種將未壓縮音頻寫入文件的標準方法。然後返回此緩衝區。

  14. 添加以下play_audio函數來播放音頻緩衝區:

    def play_audio(buffer):
    stream = audio.open(format = pyaudio.paInt16,
    rate = rate,
    channels = 1,
    output_device_index = speaker_card_number,
    output = True)

    with wave.open(buffer, 'rb') as wf:
    data = wf.readframes(4096)

    while len(data) > 0:
    stream.write(data)
    data = wf.readframes(4096)

    stream.close()

    此函數打開另一個音頻流,這次是輸出流 - 播放音頻。它使用與輸入流相同的設置。然後將緩衝區作為波形文件打開,並以4096字節塊寫入輸出流,播放音頻。然後關閉流。

  15. capture_audio函數下方添加以下代碼,循環直到按下按鈕。一旦按下按鈕,音頻將被捕捉,然後播放。

    while True:
    while not button.is_pressed():
    time.sleep(.1)

    buffer = capture_audio()
    play_audio(buffer)
  16. 運行代碼。按下按鈕並對著麥克風說話。完成後釋放按鈕,你將聽到錄音。

    在創建PyAudio實例時,你可能會遇到一些ALSA錯誤。這是由於樹莓派上對你沒有的音頻設備的配置。你可以忽略這些錯誤。

    pi@raspberrypi:~/smart-timer $ python3 app.py 
    ALSA lib pcm.c:2565:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.front
    ALSA lib pcm.c:2565:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.rear
    ALSA lib pcm.c:2565:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.center_lfe
    ALSA lib pcm.c:2565:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.side

    如果你遇到以下錯誤:

    OSError: [Errno -9997] Invalid sample rate

    那麼將rate更改為44100或16000。

💁 你可以在code-record/pi文件夾中找到此代碼。

😀 你的音頻錄製程序成功了!