PythonからPyAudioで録音/再生してみる

  •  
 
トビウオ2018年11月28日 - 18:09 に投稿

タグ

概要

Python用ライブラリ「PyAudio」を使用して、マイクから録音・スピーカーから再生・取得した音声のリアルタイム分析まで行ってみました。

準備

ライブラリをインストールする際、依存ライブラリが存在することに注意する必要があります。
単に「pip install pyaudio」だけでは、依存ライブラリ不足でインストールが止まってしまうことがあるからです。
そのため、Mac OSの場合「brew install portaudio」、Linuxの場合「sudo apt-get install portaudio19-dev python-all-dev」が必要になります。詳しくは他の方の記事を参照してください。

使用方法

まず、PyAudio型のインスタンスを作成する必要があります。PyAudio型には__enter____exit__が実装されていませんので、破棄する際は明示的にterminate()メソッドを叩く必要があります。

import pyaudio
p = pyaudio.PyAudio()

# (この間に、PyAudioによる処理を行う)

p.terminate()

次に、PyAudio型のインスタンスから実行できる処理について示します。

利用できるデバイスについて調べる

録音用や再生用に使用できるオーディオデバイスの一覧を取得できます。
ただし、APIとして用意されているのは「デバイスの数を取得する」メソッドと「指定したインデックスのデバイスの情報を取得する」メソッドであり、「デバイスの一覧を取得する」メソッドは無いので注意が必要です。

for index in range(0, p.get_device_count()):
    print(p. get_device_info_by_index(index))

また、PyAudioはPortAudioというクロスプラットフォームなオーディオライブラリのラッパーです。PortAudioでは

  • オーディオデバイスの種類を「Host API(大分類)」
  • Host APIが持つオーディオインタフェースを「Device(小分類)」

と呼んで管理しており、PyAudioでもそれを参照して操作できます。

for host_index in range(0, p.get_host_api_count()):
    print(p. get_host_api_info_by_index(host_index))
    for device_index in range(0, p. get_host_api_info_by_index(host_index)['deviceCount']):
        print(p.get_device_info_by_host_api_device_index(host_index, device_index))

より詳しく理解するため、コンピューターのオーディオデバイスが以下の表の状態だった場合で説明します。

host_api index device_index 1 device_index 2 name
0 0 0 A
0 1 1 B
0 2 2 C
1 0 3 D
1 1 4 E
2 0 5 F
# Host APIの総数を返す(この値は3)
host_api_count = p.get_host_api_count()

# 指定したHost APIの情報を返す
host_api_info1 = p.get_host_api_info_by_index(0)
host_api_info2 = p.get_host_api_info_by_index(1)

# Host APIの情報から、それに連なったDeviceの数を返す
device_count1 = host_api_info1['deviceCount']  # host_api_index=0なので、この値は3
device_count2 = host_api_info2['deviceCount']  # host_api_index=1なので、この値は2

# 指定したHost APIの、指定したDeviceの情報を返す
# 第二引数は、上記の表で言えば「device_index 1」に相当
device_info1 = p.get_device_info_by_host_api_device_index(0, 0)  # 値はA
device_info2 = p.get_device_info_by_host_api_device_index(0, 1)  # 値はB
device_info3 = p.get_device_info_by_host_api_device_index(1, 0)  # 値はD

# デバイスの総数を返す(この値は6)
device_count = p.get_device_count()

# 指定したデバイスの情報を返す
# 引数は、上記の表で言えば「device_index 2」に相当
device_info4 = p.get_device_info_by_index(0)  # 値はA
device_info5 = p.get_device_info_by_index(1)  # 値はB
device_info6 = p.get_device_info_by_index(3)  # 値はD

ここで、get_host_api_info_by_index()およびget_device_info_by_index()から取得できる、Host APIの情報・Deviceの情報はそれぞれ次の通りです。

# Host APIの情報。PaHostApiInfo構造体に準拠している。詳しくはこちら↓
# http://portaudio.com/docs/v19-doxydocs/structPaHostApiInfo.html
{
    'defaultInputDevice': 0,    # デフォルトの入力デバイスの「device_index 2」
     'defaultOutputDevice': 1,    # デフォルトの出力デバイスの「device_index 2」
     'deviceCount': 2,    # Host APIが持つオーディオデバイスの数
     'index': 0,    # Host APIのインデックス
     'name': 'Core Audio',    # Host APIの名称
     'structVersion': 1,    # ?
     'type': 5    # Host APIの種類。例えば「5」は「paCoreAudio(Mac OSのCoreAudio)」を指す
}

# Deviceの情報。PaDeviceInfo構造体に準拠している。詳しくはこちら↓
# http://portaudio.com/docs/v19-doxydocs/structPaDeviceInfo.html
{
    # 非インタラクティブな用途(wavファイルの再生など)におけるデフォルトの入力レイテンシ
    'defaultHighInputLatency': 0.01310657596371882,
    # 非インタラクティブな用途(wavファイルの再生など)におけるデフォルトの出力レイテンシ
    'defaultHighOutputLatency': 0.1,
    # インタラクティブな用途におけるデフォルトの入力レイテンシ
    'defaultLowInputLatency': 0.0029478458049886623,
    # インタラクティブな用途におけるデフォルトの出力レイテンシ
    'defaultLowOutputLatency': 0.01,
    # デフォルトのサンプリングレート
    'defaultSampleRate': 44100.0,
    'hostApi': 0,    # Deviceが属しているHost APIのインデックス
    'index': 0,    # 上記の表で言えば「device_index 2」に相当
    'maxInputChannels': 2,    # 最大の入力チャンネル数。0なら入力を受け付けない
    'maxOutputChannels': 0,    # 最大の出力チャンネル数。0なら出力を受け付けない
    'name': 'Built-in Microphone',    # Deviceの名称
    'structVersion': 2    # ?
}

なお、get_host_api_info_by_type()を使用すれば、Host APIの種類を指定してHost APIの情報を取得できます。
ですが、Host APIの種類を表す数字(PortAudioではenum PaHostApiTypeId)から、「pyaudio.paCoreAudio」などの種類を逆引きするAPIはありませんので、必要なら自分で作りましょう。

ちなみに、デフォルトで使用するHost API、デフォルトで使用する入力・出力Deviceの情報は、それぞれget_default_host_api_info()get_default_input_device_info(),get_default_output_device_info()から取得できます。

録音/再生用途で使用する

PyAudioの場合、録音/再生を行うため、デバイスからStream型のインスタンスを取得し、それに対して操作を行うことで処理を行います。
Stream型のインスタンスは、生成時に「入力用に使うか」「出力用に使うか」「入力と出力の両方に使うか」を指定できます。この際、「入力専用のStreamを作成し、処理後に出力専用のStreamに渡す」といったことも可能です。
また、Stream型のインスタンスを生成する際、入力用・出力用にどのデバイスを使用するかを設定できます(設定しないとデフォルトのデバイスが使用される)。

# Streamを開く(ブロッキング処理の場合)
stream = p.open(format=pyaudio.paInt16,
                channels=2,
                rate=44100,
                input=True,
                output=True,
                input_device_index=0,
                output_device_index=1,
                frames_per_buffer=4096)

# Streamを開く(非ブロッキング処理の場合)
stream = p.open(format=pyaudio.paInt16,
                channels=2,
                rate=44100,
                input=True,
                output=True,
                input_device_index=0,
                output_device_index=1,
                frames_per_buffer=4096,
                stream_callback=func)

例:WAVファイルを読み込んで再生する

典型的なブロッキング処理です。出力用のStreamを開いておき、読み込んだWAVEファイルを順次書き込みます。
このコードはPyAudioの公式ドキュメントにあるものを参考にしています。

import pyaudio
import wave

# チャンクサイズ(粒度)
CHUNK_SIZE = 1024

# WAVファイルを開く
wf = wave.open('test.wav', 'rb')

# PyAudioインスタンスを作成する
p = pyaudio.PyAudio()

# Streamを開く。フォーマット・チャンネル・サンプリングレートをWAVファイルと
# 合わせているが、合わせなくても再生は行える。
# ちなみにフォーマットとはビット深度のことであり、
# 8bitなら「p.get_format_from_width(1)」、
# 16bitなら「p.get_format_from_width((2)」とバイト数で設定することに注意
stream = p.open(format=p.get_format_from_width(wf.getsampwidth()),
                channels=wf.getnchannels(),
                rate=wf.getframerate(),
                output=True)

# データをチャンクサイズだけ読み込む
data = wf.readframes(CHUNK_SIZE)

# Streamに読み取ったデータを書き込む=再生する
while len(data) > 0:
    # Streamに書き込む
    stream.write(data)

    # 再度チャンクサイズだけ読み込む。これを繰り返す
    data = wf.readframes(CHUNK_SIZE)

# Streamを止めて、closeする。closeしなければ、start_stream()で再開できる
stream.stop_stream()
stream.close()

# PyAudioインスタンスを破棄する
p.terminate()

例:録音した音声の音量(RMS)を計算して随時表示する

典型的な非ブロッキング処理です。入力用のStreamを開くのですが、その際にコールバック関数を設定するのがポイントです。
コールバック関数には「(in_data, frame_count, time_info, status)」という4種類の引数を設定し、「(out_data, flag)」というタプルを戻り値として返します。それぞれの引数の意味は次の通り。

  • in_data……bytes型。その時録音された音声データがバイナリ形式て返ってくる。WAVフォーマットは「リトルエンディアン」「ステレオだとLRLRの順番」「ビット深度が8bitなら1バイト分、16bitなら2バイト分の塊」なので、structを駆使してバイナリを解析しよう
  • frame_count……int型。その時録音された音声データの要素数。in_dataのバイト数と必ずしも一致するわけではないので、例えばステレオ・16bitならframe_count×4=len(in_data)となる
  • time_info……dict型。「入力バッファから入力した時刻」「出力バッファに出力した時刻」「現在時刻」が秒単位(恐らくOS起動時からの経過時間)で書き込まれている。他のオーディオデバイスとの時刻合わせなどに使える
  • status……int型。公式ドキュメントにあるように、現在の状態が書き込まれている
  • out_data……bytes型。in_data型と書式は同じ。p.openした際にoutput=Trueだったなら当然書き込むが、そうでない場合はNoneでも渡せばいい
  • flag……int型。現在の録音状況について、続行する・中断する・終了するから選べる。後々stream.stop_stream()する予定がある場合、単にpyaudio_ex.paContinueだけ返していてもいい
import pyaudio
import struct

def callback(in_data, frame_count, time_info, status):
    # bytes型を配列に変換する
    # (とりあえず8bit・モノクロだとした例を書く。
    # データは1バイトづつであり、0〜255までで中央値が128であることに注意)
    in_data2 = struct.unpack(f'<{len(in_data)}B', in_data)
    in_data3 = tuple((x - 128) / 128.0 for x in in_data2)

    # 読み取った配列(各要素は-1以上1以下の実数)について、RMSを計算する
    rms = math.sqrt(sum([x * x for x in in_data3]) / len(in_data3))

    # RMSからデシベルを計算して表示する
    db = 20 * math.log10(rms) if rms > 0.0 else -math.inf
    print(f"RMS:{format(db, '3.1f')}[dB]")

    return None, pyaudio_ex.paContinue

p = pyaudio.PyAudio()

stream = p.open(format=pyaudio.paInt8,
                channels=1,
                rate=8000,
                input=True,
                stream_callback=callback)

Во-1-х, у нас есть чудесные кнопки. Вы знали, что просто нажатие на их может вызвать чувство радости? Да, ну да, как при запуске любимой компьютерной игры или, скажем, при увиденной скидке на пиццу. А кнопок у нас тут просто реальное волшебство!

А еще у нас есть котики. Нет, правда! Мы умеем встроить котиков в самые неожиданные места. Открываете раздел "Промоакции" - и, бац, вам милый котик рожой машет и говорит: "Привет, тут бонусы на вашу отрада!". Что еще надо(надобно) для счастья?

Помимо котиков, у нас есть крутые товары. Вы когда-нибудь лицезрели что-нибудь настолько крутое, что вам захотелось купить его в том числе и за это время, когда вы даже не знали, что такое крутое? На нашем сайте каждый товар - это небольшой кусок крутого счастья, который ожидает, когда вы его заметите.

И понимаете,https://mamont.md/article/transnistriamd-vrata-v-udivitelnyy-mir-pridne…

что еще круто? Мы не просто реализовываем вам вещи. Мы даем для вас навык. Представьте, собственно что наш сайт - это таинственный лес, а вы - отважный изыскатель, готовый обнаруживать неведомые ухищрения и отыскивать сокровища. И это не просто метафора, это наше предложение: обнаружьте удивительное в любой кнопке, товаре либо акции!

А еще мы любим веселить. Наши статьи - это настоящий карнавал знаний! От секретов удачного изготовления яичницы до памятке "Как обмануть метеорологическую станцию и устроить лето посреди зимы". У нас есть ответы на все ваши вопросы, а в случае если их нет, мы придумаем что-то умное и радостное совместно с вами!

И, друзья, если вы еще сомневаетесь, стоит ли заходить на наш крутой вебсайт, скажем так: у нас не просто покупки, у нас - приключение! Приключение с котиками, магическими кнопками и кучей смеха. Заглядывайте, друзья, ведь хохот - одно из лучших лекарство, а у нас его предостаточно!

Виниры — это ультратонкие лепестки из керамики, которые надеваются на внешнюю сторону зубов для оптимизации их внешнего вида. Они способствуют улучшить форму, цвет и выровнять зубы.

Услуги по монтажу виниров в зубной практике "Зуб Доктор в Жк Южная Битца" обеспечивают пациентам возможность улучшить внешний вид своей улыбки.

Аппликация фасеток включает в себя несколько этапов: обработка зубных поверхностей, выполнение слепков, выбор оттенка виниров, окончательное крепление. Она проводится при помощи последних достижений медтехники и в соответствии со всеми медицинскими стандартами.

Фасетки предлагаются в разных версиях материалов, включая композитные виниры, что дает возможность подобрать оптимальный тип для каждого пациента.

<a href=https://zubdoktor.ru/>Экономия времени с винирами</a>

Они также помогают для укрепления зубной эмали, сохраняя при этом естественный вид.

Доверьтесь профессионалам стоматологической клиники "Зуб Доктор в Жк Южная Битца" и улучшите свою улыбку с помощью виниров.

Slightly off topic :)

It so happened that my sister found an interesting man here, and recently got married ^_^
(Admin, don't troll!!!)

Is there are handsome people here! ;) I'm Maria, 28 years old.
I work as a model, successfull - I hope you do too! Although, if you are very good in bed, then you are out of the queue!)))
By the way, there was no sex for a long time, it is very difficult to find a decent one...

And no! I am not a prostitute! I prefer harmonious, warm and reliable relationships. I cook deliciously and not only ;) I have a degree in marketing.

My photo:
<img src="https://i.ibb.co/zhMSQpj/5489819-2-3.jpg"&gt;

___
<i>Added</i>

The photo is broken, sorry(((
Check out my blog where you'll find lots of hot information about me:
http://coolness.su/category/portfolio/
Or write to me in telegram @Lolla_sm1_best ( start chat with your photo!!!)

コメントを追加

プレーンテキスト

  • HTMLタグは利用できません。
  • 行と段落は自動的に折り返されます。
  • ウェブページのアドレスとメールアドレスは自動的にリンクに変換されます。
CAPTCHA
この質問はあなたが人間の訪問者であるかどうかをテストし、自動化されたスパム送信を防ぐためのものです。