読者です 読者をやめる 読者になる 読者になる

ami_GS's diary

情報系大学院生の備忘録。ネットワークの勉強にハマっています。

UDPで音声通信(その3)解決編

Python UDP

こんにちは、以前
UDPで音声通信(その2)挫折編 - ami_GS's diary
PythonUDP音声通信に挫折した者です。

端的に言いますと、解決しました!!

やったぜ!

気づいた経緯

前の記事で、UDPによりパケットロスが発生していると言いましたが、どれだけのパケットが伝送できてないのかを確かめる為に以下のコードを書いてみました。
(めちゃくちゃ長い記事になりますすいません。)

import socket
from threading import Thread
import pyaudio
import numpy as np
from pylab import *
import time

class UDP():
    def __init__(self):
        self.frames = []
        self.udp = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        self.finFlag = False

    def getJoinedData(self):
        return "".join(self.frames) #CHUNKごとにframesに格納されているデータを結合
    def getData(self):
        return np.frombuffer(self.getJoinedData(), dtype = "int16") / float(2**15) #録音データがstringなので数値データに戻す


class Server(Thread, UDP):
    def __init__(self, CHUNK):
        Thread.__init__(self)
        UDP.__init__(self)
        self.setDaemon(True)
        self.udp.bind(("127.0.0.1", 12345))
        self.CHUNK = CHUNK #ここ重要!
        self.moveFlag = True


    def run(self):
        while True:
            try:
                recv, addr = self.udp.recvfrom(self.CHUNK) #受信
                self.frames.append(recv) # データ格納
                if self.moveFlag:
                    self.udp.settimeout(1) #受信終了の為のタイムアウト時間を設定
                    self.moveFlag = False
            except socket.error, msg:
                break

        self.finFlag = True # 終了フラグ

class Client(Thread, UDP):
    def __init__(self, stream, CHUNK, recTime):
        Thread.__init__(self)
        UDP.__init__(self)
        self.setDaemon(True)
        self.CHUNK = CHUNK
        self.stream = stream
        self.recTime = recTime

    def run(self):
        for i in range(int(self.stream._rate / self.CHUNK * self.recTime)):
            self.frames.append(self.stream.read(self.CHUNK)) # recTimeの時間だけ録音

        self.finFlag = True # 録音終了フラグ

        for frame in self.frames:
            self.udp.sendto(frame, ("127.0.0.1", 12345)) # データ送信

if __name__ == "__main__":
    FORMAT = pyaudio.paInt16
    CHANNELS = 2
    RATE = 44100
    CHUNK = 1024
    RECORD_SECONDS = 10

    p = pyaudio.PyAudio()

    stream = p.open(format = FORMAT,
                    channels = CHANNELS,
                    rate = RATE,
                    input = True,
                    frames_per_buffer = CHUNK,
                    )

    client = Client(stream, CHUNK, RECORD_SECONDS)
    server = Server(CHUNK)
    server.start()
    client.start()

    while not client.finFlag:
        time.sleep(0.1)
    else:
        Cresult = client.getData() #録音終了を待ち、数値データにして取得

    while not server.finFlag:
        time.sleep(0.1)
    else:
        Sresult = server.getData() #データ受信終了を待ち、数値データにして取得

    print "Before sample:", len(Cresult) #送信前のサンプル数
    print "After sample:", len(Sresult) #送信後のサンプル数
    print float(len(Sresult))/len(Cresult)*100, "% data could be send" #何%送れたか

 # 送信前と送信後をプロット
    x = arange(0, RECORD_SECONDS, float(RECORD_SECONDS) / len(Cresult)) 
    subplot(211)
    plot(x, Cresult)
    autoscale(enable = True, axis = "both", tight = "True")
    grid(True, "major", "both")
    title("Before")

    subplot(212)
    plot(Sresult)
    autoscale(enable = True, axis = "both", tight = "True")
    grid(True, "major", "both")
    title("After")

    show()

(2014-1-26:コード訂正)

長すぎ申し訳・・・
指定した時間だけ録音し、送信する。
送信の前と後のデータを、サンプル数とプロットした波形により比較するスクリプトです。


これを実行したプロットが、(recTime=0.5の時)
f:id:ami_GS:20140126122947p:plain
波形は「似ている」程度ですね。
※Beforeの時だけサンプル数がわかっているのでx軸を「秒」にしています。
(ちなみに後ろで音楽を流しながら録音してます。)


コマンドライン出力は

recTime 0.5 5 10
送信前サンプル数 43008 440320 880640
送信後サンプル数 10752 110080 220160
送信率[%] 25.0 25.0 25.0


ぴったり25%のデータしか送れてないんですよ。
何回やってみても25%になったのでパケットロスにしては不自然すぎる・・・


この結果から、
「きちんとデータは送れているが、受信がうまくいっていない?」
という仮説を立てました。

検証

そこで、上記のコードを以下のように変えてみました。

class Server(Thread, UDP):
    def __init__(self, CHUNK):
        #略
        self.CHUNK = CHUNK*4 #受信データ量を4倍に


すると出力結果がなんと・・・

recTime 0.5 5 10
送信前サンプル数 43008 440320 880640
送信後サンプル数 43008 440320 839680
送信率[%] 100.0 100.0 95.35


ほぼ完璧に送れてるじゃないですか!!
しかも10秒のデータに至ってはこれこそパケットロスっぽい物が見れる!!


※補足
サンプリングレート44100なのにサンプルデータ数がおかしいんじゃないか?
と思われるかもしれませんが、これは

CHANNELS = 2

を設定した事による物です。1にすれば44100に近い値になると思います。
その場合は受信側CHUNKを2倍にしてください。

結論

  • 前回言及したノイズはパケットロスによる物ではなかった
  • 受信側で、受信データ量をチャンネル数×2倍にするとほぼ100%のデータを送れる。
  • どうしてこんな事が起こったのかわからない(StackOverflowに聞いてみるかぁ)


長くなってしまったので今回はここまで。
その2で紹介したコードの、受信データ料を4倍にすればほぼノイズの無い音声を送れますのでお試しください。
次回は動画と音声を一緒に送信してビデオ送信みたいな物を作っていきたいと思います。