Трансляция видео с Raspberry Pi по 3G тремя способами
Как хобби я выбрал Raspberry Pi, теперь решил поделится тем, что уже получилось. Если коротко, то я реализовал трансляцию видео тремя способами: HLS для iPhone — задержка 20–40 секунд, через nc (net cat) — задержка меньше секунды, но долго буферезирует, и через gstreamer: все хорошо, но нужно смотреть через gstreamer на клиенте.[embedded content]
У Pi есть следующие преимущества:
— большое сообщество (продано 5 миллионов, а еще ведь есть клоны); — дешево (пол года назад я купил B+ за $35, а B за $42 к ней можно подключить по AV телек); — GPU (VideoCore IV — 24Gflops, у Intel Edison GPU заблокирован); — напряжение на пинах 3.3v, что лучше чем 1.8v для любителя как я; — Родная MIPI CSI камера OV5647.
Я сделал следующее — подключил Raspberry Pi, 3G modem, камеру и инфракрасный датчик. Через инфракрасный пульт я могу отдавать комманды: включить/выключить интернет, транслировать живое видео тремя разными способами, отсылать имейл ссылкой на видео трансляцию. Как это использовать я оставляю за кадром видео трансляции.
Raspberry Pi до сих пор умеет работать только с 5MP камерой OV5647 (достаточно старая и слабая), хотя аналогичный броадкомовский чип в какой-то нокии работает с 42MP камерой, даже после того как документация по GPU VideoCore IV стала доступна никакого намека на разнообразие не появилось. На альтернативных платах ситуация нисколько не лучше и там используются в основном USB камеры.
Большинство USB камер сжимают каждый кадр в jpeg и шлют этот jpeg по USB. Драйвер камеры разжимает jpeg… Поэтому качество фото и видео через CSI камеру заметно лучше, чем через USB. Именно камера в Pi это киллер фича, хотя если кто-то (ау Интел) реализует плату с поддержкой камеры на уровне GoPro, то конечно это будет очень интересно.
На Pi я надел плату DVK512 на ней есть несколько выводов, 4 леда и 4 кнопки.
Еще я прицепил инфракрасный датчик и 3G модем. Программировал я все это на питоне и баше под рутом. Чтобы мигать ледом и выходить в инет по 3G, надо быть рутом.
За работу с инфракрасным пультом отвечает пакет lirc. В него входит команда irrecord, которая в теории может научиться работать с любым пультом, а можно скачать конфиг для вашего пульта из интернета.
apt-get install lirc Большинство 3G модемов будет опознано, если сделать так
sudo apt-get install usb-modeswitch , Но чтобы нормально работать с USB я добавил udev правило. По умолчанию, когда вы втыкаете что-либо в USB, то название устройства может получить непредсказуемо, а один 3G modem может сразу отразиться четырьмя устройствами ttyUSB0, ttyUSB1, ttyUSB2, ttyUSB3. Эту проблему можно решить, написав udev правило, и система будет давать строго определенное имя для устройства.
/dev/modems/nova_091095493721000_1–1.5 Такое имя означает: 091095493721000 — уникальный сериал моего модема, 1.5 — первый и единственный USB хаб, 5й USB порт. (в первый порт вроде подключен езернет)
Если os.path.exists ('/dev/modems/nova_091095493721000_1–1.5'), то модем вставлен и надо мигать ледом.
Вот само udev правило
cat /etc/udev/rules.d/52-ftdi.rules
SUBSYSTEMS==«usb», KERNEL==«ttyUSB[0–9]*», ATTRS{idVendor}==»0403», ATTRS{idProduct}==»6001», SYMLINK+=«sensors/ftdi_%s{serial}_%b»
SUBSYSTEMS==«usb», KERNEL==«ttyUSB[0–9]*», ATTRS{idVendor}==»1410», ATTRS{idProduct}==»6000», SYMLINK+=«modems/nova_%s{serial}_%b»
Суть программы — это цикл, который если нажата кнопка, запускает или останавливает процесс, если процесс запущен, то зажигается соответствующий лед.
import lirc import time import subprocess import os import signal import sys import RPi.GPIO as GPIO
class Proc (object): def __init__(self, args): self.args = args self.proc = None def start (self): if self.proc!= None: if self.proc.poll () == None: return self.proc = subprocess.Popen (self.args, shell=False, preexec_fn=os.setsid) #preexec_fn=os.setsid` def stop (self): if self.proc == None: return #self.proc.kill () try: os.killpg (os.getpgid (self.proc.pid), 15) except OSError as e: print e.strerror try: self.proc.terminate () except OSError as e: print e.strerror self.proc = None
def is_live (self): if self.proc!= None: if self.proc.poll () == None: return True self.stop () return False
GPIO.setmode (GPIO.BCM) GPIO.setup (26, GPIO.OUT) GPIO.setup (12, GPIO.OUT) GPIO.setup (16, GPIO.OUT) GPIO.setup (20, GPIO.OUT) GPIO.output (26, False) GPIO.output (12, False) GPIO.output (16, False) GPIO.output (20, False)
ppp = Proc (['./dial.sh']) video = Proc (['./video1.sh']) video2 = Proc (['./video2.sh']) video3 = Proc (['./video3.sh']) ws = Proc (['./webserver.sh']) ws.start ()
def terminate (): GPIO.cleanup () ppp.stop () video.stop () video2.stop () video3.stop () ws.stop ()
def signal_term_handler (signal, frame): print 'got SIGTERM' terminate () sys.exit (0) signal.signal (signal.SIGTERM, signal_term_handler)
sockid = lirc.init («ctrl1», blocking = False) tac = False try: while True: codeIRs = lirc.nextcode () # print codeIR nocode = True for codeIR in codeIRs: nocode = False print codeIR, len (codeIR) if codeIR == «star»: print «STAR DETECTED» if codeIR == »1»: ppp.start () if codeIR == »2»: ppp.stop () if codeIR == »3»: subprocess.call (»./notify.py») if codeIR == »4»: video.start () if codeIR == »5»: video.stop () if codeIR == »7»: video2.start () if codeIR == »8»: video2.stop () if codeIR == »6»: video3.start () if codeIR == »9»: video3.stop () tac = not tac GPIO.output (26, ppp.is_live () or (os.path.exists (»/dev/modems/nova_091095493721000_1–1.5») and tac)) GPIO.output (12, video.is_live ()) GPIO.output (16, video2.is_live () or (video3.is_live () and tac)) GPIO.output (20, ws.is_live ()) if nocode: time.sleep (0.1) except KeyboardInterrupt: terminate () print «Good bye» Класс Proc запускает программы и умеет останавливать их вместе с их подпроцессами. Пакет GPIO используется для мигания ледом, а пакет lirc, для чтения инфракрасного датчика.
Первым делом программа запускает скрипт webserver.sh
#!/bin/bash cd video python -m SimpleHTTPServer это сервер нужен для HLS видео трансляции на iPhone. iPhone очень притязательное устройство и видео принимает строго определенных характеристик.dial.sh — скрипт для выхода в интернет
#!/bin/bash route delete default wvdial Если интернет пропал, скрипт умрет, если скрипт убить, то интернет пропадет. Работает достаточно предсказуемо.Трансляция на iPhone iPhone очень привередливое устройство и оно хочет видео в формате HLS. Нарезанные (сегментированные) по 2 секунды видео файлы, плеер айфона скачивает по HTTP. Каждый файл должен содержать особые фреймы (ключ -ih в raspivid). Если коротко, то родная программа Pi raspivid умеет вроде сегментировать, но как-то она это делает не так. avconv и ffmpeg из Raspbian тоже умеют сегментировать и тоже как-то так, вообщем 5 часом и вы скомпелите свежий ffmpeg скаченный из git. Вот этот ffmpeg будет правильно нарезать поток для трансляции в iPhone. #!/bin/bash
base=»/home/pi/my/py/ir/»
cd $base
rm -fr video/*
raspivid -n -ih -t 0 -ISO 800 -ex night -w 320 -h 240 -fps 30 -b 500000 -o — | \ ./ffmpeg -y \ -loglevel panic \ -i — \ -c: v copy \ -map 0 \ -f ssegment \ -segment_time 1 \ -segment_format mpegts \ -segment_list »$base/video/stream.m3u8» \ -segment_list_size 10 \ -segment_wrap 20 \ -segment_list_flags +live \ -segment_list_type m3u8 \ -segment_list_entry_prefix / \ »$base/video/%03d.ts» Эта трансляция работает, но задержка 40 секунд это норма.
Трансляция с задержкой менее 1 секунды тут все просто! Надо направить поток в net cat #!/bin/bash raspivid -t 0 -h 240 -w 320 -fps 30 -hf -b 100000 -o — | nc -l 5001 А смотреть можно с помощью mplayer nc 94.248.14.212 5001 | mplayer -fps 120 -cache 1024 - , но так как трансляция должна начаться раньше, то mplayer должен догнать трансляцию. Поэтому надо ставить -fps выше, чем в трансляции. Но в любом случае, когда mplayer догонит, то его будет подергивать.Самым оптимальмым получился стриминг по gstreamer
$> cat video3.sh #!/bin/bash
raspivid -t 0 -h 240 -w 320 -fps 25 -hf -b 2000000 -o — | gst-launch-1.0 -v fdsrc! h264parse! rtph264pay config-interval=1 pt=96! gdppay! tcpserversink host=0.0.0.0 port=5000 и соответственно смотреть
gst-launch-1.0 -v tcpclientsrc host=94.248.14.212 port=5000! gdpdepay! rtph264depay! avdec_h264! videoconvert! autovideosink sync=false В Google Play есть приложения, которые умеют показывать видео из gstreamer.
PS: цены на компоненты, чтобы никто не переплачивал. (с учетом бесплатной доставки)6 китайских аккумуляторов 18650 — $111 panasonic nrc18650b — $10 (почти как в тесле)USB банк на один 18650 $1.24DVK 512 — $15IR датчик + пульт — $2.17Сейчас стоит брать Raspberry Pi 2 ~$40
PS2: У Pi все пины имеют два названия. Родные броадкомовские GPIO.setmode (GPIO.BCM) и универсальные для всех Raspberry Pi GPIO.setmode (GPIO.BOARD), но у DVK512 своя третья нумерация.
Led0 — 26 #GPIO.setmode (GPIO.BCM), он же GPIO.setmode (GPIO.BOARD) — 37, он же на DV512 P25Led1 — 12Led2 — 16Led3 — 20Key0 — 5Key1 — 6Key2 — 13Key3 — 19
Happy Hacking