音声転送プログラム

 

■前書き


 現代において
  インターネット回線を使ってPC同士で会議とか、通話とかする事が出来ます。

今回はその初歩的部分。
  pythonを使って一方通行ですが、
   音声を送るプログラムを作ってみたので、そのプログラムとセットアップ方法を
    記録しておきます。

 

■構成

 

  マイク-PC1-(LAN)-PC2-アンプスピーカー

 

■方式
 
 PC1
   PyAudioでマイクから音声を入力。44.1Khz モノラル 16Bit
    そのままUDPで宛先を指定して送信
 
 PC2
    UDPでデータを受け取って
    PyAudioでスピーカーに音声を入力。44.1Khz モノラル 16Bit

 ※この方式の欠点
    全く圧縮していないので、ネットワーク帯域を結構使う。
      といっても、計算すると
       44100×2×8=約71Kbps 
        ぐらいですので、構内LANで単独使用なら問題ないかなーと思います。
    UDPなので、順序保証とか、データの保障が無い。
      とはいえ、こちらも多少ノイズとして聞こえるぐらいの
       影響しかないかなーと思います。
     送信側と受信側で同期を取っていないので、長時間使うと遅延したり、
      バッファーがオーバーフローする可能性あり。
     送受信の両方でエラー時に自動的に再起動するとか、対処必要になると
      思われ。

 

========================
セットアップ
========================

 

■ファイル配置

 

 /usr/local/bin/audio-transferに所定のファイルを配置していく

 

[root@localhost audio-transfer]# pwd

/usr/local/bin/audio-transfer

 

[root@localhost audio-transfer]# ls -al
合計 12
drwxr-xr-x. 2 root root   57  3月 21 09:42 .
drwxr-xr-x. 3 root root   28  3月 21 09:42 ..
-rw-r--r--. 1 root root 1241  3月 21 09:42 recv.py
-rw-r--r--. 1 root root 1526  3月 21 09:42 send.py
-rw-r--r--. 1 root root 1248  3月 21 09:42 show-audio.py
[root@localhost audio-transfer]# 

 

■python準備

 

Windowsも手順は同じ。
以下はCentOSの場合を記載しています。

 

[root@localhost audio-transfer]# python --version
Python 2.7.5

古い。せめて3系にしたいなー
って事で・・・

 

yum install -y python36
yum install -y python36-devel
pip3 install pyaudio

 

■テスト

 

python3.6 show-audio.py

これで、パソコンに入っているオーディオの入出力ポートが一覧出力される。

 

 Device 0: HDA Intel HDMI: HDMI 0 (hw:0,3), Channels: 0
 Device 1: HDA Intel HDMI: HDMI 1 (hw:0,7), Channels: 0
 Device 2: HDA Intel HDMI: HDMI 2 (hw:0,8), Channels: 0
 Device 3: HDA Intel HDMI: HDMI 3 (hw:0,9), Channels: 0
 Device 4: HDA Intel HDMI: HDMI 4 (hw:0,10), Channels: 0
 Device 5: HDA Intel PCH: ALC269VC Analog (hw:1,0), Channels: 2
 Device 6: hdmi, Channels: 0
 Device 7: pulse, Channels: 32
 Device 8: default, Channels: 32
 
しかし、このままではどれがマイクからの入力なのかわからん。
 なので、0から順番に試していって、
  5番がそれである事を突き止めて、
   input_device_index = 5 #
    に決定。

 

だけど、一瞬つながった後にエラーになる。
 その一瞬に送信側でなにか叫ぶと受信側のスピーカーから音が鳴る。
  少し成功。あと一歩です。

 

Traceback (most recent call last):
  File "send.py", line 42, in <module>
    data = stream.read(chunk)
  File "/usr/local/lib64/python3.6/site-packages/pyaudio/__init__.py", line 571, in read
    exception_on_overflow)
OSError: [Errno -9981] Input overflowed

[root@localhost audio-transfer]# 

 

バッファーが足りなくなるとダメっぽいので5倍にする。
「chunk = 50240」に変更

 

Traceback (most recent call last):
  File "send.py", line 45, in <module>
    sock.sendto(data, addr)
OSError: [Errno 90] Message too long
[root@localhost audio-transfer]# 

 

今度はUDP送信でメッセージが長いって。

 

試行錯誤・・・・

 

結局googleで探して
音声をマイクから読み込む部分に「exception_on_overflow=False」を追加。
data = stream.read(chunk, exception_on_overflow=False)

 

いちおうこれで落ち無くなった。

 

いろいろいじってると判るんだけど、
chunkを大きくすると遅延が出ることが判明。
結局chunkは元に戻して1024とする。
「chunk = 1024」

 

■解決

 

音はクリアですね。デジタル通信なんだから当たり前といえば当たり前。
 しかし、圧縮してないので、理論的に考えて普通のIP電話よりクリアですね。
帯域さえ確保できれば(といいつつも、それほど大きな帯域でもないし)
44.1Khz/16Bitですから、CD音質で音を運ぶのは現代の通信回線において余裕です

 

■起動

 

受信側PC(Windows)
 python3.6 recv.py
 ※受信側はOSのFW設定でUDPの5005を開けてください。

 

送信側PC(CentOS)
 python3.6 send.py

 

■結果のソース。

 

「送信側」

 

import pyaudio
import socket
import sys

# ネットワーク設定
UDP_IP = "192.168.10.111"  # 送信先のIPアドレス
UDP_PORT = 5005  # 送信先のポート番号
addr = (UDP_IP, UDP_PORT)

# PyAudioの設定
chunk = 1024 # 音声データのチャンクサイズ
#chunk = 10240 # 音声データのチャンクサイズ
format = pyaudio.paInt16  # 音声のフォーマット
channels = 1  # モノラル
rate = 44100  # サンプリングレート
input_device_index = 5 # 

# UDPソケットの初期化
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

# PyAudioの初期化
p = pyaudio.PyAudio()

# 音声入力ストリームの開始
stream = p.open(format=format,
                channels=channels,
                rate=rate,
                input=True,
                frames_per_buffer=chunk,
                input_device_index=input_device_index)

# 音声出力ストリームの開始
#output_stream = p.open(format=format,
#                       channels=channels,
#                       rate=rate,
#                       output=True,
#                       frames_per_buffer=chunk)

print("録音を開始します。CTRL+Cで終了します。")

try:
    while True:
        data = stream.read(chunk, exception_on_overflow=False)
        #output_stream.write(data)  # スピーカーから音声出力
        sock.sendto(data, addr)  # UDPで音声データを送信
except KeyboardInterrupt:
    print("録音を終了します。")

# ストリームを閉じる
stream.stop_stream()
stream.close()
#output_stream.stop_stream()
#output_stream.close()
p.terminate()

# ソケットを閉じる
sock.close()

 

 

 

「受信側」

 

import pyaudio
import socket
import sys

# ネットワーク設定
HOST = ""  # 送信先のIPアドレス
PORT = 5005  # 送信先のポート番号

# PyAudioの設定
chunk = 102400  # 音声データのチャンクサイズ
format = pyaudio.paInt16  # 音声のフォーマット
channels = 1  # モノラル
rate = 44100  # サンプリングレート

# UDPソケットの初期化
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind((HOST, PORT))

# PyAudioの初期化
p = pyaudio.PyAudio()

# 音声出力ストリームの開始
output_stream = p.open(format=format,
                       channels=channels,
                       rate=rate,
                       output=True,
                       frames_per_buffer=chunk)

print("受信を開始します。CTRL+Cで終了します。")

try:
    while True:
        data, address = sock.recvfrom(chunk*2)
        #print(f"address: {address}")
        output_stream.write(data)  # スピーカーから音声出力
except KeyboardInterrupt:
    print("受信を終了します。")

# ストリームを閉じる
output_stream.stop_stream()
output_stream.close()
p.terminate()

# ソケットを閉じる
sock.close()

 

「オーディオデバイス確認」

import pyaudio

p = pyaudio.PyAudio()

# 使用可能なオーディオデバイスのリストを表示
for i in range(p.get_device_count()):
    info = p.get_device_info_by_index(i)
    print(f"Device {i}: {info['name']}, Channels: {info['maxInputChannels']}")

# 目的のデバイスのインデックスを選択
input_device_index = 0  # 例えば、リストから選んだ入力デバイスのインデックス
output_device_index = 2  # 例えば、リストから選んだ出力デバイスのインデックス

# 音声入力ストリームの開始(特定のデバイスを使用)
stream = p.open(format=pyaudio.paInt16,
                channels=1,
                rate=44100,
                input=True,
                frames_per_buffer=1024,
                input_device_index=input_device_index)  # 入力デバイスを指定

# 音声出力ストリームの開始(特定のデバイスを使用)
output_stream = p.open(format=pyaudio.paInt16,
                       channels=1,
                       rate=44100,
                       output=True,
                       frames_per_buffer=1024,
                       output_device_index=output_device_index)  # 出力デバイスを指定