Software apps and online services
Hand tools and fabrication machines
As a YouTuber, I'm always looking for ways to streamline different tasks. Live streaming is definitely something that has the opportunity to be a LOT simpler. It would be nice if you just had a camera that you can take with you, turn on, and live stream. So that will be the primary goal of this project: to make a dedicated camera for live streaming to YouTube.
My idea is to make a video camera using a Raspberry Pi 3, 2.8" LCD Touchscreen, and the Pi Camera as the basic platform. We'll also need a small USB microphone to record audio, and some type of rechargeable battery to power it all. Then I'm going to 3D print a custom case shaped like the YouTube logo to power it all.
As with any project using a Raspberry Pi, the first step is to get it set up. To do this, you'll need:
- A Raspberry Pi 3
- A micro SD Card (8gb or greater)
- HDMI or composite monitor and cable
- Keyboard and Mouse
- Power supply
With the hardware set, we next need to load the software onto the PI. You can download the latest Raspbian software from here. It's a large file, so it may take a while to download. But once it has, you can burn it to your SD card using the Etcher.io software for Windows, Mac and Linux.
After you've successfully copied the files to the SD card, you can put it in your Raspberry Pi, plug in the monitor, keyboard, mouse and then the power. After a minute or so, it should boot to the Desktop.
The first thing you want to do is connect to your internet by clicking on the internet icon in the upper right and selecting your wireless network. Then inter your password and click "connect". Next you can right click on that same icon, and select "Wireless and Wired Network Settings". Here, you'll want to make sure wlan0 is selected and then give it a static IP address that matches the IP scheme of your network.
To finish up, click on the start menu in the upper left go to Preferences > Raspberry Pi Configuration. On the "Interfaces" tab, select to enable SSH. Then on the "Localisation" tab, set your keyboard language settings to match your country. Then reboot your machine. Now you should be able to log in from a remote computer using SSH.
The camera I'm using is the Raspberry Pi Camera Module v2. I like it because it's very flat in shape and doesn't take up any USB ports. It has it's own dedicated IO port. So with the Pi off, insert the Camera Module (as seen below). Then power your PI back on.
Before we can use the camera, we have to enable it. So after the Pi boots back up, you can SSH into it and run
sudo raspi-config #Interfacing Options > Camera > Enable sudo reboot
Once your Pi boots back up, you should be able to utilize the camera.
I'm using an Adafruit 2.8" Touchscreen LCD. It comes connected with header pins that make it easy to fit right on top of the Raspberry Pi GPIO Pins.
Adafruit has some great documentation about setting up the Raspberry Pi, and they even have their own Raspbian Image that you can download and install with everything already on it. Sadly, the pre-made image didn't work for me, which is why I installed a fresh version of Raspbian.
The LCD requires a Kernel in order for it to work, so I'll need to download it manually from the Adafruit website (these steps are also on their instruction page).
sudo apt-get update curl -SLs https://apt.adafruit.com/add-pin | sudo bash sudo apt-get install raspberrypi-bootloader
Installing the bootloader will take a few minutes, but when it's done, you can shutdown your Pi, and unplug any other external monitors before turning it back on.
Once the Pi boots back up, we'll need to modify the Pi's boot config file and adjust it so that it displays to the LCD properly.
sudo nano /boot/config.txt
and then add this text to the bottom of it:
dtparam=i2c_arm=on dtparam=i2s=on dtparam=spi=on dtoverlay=pitft28-resistive,rotate=90,speed=32000000,fps=20
In order to get anything to display on the LCD, we need to tell the operating system to use it as it's primary display. To do this, you can use these commands:
export FRAMEBUFFER=/dev/fb1 startx
Now we can test it by displaying an image on it using the fbi (not that F.B.I.) image viewer.
sudo apt-get install fbi wget http://www.tinkernut.com/wp-content/uploads/2015/01/logo_gear_sm3.png -O image.png sudo fbi -T 2 -d /dev/fb1 -noverbose -a image.png
The LCD part works. Now we need to test the touch screen. By default, the touchscreen should work with the Adafruit kernel. But there are a few commands you can run to help calibrate it.
sudo TSLIB_FBDEVICE=/dev/fb1 TSLIB_TSDEVICE=/dev/input/touchscreen ts_calibrate sudo TSLIB_FBDEVICE=/dev/fb1 TSLIB_TSDEVICE=/dev/input/touchscreen ts_test
These commands will give you visual feedback on your screen that you can touch to calibrate your screen.
If you have an Adafruit PiTFT touchscreen like I do, you may find that no matter how much you calibrate the LCD, it still won't work with your Pygame code. This is because there is an issue with the Adafruit touchscreens and the current version of Raspbbian.
The touchscreens work well with the Raspbian "Wheezy" distro, but they don't play well with Raspbian "Jessie". There is a workaround, however, to get it functional again. It involves downloading and installing the "Wheezy" driver.
#enable wheezy package sources echo "deb http://archive.raspbian.org/raspbian wheezy main " >> /etc/apt/sources.list.d/wheezy.list #set stable as default package source (currently jessie) echo "APT::Default-release \"stable\"; " >> /etc/apt/apt.conf.d/10defaultRelease #set the priority for libsdl from wheezy higher then the jessie package echo "Package: libsdl1.2debian Pin: release n=jessie Pin-Priority: -10 Package: libsdl1.2debian Pin: release n=wheezy Pin-Priority: 900 " >> /etc/apt/preferences.d/libsdl #install apt-get update apt-get -y --force-yes install libsdl1.2debian/wheezy
Running the above code should fix the issue and the touchscreen will now work with Pygame. So let's go ahead and create our own test script that sets a background, creates a button, and checks for touch input to perform an action.
import pygame import os from time import sleep import random os.environ['SDL_FBDEV']= '/dev/fb1' os.environ["SDL_MOUSEDEV"] = '/dev/input/touchscreen' os.environ['SDL_MOUSEDRV'] = 'TSLIB' pygame.init() lcd = pygame.display.set_mode((320,240)) lcd.fill((255,0,0)) def make_button(text, xpo, ypo, color): font=pygame.font.Font(None,24) label=font.render(str(text),1,(color)) lcd.blit(label,(xpo,ypo)) pygame.draw.rect(lcd, cream, (xpo-5,ypo-5,110,35),1) def random_color(): rgbl=[255,0,0] random.shuffle(rgbl) return tuple(rgbl) blue = 26, 0, 255 white = 255, 255, 255 cream = 254, 255, 250 lcd.fill(blue) pygame.mouse.set_visible(False) while 1: make_button("Menu item 1", 20, 20, white) for event in pygame.event.get(): if (event.type == pygame.MOUSEBUTTONDOWN): print "screen pressed" lcd.fill(random_color()) pos = (pygame.mouse.get_pos(),pygame.mouse.get_pos()) print pos pygame.display.update()
Since the Raspberry Pi doesn't have audio input by default, we're gonna have to use a USB based audio input. I just got a cheap mini USB microphone and plugged it in.
The Pi should automatically detect it and make it useable. I'm going to be using the microphone in conjunction with the Alsa audio library, so we'll need to install that first. Then we can test out the microphone by making a simple 30 second recording (make sure to have some speakers or headphones plugged in in order to hear the playback).
sudo apt-get install libasound2-dev arecord -D plughw:1 --duration=30 -f cd -vv ~/test.wav omxplayer -p -o hdmi ~/test.wav
Although I'm using Youtube in this tutorial, the concept should also work with other streaming services like Twitch.tv and FacebookLive. Most of these streaming services use a protocol called Real-Time Messaging Protocol (RTMP). So in order to stream to Youtube, we'll need the RTMP URL as well as a private key for our specific stream.
To get your key and URL, go to your Youtube Dashboard and select "Live Streaming" and select either "Stream now" or "Events" and create a new event. Following those steps will allow you to generate a new RTMP URL and private key.
With our URL and key, we can now turn back to the Raspberry Pi to make a streaming program using Python. When it comes to streaming from a Raspberry Pi, there are actually several different methods. One of the easier ways is by installing avconv (part of the libav-tools package) and using raspivid to pipe the stream to the RTMP URL. Here's a sample of how that would work from the command line:
sudo apt-get install libav-tools raspivid -o - -t 0 -vf -hf -fps 30 -b 6000000 | avconv -re -ar 44100 -ac 2 -acodec pcm_s16le -f s16le -ac 2 -i /dev/zero -f h264 -i - -vcodec copy -acodec aac -ab 128k -g 50 -strict experimental -f flv rtmp://a.rtmp.youtube.com/live2/[your-secret-key-here]
This method might work alright through the command line, but whenever I tried to incorporate it into python code, it wouldn't work. There seems to be an issue with avconv and Youtube.
The program that worked the best for me was FFMpeg. Most of you may know that avconv is a 99% compatible replacement for FFMpeg, but Youtube streaming seems to fall within that 1%. Those of you that have worked with FFMpeg on the Raspberry Pi before know how difficult (and how long) it can be to install. I clocked it at just slightly over an hour from beginning to end using a Raspberry Pi 3 using the steps below (based on these steps https://github.com/tgogos/rpi_ffmpeg).
sudo sh -c 'echo "deb http://www.deb-multimedia.org jessie main non-free" >> /etc/apt/sources.list.d/deb-multimedia.list' sudo sh -c 'echo "deb-src http://www.deb-multimedia.org jessie main non-free" >> /etc/apt/sources.list.d/deb-multimedia.list' sudo apt-get update sudo apt-get install deb-multimedia-keyring sudo apt-get update sudo apt-get install build-essential libmp3lame-dev libvorbis-dev libtheora-dev libspeex-dev yasm libopenjpeg-dev libx264-dev libogg-dev cd ~ sudo git clone git://git.videolan.org/x264 cd x264/ sudo ./configure --host=arm-unknown-linux-gnueabi --enable-static --disable-opencl sudo make -j4 sudo make install cd ~ sudo git clone https://github.com/FFmpeg/FFmpeg.git cd FFmpeg/ sudo ./configure --arch=armel --target-os=linux --enable-gpl --enable-libx264 --enable-nonfree sudo make -j4 sudo make install
Now that you're back from a nice break after letting all this install, we can finally write a simple streaming script to test it out. Basically we can use the subprocess PIPE command to emulate entering the command in a terminal and then "pipe" the camera stream through FFMpeg to Youtube.
Getting the FFMpeg command just right is kinda tricky, but the one I used below seems to work well for me. The value that you might need to adjust is the itsoffset value. It's what helps sync the audio with the video. It basically "offsets" the video by a number in seconds. In my example, the offset is for 5.5 seconds. So if your audio and video or out of sync, you can try adjusting this number.
NOTE*** If you start getting a lot of "Alsa X Buffer" errors, then that means your itsoffest is probably too high. Adjusting it to lower will fix this issue.
Depending on the type of microphone your using, you may also need to change the hw value in the script. Mine is listed as card 1, so my hw value is 1,0 below. You can find what your audio device is listed as by typing the command:
This will list all of your recording devices and tell you what card number they're listed as.
#!/usr/bin/env python3 import subprocess import picamera import time YOUTUBE="rtmp://a.rtmp.youtube.com/live2/" KEY= #ENTER PRIVATE KEY HERE# stream_cmd = 'ffmpeg -f h264 -r 25 -i - -itsoffset 5.5 -fflags nobuffer -f alsa -ac 1 -i hw:1,0 -vcodec copy -acodec aac -ac 1 -ar 8000 -ab 32k -map 0:0 -map 1:0 -strict experimental -f flv ' + YOUTUBE + KEY stream_pipe = subprocess.Popen(stream_cmd, shell=True, stdin=subprocess.PIPE) camera = picamera.PiCamera(resolution=(640, 480), framerate=25) try: now = time.strftime("%Y-%m-%d-%H:%M:%S") camera.framerate = 25 camera.vflip = True camera.hflip = True camera.start_recording(stream.stdin, format='h264', bitrate = 2000000) while True: camera.wait_recording(1) except KeyboardInterrupt: camera.stop_recording() finally: camera.close() stream.stdin.close() stream.wait() print("Camera safely shut down") print("Good bye")
With the touchscreen, camera, and Youtube streaming all working, all we need now is a simple interface to control it. My vision is to have it so that you can preview the camera, using the touchscreen as a view finder, and then click a button to stream it to Youtube.
The vision I have is very similar to the Adafruit Pi Camera project. I like it because it uses the camera feed as the background of the touchscreen. It basically uses the io.BytesIO library to stream the buffer of camera images to use as background images on the touchscreen.
We want to have 3 buttons on the touchscreen:
- Stream - Pressing this will stream the camera and audio to Youtube
- Preview - Pressing this will show a preview of the camera on the LCD screen
- Power - Pressing this will shutdown the Operating System
So combining that with our streaming code, and our touchscreen code, I came up with something like this:
#!/usr/bin/env python import os import time import io import pygame import picamera import subprocess os.environ['SDL_VIDEODRIVER'] = 'fbcon' os.environ['SDL_FBDEV'] = '/dev/fb1' os.environ['SDL_MOUSEDEV'] = '/dev/input/touchscreen' os.environ['SDL_MOUSEDRV'] = 'TSLIB' pygame.init() lcd = pygame.display.set_mode((0,0), pygame.FULLSCREEN) pygame.mouse.set_visible(False) img_bg = pygame.image.load('/home/pi/camera_bg.jpg') preview_toggle = 0 stream_toggle = 0 blue = 26, 0, 255 white = 255, 255, 255 cream = 254, 255, 250 YOUTUBE="rtmp://a.rtmp.youtube.com/live2/" KEY= #ENTER PRIVATE KEY HERE# stream_cmd = 'ffmpeg -f h264 -r 25 -i - -itsoffset 5.5 -fflags nobuffer -f alsa -ac 1 -i hw:1,0 -vcodec copy -acodec aac -ac 1 -ar 8000 -ab 32k -map 0:0 -map 1:0 -strict experimental -f flv ' + YOUTUBE + KEY stream_pipe = subprocess.Popen(stream_cmd, shell=True, stdin=subprocess.PIPE) camera = picamera.PiCamera() camera.resolution = (1080, 720) camera.rotation = 180 camera.crop = (0.0, 0.0, 1.0, 1.0) camera.framerate = 25 rgb = bytearray(camera.resolution * camera.resolution * 3) def make_button(text, xpo, ypo, color): font=pygame.font.Font(None,24) label=font.render(str(text),1,(color)) lcd.blit(label,(xpo,ypo)) pygame.draw.rect(lcd, cream, (xpo-5,ypo-5,150,35),1) def stream(): camera.wait_recording(1) def shutdown_pi(): os.system("sudo shutdown -h now") def preview(): stream = io.BytesIO() camera.vflip = True camera.hflip = True camera.capture(stream, use_video_port=True, format='rgb', resize=(320, 240)) stream.seek(0) stream.readinto(rgb) stream.close() img = pygame.image.frombuffer(rgb[0:(320 * 240 * 3)], (320, 240), 'RGB') lcd.blit(img, (0,0)) make_button("STOP", 175,200, white) pygame.display.update() try: while True: if stream_toggle == 1: stream() elif preview_toggle == 1: preview() else: click_count = 0 lcd.fill(blue) lcd.blit(img_bg,(0,0)) make_button("STREAM", 5, 200, white) make_button("PREVIEW",175,200, white) make_button("POWER", 200, 5, white) pygame.display.update() for event in pygame.event.get(): if (event.type == pygame.MOUSEBUTTONDOWN): pos = pygame.mouse.get_pos() if (event.type == pygame.MOUSEBUTTONUP): pos = pygame.mouse.get_pos() print pos x,y = pos if y > 100: if x < 200: print "stream pressed" if stream_toggle == 0 and preview_toggle == 0: stream_toggle = 1 lcd.fill(blue) lcd.blit(img_bg,(0,0)) make_button("STOP", 20, 200, white) pygame.display.update() camera.vflip=True camera.hflip = True camera.start_recording(stream_pipe.stdin, format='h264', bitrate = 2000000) elif preview_toggle == 1: preview_toggle = 0 lcd.fill(blue) lcd.blit(img_bg,(0,0)) make_button("STREAM", 5, 200, white) make_button("PREVIEW",175,200, white) pygame.display.update() else: stream_toggle = 0 lcd.fill(blue) make_button("STREAM", 5, 200, white) make_button("PREVIEW",175,200, white) pygame.display.update() camera.stop_recording() elif x > 225: print "preview pressed" if preview_toggle == 0 and stream_toggle == 0: preview_toggle = 1 lcd.fill(blue) make_button("STOP", 175,200, white) pygame.display.update() elif stream_toggle == 1: stream_toggle = 0 lcd.fill(blue) make_button("STREAM", 5, 200, white) make_button("PREVIEW",175,200, white) pygame.display.update() camera.stop_recording() else: preview_toggle = 0 lcd.fill(blue) make_button("STREAM", 5, 200, white) make_button("PREVIEW",175,200, white) pygame.display.update() except KeyboardInterrupt: camera.stop_recording() print ' Exit Key Pressed' finally: camera.close() stream_pipe.stdin.close() stream_pipe.wait() print("Camera safely shut down") print("Good bye")
Customize the script the way you want to make it work for you, and then test it out. Just run it using
sudo python youtube_stream.py
Then click on the "Preview" button to preview your camera. Press "Stop" to go back to the main menu. Then click the "Stream" button to start streaming to Youtube. Now you should be able to go to your Youtube Live dashboard and preview your live stream!
Once you're happy with the results, the last thing to do is to make the script executable and add it to the Pi's rc.local file so that it auto-launches whenever the Pi boots up
sudo chmod +x youtube_stream.py sudo nano /etc/rc.local
Towards the bottom of the rc.local file, right before "exit 0", add this line:
sudo python /home/pi/youtube_stream.py &
Save it and reboot the Pi. Once it's through rebooting, you should see your new touchscreen interface!
Obviously we want to make this camera portable, so we're going to need a battery. I like to use "emergency cell phone chargers" because they can last for a while, they can be recharged, and a lot of them even have a built in USB cable that can plug directly into the Pi!
The only thing one of these battery banks doesn't have is an an/off switch. There are a couple ways to go about this: You could either solder a switch onto the battery bank yourself, or you could buy a premade micro-usb on/off switch, which is a much safer way to go than soldering near a lithium polymer battery.
This will take care of turning the power on and off, but it's generally not a good idea to cut the power to a Pi while it's running software. This can corrupt the software and/or the Pi itself. You eagle eyed code monkeys may have noticed that the code above generates a "Power" button that can shut down the Raspberry Pi OS.
So you can switch the USB cable "on" to power on the Pi, and then touch the "Power" touchscreen button to turn off the Raspberry Pi OS before switching the USB cable to "off".
The finishing touch for this project is 3D printing an epic case to contain everything! There are plenty of great CAD programs out there, but for simplicities sake, I'm using Tinkercad. You'll want to 3D print the enclosure using supports.
The LCD, Raspberry Pi and Camera should basically snap into place, as long as you're using the same parts as me. The LiPo battery will sit in between the camera and the Pi, but you won't be able to plug the battery directly into the USB port of the Pi because there isn't enough room in the case. As an alternative, we can strip the wires coming from the battery and solder them to the PP2 and PP5 ports on the back of the Raspberry Pi Micro USB port.
I used hot glue to hold everything into place within the camera case, but DO NOT USE HOT GLUE DIRECTLY ON THE LIPO BATTERY!!! Once all the pieces are together, you can test it out. If it works, you can hot glue the two halves of the case together!
Finally, you can add a little bit of white paint to make it look more like a Youtube logo, and then start streaming!