やってみよう!

プログラミングとかでぃーぷらーにんぐとかVRとか気になったものをやってみる予定

美少女声への道2

前回の続き

blog.ascreit.com

結論から言うと、マイクから音声変換は未だできていません。

詳しい人教えてください!

うまく行ってないコード

audio_mic.py

import pyworld as pw
import numpy as np
import pyaudio
import time
import queue
import struct

class World:
    def __init__(self):
        self.RATE=RATE = 44100
        self.CHUNK=CHUNK = 1024*2
        self.q = queue.Queue()
        # for name in vars(pyaudio): print (name)
        p = pyaudio.PyAudio()
        print("a")
        for x in range(0, p.get_device_count()):
            device = p.get_device_info_by_index(x)
            if device['name'] =='USB PnP Audio Device: Audio (hw:2,0)':
                input_device_index = device['index']
            if device['name'] =='HDA NVidia: HDMI 1 (hw:1,7)':
                output_device_index = device['index']
        print(input_device_index,output_device_index)
        if(not input_device_index or not output_device_index):
            print("device not found")
            exit()
        stream = p.open(format=pyaudio.paInt16,
                        channels=1,
                        rate=RATE,
                        input=True,
                        input_device_index=input_device_index,
                        output=False
                        # stream_callback=self.callback
                        )

        out_stream = p.open(format=pyaudio.paInt16,
                        channels=1,
                        rate=RATE,
                        input=False,
                        output=True,
                        output_device_index=output_device_index
                        # stream_callback=self.outcallback
                        )
        stream.start_stream()
        out_stream.start_stream()
        while stream.is_active():

            try:

                time.sleep(0.1)
                data = stream.read(CHUNK,exception_on_overflow = False)

                data = np.frombuffer(data, np.int16).astype(np.float)
                _f0, t = pw.dio(data, RATE)  # 基本周波数の抽出
                f0 = pw.stonemask(data, _f0, t, RATE)  # 基本周波数の修正
                sp = pw.cheaptrick(data, f0, t, RATE)  # スペクトル包絡の抽出
                ap = pw.d4c(data, f0, t, RATE)  # 非周期性指標の抽出
                data = pw.synthesize(f0*2.0, sp, ap, RATE).astype(np.int16).tobytes()
                out_stream.write(data)
            except KeyboardInterrupt:
                print("end")
                stream.close()      # ストリーム終了
                p.terminate()
            except Exception as e:
                print("errorrrr")
                print(e)

if __name__ == "__main__":
    spec = World()

エラーなし、マイクに話しかけてもなんの応答もない無音状態です。

ちなみに、この部分

                data = np.frombuffer(data, np.int16).astype(np.float)
                _f0, t = pw.dio(data, RATE)  # 基本周波数の抽出
                f0 = pw.stonemask(data, _f0, t, RATE)  # 基本周波数の修正
                sp = pw.cheaptrick(data, f0, t, RATE)  # スペクトル包絡の抽出
                ap = pw.d4c(data, f0, t, RATE)  # 非周期性指標の抽出
                data = pw.synthesize(f0*2.0, sp, ap, RATE).astype(np.int16).tobytes()

コメントアウトしても症状は変わらず...

マイクが認識されてないのでは?と思って別のコードで試してみると

realtime.py

import queue
import sys
from matplotlib.animation import FuncAnimation
import matplotlib.pyplot as plt
import numpy as np
import sounddevice as sd
import pyworld as pw

def audio_callback(indata, outdata, frames, time, status):
    """サンプリングごとに呼ばれるコールバック関数"""
    if status:
        print(status, file=sys.stderr)

    # try:
    #     data = indata[:,0]
    #     data = data.astype(np.float64)
    #     _f0, t = pw.dio(data, samplerate)                   # 基本周波数の抽出
    #     f0 = pw.stonemask(data, _f0, t, samplerate)         # 基本周波数の修正
    #     sp = pw.cheaptrick(data, f0, t, samplerate)         # スペクトル包絡の抽出
    #     ap = pw.d4c(data, f0, t, samplerate)                # 非周期性指標の抽出
    #     data = pw.synthesize(f0*2.0, sp, ap, samplerate)    # 1オクターブ高い声に変換
    #
    #     # # スペクトル包絡を1.2倍
    #     # female_like_sp = np.zeros_like(sp)
    #     # for f in range(female_like_sp.shape[1]):
    #     #     female_like_sp[:, f] = sp[:, int(f/1.2)]
    #     # data = pw.synthesize(f0*2, female_like_sp, ap, samplerate)
    #
    #     data = data.astype(np.float32)      # float64から32へ戻す
    #     frames = data.size                  # 何故かsynthesize()で要素数が増えるので無理やり修正
    #     data = data.reshape(data.size,1)    # 何故かsynthesize()で要素数が増えるので無理やり修正
    #
    #     outdata = data
    #
    # except Exception as e:
    #     print(e)
    outdata[:] = indata

    global q
    q.put(indata[:])


def update_plot(frame):
    """matplotlibのアニメーション更新毎に呼ばれるグラフ更新関数"""

    global plotdata
    while True:
        try:
            data = q.get_nowait()
        except queue.Empty:
            break

        shift = len(data)
        plotdata = np.roll(plotdata, -shift, axis=0)
        plotdata[-shift:, :] = data

        lines[0].set_ydata(plotdata[:, 0])

    return lines


if __name__ == '__main__':
    samplerate = 44100.0   # サンプリング周波数
    window = 0.2        # グラフ表示サンプル数決定係数
    interval = 3        # グラフ更新頻度(ミリ秒)
    channels = 1        # チャンネル数(1固定)

    q = queue.Queue()

    length = int(window * samplerate)   # グラフに表示するサンプル数
    plotdata = np.zeros((length, 1))

    fig, ax = plt.subplots()
    lines = ax.plot(plotdata)          # グラフのリアルタイム更新の最初はプロットから
    ax.axis((0, len(plotdata), -1, 1))

    stream = sd.Stream(channels=channels, samplerate=samplerate, callback=audio_callback,
        device=('USB PnP Audio Device: Audio (hw:2,0)','HDA NVidia: HDMI 1 (hw:1,7)'))
    ani = FuncAnimation(fig, update_plot, interval=interval, blit=True)
    with stream:
        plt.show()

この状態だと自分の声がそのままスピーカーから出てきます。

これで勝つると思って

    # try:
    #     data = indata[:,0]
    #     data = data.astype(np.float64)
    #     _f0, t = pw.dio(data, samplerate)                   # 基本周波数の抽出
    #     f0 = pw.stonemask(data, _f0, t, samplerate)         # 基本周波数の修正
    #     sp = pw.cheaptrick(data, f0, t, samplerate)         # スペクトル包絡の抽出
    #     ap = pw.d4c(data, f0, t, samplerate)                # 非周期性指標の抽出
    #     data = pw.synthesize(f0*2.0, sp, ap, samplerate)    # 1オクターブ高い声に変換
    #
    #     # # スペクトル包絡を1.2倍
    #     # female_like_sp = np.zeros_like(sp)
    #     # for f in range(female_like_sp.shape[1]):
    #     #     female_like_sp[:, f] = sp[:, int(f/1.2)]
    #     # data = pw.synthesize(f0*2, female_like_sp, ap, samplerate)
    #
    #     data = data.astype(np.float32)      # float64から32へ戻す
    #     frames = data.size                  # 何故かsynthesize()で要素数が増えるので無理やり修正
    #     data = data.reshape(data.size,1)    # 何故かsynthesize()で要素数が増えるので無理やり修正
    #
    #     outdata = data
    #
    # except Exception as e:
    #     print(e)

この部分のコメントアウトを外すと

Traceback (most recent call last):
  File "/usr/local/lib/python3.6/dist-packages/sounddevice.py", line 765, in callback_ptr
    callback, idata, odata, frames, time, status)
  File "/usr/local/lib/python3.6/dist-packages/sounddevice.py", line 2516, in _wrap_callback
    callback(*args)
  File "realtime.py", line 37, in audio_callback
    outdata[:] = indata
ValueError: could not broadcast input array from shape (512,1) into shape (661,1)

このエラーが出てきます。

確かに、pyworldで合成すると元のデータより要素数が増えるみたいですが、増えたらだめなの?

ん〜、sounddeviceも使い方よくわからないしpyaudioもなんで動かないのかわからないー!

こんなの楽勝だぜっ!って人もし居たらコメント欄にアドバイスください!

お願いしますっ!

追記

コメントで動いたソースを教えて頂いたので早速試してみます。

https://gist.github.com/lefirea/ca5141176507c8d543542f09dc401164

Docker環境のため、ホストOSとは別のデバイス指定しないとホストOSが占有しててアクセスできないので、そこだけデバイス指定するように修正しました。

# -*- coding: utf-8 -*-
import numpy as np
import pyaudio
import pyworld as pw
import time
import threading
import re

from pynput.keyboard import Key, Controller, Listener
#from scipy.io import wavfile

Chunk=1024
Format=pyaudio.paInt16
Channels=1
#Rate=16000 だと OSError: [Errno -9997] Invalid sample rate が出る(多分マイクの違いで、私のマイクが16kHzに対応してないのかと。)
Rate=16000

QuitKeyPushed=False #Escキーが押されたかどうか

def on_press(key):
    global listener
    global QuitKeyPushed

    try:
        #通常キー
        char=key.char
    except:
        #修飾キー
        char=key
    finally:
        #print(char)
        #Escキー or Qキーが押されたらプログラム終了
        if char==Key.esc or char=='q':
            print('quit...')
            #QuitKeyPushed=True
            listener.stop()
            thread1.join()

def on_release(key):
    pass

#キー入力監視
def KeyLogger():
    try:
        with Listener(on_press=on_press, on_release=on_release) as listener:
            listener.join()
    except:
        pass

#音声変換
def ChangeVoice(data):
    data=np.frombuffer(data, dtype=np.int16).astype(np.float)
    _f0, t=pw.dio(data, Rate)
    f0=pw.stonemask(data, _f0, t, Rate)
    sp=pw.cheaptrick(data, f0, t, Rate)
    ap=pw.d4c(data, f0, t, Rate)

    synthe=pw.synthesize(f0*1.5, sp, ap, Rate).astype(np.int16)
    return synthe.tobytes()

#キー入力監視開始
thread1=threading.Thread(target=KeyLogger)
thread1.start()

p=pyaudio.PyAudio()

# Docker環境のため、ホストOSとは別のデバイス指定しないとホストOSが占有しててアクセスできない
for x in range(0, p.get_device_count()):
    device = p.get_device_info_by_index(x)
    if  re.match( r'HDA Intel PCH: ALC892 Analog*', device['name']):
        input_device_index = device['index']
    if re.match( r'HDA NVidia: HDMI 1 *', device['name']):
        output_device_index = device['index']

stream=p.open(format=Format,
              channels=Channels,
              rate=Rate,
              input=True,
              input_device_index=input_device_index,
              output=True,
              output_device_index=output_device_index
              #frames_per_buffer=Chunk
              )

#ストリーム開始
stream.start_stream()
while stream.is_active():
    try:
        # ,exception_on_overflow = False をつけないと私の環境では 
        # OSError: [Errno -9981] Input overflowed がでる。
        # つけると溢れた部分が切れるからオーバーフローしないように頑張るしか無いのでは?
        data=stream.read(Chunk)
        data=ChangeVoice(data)
        stream.write(data)

        if QuitKeyPushed==True:
            break;
    except Exception as e:
        # 何故か落ちる(原因はInput overflowedでした)のでデバッグのためにexceptionを表示
        print(e)
        print('except')

        #listener.join()を誘発して終了
        keyboard=Controller()
        keyboard.press(Key.esc)
        keyboard.release(Key.esc)

        thread1.join()
        break;
    finally:
        pass

stream.stop_stream()
stream.close()
p.terminate()
print('Stop Streaming...')

結果

root@3b3707a032a1:/home/python/test# python3 VoiceChange_test1.py
ALSA lib pcm_dsnoop.c:618:(snd_pcm_dsnoop_open) unable to open slave
ALSA lib pcm_dmix.c:1052:(snd_pcm_dmix_open) unable to open slave
ALSA lib pcm.c:2495:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.rear
ALSA lib pcm.c:2495:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.center_lfe
ALSA lib pcm.c:2495:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.side
ALSA lib confmisc.c:1281:(snd_func_refer) Unable to find definition 'cards.USB-Audio.pcm.hdmi.0:CARD=0,AES0=4,AES1=130,AES2=0,AES3=2'
ALSA lib conf.c:4528:(_snd_config_evaluate) function snd_func_refer returned error: No such file or directory
ALSA lib conf.c:5007:(snd_config_expand) Evaluate error: No such file or directory
ALSA lib pcm.c:2495:(snd_pcm_open_noupdate) Unknown PCM hdmi
ALSA lib confmisc.c:1281:(snd_func_refer) Unable to find definition 'cards.USB-Audio.pcm.hdmi.0:CARD=0,AES0=4,AES1=130,AES2=0,AES3=2'
ALSA lib conf.c:4528:(_snd_config_evaluate) function snd_func_refer returned error: No such file or directory
ALSA lib conf.c:5007:(snd_config_expand) Evaluate error: No such file or directory
ALSA lib pcm.c:2495:(snd_pcm_open_noupdate) Unknown PCM hdmi
ALSA lib confmisc.c:1281:(snd_func_refer) Unable to find definition 'cards.USB-Audio.pcm.modem.0:CARD=0'
ALSA lib conf.c:4528:(_snd_config_evaluate) function snd_func_refer returned error: No such file or directory
ALSA lib conf.c:5007:(snd_config_expand) Evaluate error: No such file or directory
ALSA lib pcm.c:2495:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.phoneline:CARD=0,DEV=0
ALSA lib confmisc.c:1281:(snd_func_refer) Unable to find definition 'cards.USB-Audio.pcm.modem.0:CARD=0'
ALSA lib conf.c:4528:(_snd_config_evaluate) function snd_func_refer returned error: No such file or directory
ALSA lib conf.c:5007:(snd_config_expand) Evaluate error: No such file or directory
ALSA lib pcm.c:2495:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.phoneline:CARD=0,DEV=0
ALSA lib confmisc.c:1281:(snd_func_refer) Unable to find definition 'cards.USB-Audio.pcm.modem.0:CARD=0'
ALSA lib conf.c:4528:(_snd_config_evaluate) function snd_func_refer returned error: No such file or directory
ALSA lib conf.c:5007:(snd_config_expand) Evaluate error: No such file or directory
ALSA lib pcm.c:2495:(snd_pcm_open_noupdate) Unknown PCM phoneline
ALSA lib confmisc.c:1281:(snd_func_refer) Unable to find definition 'cards.USB-Audio.pcm.modem.0:CARD=0'
ALSA lib conf.c:4528:(_snd_config_evaluate) function snd_func_refer returned error: No such file or directory
ALSA lib conf.c:5007:(snd_config_expand) Evaluate error: No such file or directory
ALSA lib pcm.c:2495:(snd_pcm_open_noupdate) Unknown PCM phoneline
ALSA lib pcm_dsnoop.c:618:(snd_pcm_dsnoop_open) unable to open slave
ALSA lib pcm_dmix.c:1052:(snd_pcm_dmix_open) unable to open slave
ALSA lib pcm_dmix.c:1052:(snd_pcm_dmix_open) unable to open slave
Expression 'paInvalidSampleRate' failed in 'src/hostapi/alsa/pa_linux_alsa.c', line: 2048
Expression 'PaAlsaStreamComponent_InitialConfigure( &self->capture, inParams, self->primeBuffers, hwParamsCapture, &realSr )' failed in 'src/hostapi/alsa/pa_linux_alsa.c', line: 2719
Expression 'PaAlsaStream_Configure( stream, inputParameters, outputParameters, sampleRate, framesPerBuffer, &inputLatency, &outputLatency, &hostBufferSizeMode )' failed in 'src/hostapi/alsa/pa_linux_alsa.c', line: 2843
Traceback (most recent call last):
  File "VoiceChange_test1.py", line 81, in <module>
    output_device_index=output_device_index
  File "/usr/local/lib/python3.6/dist-packages/pyaudio.py", line 750, in open
    stream = Stream(self, *args, **kwargs)
  File "/usr/local/lib/python3.6/dist-packages/pyaudio.py", line 441, in __init__
    self._stream = pa.open(**arguments)
OSError: [Errno -9997] Invalid sample rate

Invalid sample rate と怒られます。

多分、これマイクの違いで、私のマイクが16kHzに対応してないのかと思います。

ちなみに、RATE=44100に修正すると今度は OSError: [Errno -9981] Input overflowed

と怒られます。 いままで何も考えず、このオーバーフローで怒られないためexception_on_overflow = False とつけてましたが、よくよく考えると、このオプションは例外を発生させないだけであって、exception_on_overflow = Falseをつけたところでオーバーフローを起こしていることには違いないのではないでしょうか?

だからinputとoutputで長さが変わって、エラーになる。そんな気がします。

teratail.com

この質問を参考に、CHANKを変えて見ましたが、オーバーフローは変わらず起こりました。

マイク買い換えれば動くのかな?

教えてくれた人はどんなマイクを使ってるのだろう?

私は サンワサプライ USBマイクロホン MM-MCUSB25 https://www.amazon.co.jp/gp/product/B009ZTNJZ0/ref=oh_aui_detailpage_o00_s00?ie=UTF8&psc=1www.amazon.co.jp

iBUFFALO マイクロフォン ミニクリップ ブラック BSHSM03BK https://www.amazon.co.jp/gp/product/B001GNZRNU/ref=oh_aui_detailpage_o00_s00?ie=UTF8&psc=1

の2つが手元にありますが、どちらもだめでした。。。