2021年1月4日月曜日

OpenCVで簡単に音声付き動画を再生する

音声付動画を再生するためのちょうど良いプログラムの書き方例がネット上で意外と見当たらない。 単に動画を再生するだけじゃなくて動画の各フレームに対して画像処理できるようにOpenCVで画像を扱いたい。 mp4動画だけならOpenCVで簡単に再生できる。音声はmp4からwaveに変換しておいてwaveをPyAudioで再生する。 動画と音声をスレッドで独立に再生させると音ズレの問題があるので、 動画もフレーム単位で、音声はチャンク(一定数のフレーム)単位で再生して、動画の再生後の経過時間とフレーム番号の同期を取る。 音声は特に処理するつもりはないので、遅れたりしないものとしてる(というか、同期とる方法を知らないだけ)。

ライブラリとグローバル変数

必要なライブラリはこれだけ.唯一のグローバル変数はflagで,動画再生スレッドが終了したと同時に音声再生スレッドを終了させるために用いる.

import cv2
import pyaudio
import wave
import threading
import time

flag = True

音声再生用の関数

チャンクのサイズはchunk=1024フレーム. while文でflagがFalseになるまで再生し続ける.

def play_audio(fname):
    wf = wave.open(fname, "rb")
    p = pyaudio.PyAudio()
    stream = p.open(format=p.get_format_from_width(wf.getsampwidth()),\
                    channels=wf.getnchannels(),\
                    rate=wf.getframerate(), output=True)
	chunk = 1024
    data = wf.readframes(chunk)
    
    #flagがFalseになるとループを抜ける
    while data != '' and flag:
        #ここで再生している
        stream.write(data)
		data = wf.readframes(chunk)

    stream.stop_stream()
    stream.close()
    p.terminate()

動画再生用の関数

動画再生時のポイントは,while文の中の最初のif文. CAP_PROP_POS_MSECで再生中動画フレームに対応した時間を取得できるので, これとPCの時間とを比較して動画再生が早すぎると空回りするようになっている. もうちっとシンプルに書けるかも.一応これでパット見スムーズに再生される.

def play_video(fname):
	global flag
    window_name = "PlayVideoAudio"
    cap = cv2.VideoCapture(fname)
    start = time.time()

    if not cap.isOpened():
        sys.exit()

    while True:
        elapsed_time = (time.time() - start)*1000
        play_time = int(cap.get(cv2.CAP_PROP_POS_MSEC))

        if elapsed_time < play_time:
            key = cv2.waitKey(1)
            if key == 27:
                global flag
                flag = False
                break            
            else:
                continue
        else:
            ret, frame = cap.read()

        if ret:
            cv2.imshow(window_name, frame)
        else:
        	flag = False
            break

ここからが実行部分

これがメインスレッド.このプログラムの欠点は, mp4に音声が含まれているのにmp4とwavを両方準備しないといけないこと. あとは,エラーで停止すると音声再生スレッドがゾンビ化する. 例外処理などもちゃんとしないといけない.

audio_file = "test.wav"
video_file = "test.mp4"

thread1 = threading.Thread(target=play_audio, args=[audio_file])
thread2 = threading.Thread(target=play_video, args=[video_file])
thread1.start()
thread2.start()
thread1.join()
thread2.join()

PyAudioのインストール方法

上記のプログラムを実行するには予めPyAudioのインストールが必要. pyaudioはwindowsでpipを使うと上手くインストールできない.対処法は, ここからPyAudio-0.2.11-cp37-cp37m-win_amd64.whlをダウンロードして次のコマンドなどでインストールする.

pip install PyAudio-0.2.11-cp37-cp37m-win_amd64.whl

cp37の部分がpythonのバージョン3.7に対応しているので自分の環境のバージョンに合わせてファイルを選択する必要がある.