PyAV と PIL.ImageGrab でデスクトップ録画で音声も録音するとしたらの皮算用

皮算用てくらいだから、答えを出すつもりなっしんぐ。

PyAV は ffmpeg (or libav) へのラッパーであるからして、fffmpeg で出来ることは「出来る可能性がある」。

そもそも「PyAV と PIL.ImageGrab でデスクトップ録画(Windows、かつ音はなしね)」で PIL (Pillow) の ImageGrab でデスクトップ録画を実現しようとしたけれど、実際 ffmpeg だけで実現しようと思えば出来る「こともある」らしい: FFMpeg wiki:Capture/Desktop Windows。考えたこともなかったけど。

どうでもいいが、Unix variant なら簡単だぜ、てゾーンかと思ったら違うのね。/dev/audio 等にアクセスすりゃいいてもんではなくて、x11grab 、alsa や pulseaudio 経由でデバイスにアクセスしてる。Windows の場合は DirectShow (--format dshow)経由ね。

でさ、FFMpeg wiki:Capture/Desktop Windows で紹介してる video="screen-capture-recorder" ってもうさ、何か他のもんに依存してない? これがナニモノなのかわからんが、仮に video="screen-capture-recorder" が外部依存としてインストールされてるんだったら、もう ffmpeg いらんのぢゃぁないの? わからんけど。いずれにしてもアタシの環境で dshow なデバイス列挙するとこうよ:

 1 me@host: ~$ ffmpeg -list_devices true -f dshow -i dummy
 2 ffmpeg version 3.2.4 Copyright (c) 2000-2017 the FFmpeg developers
 3   built with gcc 6.3.0 (GCC)
 4   configuration: --disable-static --enable-shared --enable-gpl --enable-version3 --enable-d3d11va --enable-dxva2 --enable-libmfx --enable-nvenc --enable-avisynth --enable-bzlib --enable-fontconfig --enable-frei0r --enable-gnutls --enable-iconv --enable-libass --enable-libbluray --enable-libbs2b --enable-libcaca --enable-libfreetype --enable-libgme --enable-libgsm --enable-libilbc --enable-libmodplug --enable-libmp3lame --enable-libopencore-amrnb --enable-libopencore-amrwb --enable-libopenh264 --enable-libopenjpeg --enable-libopus --enable-librtmp --enable-libsnappy --enable-libsoxr --enable-libspeex --enable-libtheora --enable-libtwolame --enable-libvidstab --enable-libvo-amrwbenc --enable-libvorbis --enable-libvpx --enable-libwavpack --enable-libwebp --enable-libx264 --enable-libx265 --enable-libxavs --enable-libxvid --enable-libzimg --enable-lzma --enable-zlib
 5   libavutil      55. 34.101 / 55. 34.101
 6   libavcodec     57. 64.101 / 57. 64.101
 7   libavformat    57. 56.101 / 57. 56.101
 8   libavdevice    57.  1.100 / 57.  1.100
 9   libavfilter     6. 65.100 /  6. 65.100
10   libswscale      4.  2.100 /  4.  2.100
11   libswresample   2.  3.100 /  2.  3.100
12   libpostproc    54.  1.100 / 54.  1.100
13 [dshow @ 0000000000595e60] DirectShow video devices (some may be both video and audio devices)
14 [dshow @ 0000000000595e60]  "Chicony USB 2.0 Camera"
15 [dshow @ 0000000000595e60]     Alternative name "@device_pnp_\\?\usb#vid_04f2&pid_b43b&mi_00#7&8618e4c&0&0000#{65e8773d-8f56-11d0-a3b9-00a0c9223196}\global"
16 [dshow @ 0000000000595e60] DirectShow audio devices
17 [dshow @ 0000000000595e60]  "繝槭う繧ッ (Realtek High Definition Au"
18 [dshow @ 0000000000595e60]     Alternative name "@device_cm_{33D9A762-90C8-11D0-BD43-00A0C911CE86}\繝槭う繧ッ (Realtek High Definition Au"
19 dummy: Immediate exit requested

文字化けしてる部分は utf-8 エンコードで「マイク」。ターミナル(コンソール)が cp932 なんで化ける。しかしまぁ、なんでこんなもん日本語訳してるんだか。どこの誰が「Microphone」で「わがらーん、ビコウズエイゴであるからして」叫ぶ? ていうかマスタデータは英語のままにしといて、ビュー層で翻訳すりゃいいでしょーよ、面倒くさい…。

ともあれこの列挙でのポイントは2つ。

  1. screen-capture-recorder なんてもんはない
  2. FFMpeg wiki:DirectShow での
    1 me@host: ~$ ffmpeg -f dshow -i video="UScreenCapture":audio="Microphone" output.flv
    

    という例の部分は「アタシの」環境に従って「マイク (Realtek High Definition Au」などを突っ込む。

つまり screen-capture-recorder がない限りは、「スクリーンキャプチャ」(音声込み)は ffmpeg だけでは不可能だろう、てことで、なので 「PyAV と PIL.ImageGrab でデスクトップ録画(Windows、かつ音はなしね)」自体はちょっと救世主なのかもしらんね。そして音声も録音出来るんだったらこれは「少し」どころか、「答え」になりうるのかもなぁ、と。

とりあえずマイクからの録音を ffmpeg で出来るんかいな、とやってみた。先に説明した「なんで日本語やねん」のために、MSYS な bash コマンドラインに日本語入れらんないんでシェルスクリプトにしとく:

1 #! /bin/sh
2 # -*- coding: cp932-unix -*-
3 ffmpeg -f dshow -i audio="マイク (Realtek High Definition Au" hoge.mp4

また説明が面倒な話だが、さっき「utf-8 エンコードで」言ったが utf-8 化しているのは ffmpeg であって、dshow への入力文字列としては「cp932 なバイト列」である必要があり、だからスクリプトもそうしてる。

これを実行するとマイクから音を拾って mp4 にエンコードし続ける。終了は q。問題ない、イケる。

あとはこれを PyAV からどうすりゃいいの、てことなのだが、video に関しては examples/decode.py がそのまま参考になりそうだ。今の場合目的ではないがワタシの環境における「"Chicony USB 2.0 Camera"」をこのサンプルスクリトに与えたら、「確かに何かデコードしてるらしい」出力を得た(見ての通り視覚的なフィードバックはなくただのデータ出力だけなので、「多分うまくいってる」としか言えないけれど):

 1 me@host: ~$ python PyAV/examples/decode.py --format dshow video="Chicony USB 2.0 Camera"
 2 container: <av.InputContainer 'video=Chicony USB 2.0 Camera'>
 3 	format: <av.ContainerFormat 'dshow'>
 4 	duration: -9.22337203685e+12
 5 	metadata:
 6 
 7 1 stream(s):
 8 	<av.VideoStream #0 rawvideo, yuyv422 640x480 at 0x28c7588>
 9 		time_base: Fraction(1, 10000000)
10 		rate: Fraction(10000000, 333333)
11 		start_time: 5365184420000L
12 		duration: None
13 		bit_rate: None
14 		bit_rate_tolerance: None
15 		video:
16 			format: <av.VideoFormat yuyv422, 640x480>
17 			average_rate: Fraction(10000000, 333333)
18 		metadata:
19 
20 00 <av.Packet of #0, dts=5365184420000, pts=5365184420000; 614400 bytes at 0x288aa48>
21 	duration: 0.033s (333333/10000000 or 333333/10000000)
22 	pts: 536518.442s (268259221/500 or 5365184420000/10000000)
23 	dts: 536518.442s (268259221/500 or 5365184420000/10000000)
24 	decoded: <av.VideoFrame #0, yuyv422 640x480 at 0x27176c8>
25 		pts: 536518.442s (268259221/500 or 5365184420000/10000000)
26 
27 01 <av.Packet of #0, dts=5365185060000, pts=5365185060000; 614400 bytes at 0x288ab88>
28 	duration: 0.033s (333333/10000000 or 333333/10000000)
29 	pts: 536518.506s (268259253/500 or 5365185060000/10000000)
30 	dts: 536518.506s (268259253/500 or 5365185060000/10000000)
31 	decoded: <av.VideoFrame #1, yuyv422 640x480 at 0x2717730>
32 		pts: 536518.506s (268259253/500 or 5365185060000/10000000)
33 
34 02 <av.Packet of #0, dts=5365185700000, pts=5365185700000; 614400 bytes at 0x288aa48>
35 	duration: 0.033s (333333/10000000 or 333333/10000000)
36 	pts: 536518.570s (53651857/100 or 5365185700000/10000000)
37 	dts: 536518.570s (53651857/100 or 5365185700000/10000000)
38 	decoded: <av.VideoFrame #2, yuyv422 640x480 at 0x2883ce0>
39 		pts: 536518.570s (53651857/100 or 5365185700000/10000000)
40 
41 03 <av.Packet of #0, dts=5365186500000, pts=5365186500000; 614400 bytes at 0x288ab88>
42 	duration: 0.033s (333333/10000000 or 333333/10000000)
43 	pts: 536518.650s (10730373/20 or 5365186500000/10000000)
44 	dts: 536518.650s (10730373/20 or 5365186500000/10000000)
45 	decoded: <av.VideoFrame #3, yuyv422 640x480 at 0x2883d48>
46 		pts: 536518.650s (10730373/20 or 5365186500000/10000000)
47 
48 04 <av.Packet of #0, dts=5365187140000, pts=5365187140000; 614400 bytes at 0x288aa48>
49 	duration: 0.033s (333333/10000000 or 333333/10000000)
50 	pts: 536518.714s (268259357/500 or 5365187140000/10000000)
51 	dts: 536518.714s (268259357/500 or 5365187140000/10000000)
52 	decoded: <av.VideoFrame #4, yuyv422 640x480 at 0x2883db0>
53 		pts: 536518.714s (268259357/500 or 5365187140000/10000000)

この example はビデオ用になっちゃってるんで、audio 用に読み替えればきっと期待したことが出来る。

そうなんだけどね…、「PyAV と PIL.ImageGrab でデスクトップ録画(Windows、かつ音はなしね)」から派生すんのは簡単ではないよ。アレはシンプルに「1秒間に24回キャプチャ」としているだけなんだけれど、この構造にマイクからの入力フレームをデコードする構造を、直接混ぜ込むことは出来ない。ちょい工夫が必要よね。あー面倒だ、てことで、「やってやれないことはないと思うけれど、ワタシのニーズに今ないし、今は気力もないので、今はやらない」。

すまんね、「デスクトップレコーダを作ってやるぜ」はワタシの本題ではないの。けどこんだけ情報あげたんだから、きっと出来ると思うよ、ご自分で。


15:30追記:
ちゃんと続ける気は上述通りないけれど、「examples/decode.py は video 用」はコードの見かけに騙されてた。「video =」となってるからビデオ用と思っちゃった。ただし「なんで日本語やねん」問題のためにいずれにしても書き換える必要あり:

examples/decode.py
36 proc = None
37 
38 options = dict(x.split('=') for x in args.option)
39 video = open(args.path.decode("cp932").encode("utf-8"), format=args.format, options=options)
40 
41 print('container:', video)

日本語のエンコーディングがやっぱりややこしいことになっていて、多分これは PyAV 経由するからだろうと思うんだけど、今度は「utf-8 でないとダメ」。

てわけで、例によって bash に日本語打てないんでシェルスクリプトにして:

1 #! /bin/sh
2 # -*- coding: cp932-unix -*-
3 python PyAV/examples/decode.py -a --format dshow audio="マイク (Realtek High Definition Au"

これを実行したらこんな出力してくれた:

 1 container: <av.InputContainer 'audio=\xe3\x83\x9e\xe3\x82\xa4\xe3\x82\xaf (Realtek High Definition Au'>
 2 	format: <av.ContainerFormat 'dshow'>
 3 	duration: -9.22337203685e+12
 4 	metadata:
 5 
 6 1 stream(s):
 7 	<av.AudioStream #0 pcm_s16le at 44100Hz, stereo, s16 at 0x29076d8>
 8 		time_base: Fraction(1, 10000000)
 9 		rate: 44100
10 		start_time: 5471866160000L
11 		duration: None
12 		bit_rate: 1411200
13 		bit_rate_tolerance: None
14 		audio:
15 			format: <av.AudioFormat s16>
16 			channels: 2
17 		metadata:
18 
19 00 <av.Packet of #0, dts=5471866160000, pts=5471866160000; 88200 bytes at 0x28cdb88>
20 	duration: 0.500s (1/2 or 5000000/10000000)
21 	pts: 547186.616s (68398327/125 or 5471866160000/10000000)
22 	dts: 547186.616s (68398327/125 or 5471866160000/10000000)
23 	decoded: <av.AudioFrame 0, 22050 samples at 44100Hz, stereo, s16 at 0x28833c8>
24 		pts: 547186.616s (68398327/125 or 5471866160000/10000000)
25 		samples: 22050
26 		format: s16
27 		layout: stereo
28 
29 01 <av.Packet of #0, dts=5471871260000, pts=5471871260000; 88200 bytes at 0x28cdcc8>
30 	duration: 0.500s (1/2 or 5000000/10000000)
31 	pts: 547187.126s (273593563/500 or 5471871260000/10000000)
32 	dts: 547187.126s (273593563/500 or 5471871260000/10000000)
33 	decoded: <av.AudioFrame 1, 22050 samples at 44100Hz, stereo, s16 at 0x28830c8>
34 		pts: 547187.126s (273593563/500 or 5471871260000/10000000)
35 		samples: 22050
36 		format: s16
37 		layout: stereo
38 
39 02 <av.Packet of #0, dts=5471876260000, pts=5471876260000; 88200 bytes at 0x28cdb88>
40 	duration: 0.500s (1/2 or 5000000/10000000)
41 	pts: 547187.626s (273593813/500 or 5471876260000/10000000)
42 	dts: 547187.626s (273593813/500 or 5471876260000/10000000)
43 	decoded: <av.AudioFrame 2, 22050 samples at 44100Hz, stereo, s16 at 0x28831c8>
44 		pts: 547187.626s (273593813/500 or 5471876260000/10000000)
45 		samples: 22050
46 		format: s16
47 		layout: stereo
48 
49 03 <av.Packet of #0, dts=5471881260000, pts=5471881260000; 88200 bytes at 0x28cdcc8>
50 	duration: 0.500s (1/2 or 5000000/10000000)
51 	pts: 547188.126s (273594063/500 or 5471881260000/10000000)
52 	dts: 547188.126s (273594063/500 or 5471881260000/10000000)
53 	decoded: <av.AudioFrame 3, 22050 samples at 44100Hz, stereo, s16 at 0x28834c8>
54 		pts: 547188.126s (273594063/500 or 5471881260000/10000000)
55 		samples: 22050
56 		format: s16
57 		layout: stereo
58 
59 04 <av.Packet of #0, dts=5471886250000, pts=5471886250000; 88200 bytes at 0x28cdb88>
60 	duration: 0.500s (1/2 or 5000000/10000000)
61 	pts: 547188.625s (4377509/8 or 5471886250000/10000000)
62 	dts: 547188.625s (4377509/8 or 5471886250000/10000000)
63 	decoded: <av.AudioFrame 4, 22050 samples at 44100Hz, stereo, s16 at 0x28835c8>
64 		pts: 547188.625s (4377509/8 or 5471886250000/10000000)
65 		samples: 22050
66 		format: s16
67 		layout: stereo