Tkinterを使った録音
 PyAudioの録音に関する記事を検索するとまるでコピペしたかのようなサンプルプログラムが紹介されています。ここでそんなコードを書いても芸がないのでTkinterを使った録音のコードを紹介します。 まずは外観。



仕様

録音ボタンを押すと5秒間録音します。
録音中は停止ボタンで録音を停止できます。
録音されると保存ボタンが有効になります。

ソース

# -*- coding: utf-8 -*-
import pyaudio
import sys
import tkinter  as tk
import wave

DEVICE_INDEX = 0
CHUNK = 512
FORMAT = pyaudio.paInt16 # 16bit
CHANNELS = 1             # monaural
RATE = 44100            # sampling frequency [Hz]
REC_TIME = 5            # sec
MAX_FRAME = int(REC_TIME * RATE / CHUNK)
WAVE_FILENAME = 'test.wav'


g_stopReq = False
g_recording = False
g_recorded = False
g_audio = pyaudio.PyAudio()

# g_stream = None とするとすっとNoneのまま
#一旦オープンしてクローズする
g_stream = g_audio.open(format=FORMAT,
                channels=CHANNELS,
                rate=RATE,
                input=True,
                input_device_index = DEVICE_INDEX,
                frames_per_buffer=CHUNK)
g_stream.close()

g_frames = []
g_frameIndex = 0

g_btnRec  = None
g_btnStop = None
g_btnSave = None

#ボタンの有効無効の処理
def btnCtrl():
    global g_recording, g_btnRec , g_btnStop, g_btnSave

    print(g_recording)
    if g_recording == True:
        g_btnRec .configure(state = 'disable')
        g_btnStop.configure(state = 'normal')
    else:
        g_btnRec .configure(state = 'normal')
        g_btnStop.configure(state = 'disable')
    
    if g_recorded == True:
        if g_recording == False:
            g_btnSave.configure(state = 'normal')
        else:
            g_btnSave.configure(state = 'disable')
    else:
        g_btnSave.configure(state = 'disable')

def closeAudio():
    global g_frameIndex, g_stream

    g_stream.stop_stream()
    g_stream.close()

def callback(in_data, frame_count, time_info, status):
    global g_frameIndex, g_stream,g_recording, g_recorded,g_stopReq

    g_frames.append(in_data)
    g_frameIndex += 1
    if g_frameIndex >= MAX_FRAME or g_stopReq == True:
        closeAudio()
        g_recording = False
        g_recorded = True
        g_stopReq = False
        btnCtrl()
        return(None, pyaudio.paAbort)
    return (None, pyaudio.paContinue)


#ボタンをクリックしたときのイベントハンドラー
def rec_clicked():
    global g_frames,g_recording, g_audio

    g_recording = True
    g_frameIndex = 0
    g_frames.clear()
    g_stream = g_audio.open(format=FORMAT,
                channels=CHANNELS,
                rate=RATE,
                input=True,
                input_device_index = DEVICE_INDEX,
                frames_per_buffer=CHUNK,
                stream_callback=callback)
    
    
    g_stream.start_stream()
    btnCtrl()


def stop_clicked():
    global g_stopReq

    g_stopReq = True
    btnCtrl()

def save_clicked():
    print('saved')
    saveToFile(WAVE_FILENAME)
    btnCtrl()

def saveToFile(fileName):
    global g_frame,g_audio

    wf = wave.open(fileName, 'wb')
    wf.setnchannels(CHANNELS)
    wf.setsampwidth(g_audio.get_sample_size(FORMAT))
    wf.setframerate(RATE)
    wf.writeframes(b''.join(g_frames))
    wf.close()

def main():
    global g_frame,g_recording, g_btnRec , g_btnStop, g_btnSave

    root = tk.Tk()
    root.title("オーディオ録音")
    root.geometry("250x36")

    g_btnRec  = tk.Button(
        root,
        text='録音',
        compound=tk.TOP,
        command=rec_clicked)
    g_btnRec .grid(row = 0,column = 0)

    g_btnStop = tk.Button(
        root,
        text='停止',
        state = 'disable',
        compound=tk.TOP,
        command= stop_clicked)
    g_btnStop.grid(row = 0,column = 1)

    g_btnSave = tk.Button(
        root,
        text='保存',
        state = 'disable',
        compound=tk.TOP,
        command=save_clicked)
    g_btnSave.grid(row = 0,column = 2)

    root.mainloop()
    g_audio.terminate() 

if __name__ == "__main__":
    main()	
	
よそのサイトと違っているところはコールバッグ関数を使っています。これを使うことにより録音中に固まってしますことがありません。 具体的には録音ボタンを押したときのイベントハンドラーrec_clicked()でセットしています。

#ボタンをクリックしたときのイベントハンドラー
def rec_clicked():
    global g_frames,g_recording, g_audio

    g_recording = True
    g_frameIndex = 0
    g_frames.clear()
    g_stream = g_audio.open(format=FORMAT,
                channels=CHANNELS,
                rate=RATE,
                input=True,
                input_device_index = DEVICE_INDEX,
                frames_per_buffer=CHUNK,
                stream_callback=callback)
    
    
    g_stream.start_stream()
    btnCtrl()
コールバック関数です。

def callback(in_data, frame_count, time_info, status):
    global g_frameIndex, g_stream,g_recording, g_recorded,g_stopReq

    g_frames.append(in_data)
    g_frameIndex += 1
    if g_frameIndex >= MAX_FRAME or g_stopReq == True:
        closeAudio()
        g_recording = False
        g_recorded = True
        g_stopReq = False
        btnCtrl()
        return(None, pyaudio.paAbort)
    return (None, pyaudio.paContinue)
コールバック関数の名前は何でもOKです。引数は指定されています。returnがコールバック関数を次にどうするかを指定します。
pyaudio.paAbort:録音を終了
pyaudio.paContinue:録音を継続
Visual Studio Codeでの大きな問題です。コールバック関数内のブレークポイントで停止しません。仕方ないのでprintでデバッグします。効率が悪いです。
録音の停止は,g_stopReqがTrueになるとタイムアウト同様に動作します。

ボタンの有効無効の処理をちゃんとやっています。自分のよくやる方法です。ソースのあちこちでボタンの有効無効をいれるのは効率が悪くミスをしがちです。状態に応じたボタンの処理をまとめたbtnCtrl()を用意します。これを状態がが変わったところに入れるだけなので簡単です。