8: WiFi live streaming Raspberry Pi camera (Android)

In this tutorial, we are going to use Raspberry Pi camera to stream video to Android device via WIFI. The real-time video can be viewed by many Android devices simultaneously as long as they are connected to the same WIFI source. 

You need a Raspberry Pi 4B + Raspberry Pi CSI camera module + Android smartphone.

Step 1: Insert the Pi CSI camera to Raspberry Pi 4B.

Step 2: Open "Raspberry Pi Configuration". Click "Interface" to enable Camera, SSH, I2C and SPI. Then reboot the Raspberry Pi. 


Step 3: Find your own IP address by typing "hostname -I". You should see 192.168.xx.xxx

Step 4: Write python code in Thonny and save it as app.py . Focus on the yellow sentences carefully.  Port 8000 is chosen. 



import io
import picamera
import logging
import socketserver
from threading import Condition
from http import server

PAGE="""\
<html>
<head>
<title>Raspberry Pi - Surveillance Camera</title>
</head>
<body>
<center><h1>Raspberry Pi </h1></center>
<center><img src="stream.mjpg" width="640" height="480"></center>
</body>
</html>
"""
        
class StreamingOutput(object):
    def __init__(self):
        self.frame = None
        self.buffer = io.BytesIO()
        self.condition = Condition()    

    def write(self, buf):
        if buf.startswith(b'\xff\xd8'):
            # New frame, copy the existing buffer's content and notify all
            # clients it's available
            self.buffer.truncate()
            with self.condition:
                self.frame = self.buffer.getvalue()
                self.condition.notify_all()
            self.buffer.seek(0)
        return self.buffer.write(buf)

class StreamingHandler(server.BaseHTTPRequestHandler):
    def do_GET(self):
        if self.path == '/':
            self.send_response(301)
            self.send_header('Location', '/index.html')
            self.end_headers()
        elif self.path == '/index.html':
            content = PAGE.encode('utf-8')
            self.send_response(200)
            self.send_header('Content-Type', 'text/html')
            self.send_header('Content-Length', len(content))
            self.end_headers()
            self.wfile.write(content)
        elif self.path == '/stream.mjpg':
            self.send_response(200)
            self.send_header('Age', 0)
            self.send_header('Cache-Control', 'no-cache, private')
            self.send_header('Pragma', 'no-cache')
            self.send_header('Content-Type', 'multipart/x-mixed-replace; boundary=FRAME')
            self.end_headers()
            try:
                while True:
                    with output.condition:
                        output.condition.wait()
                        frame = output.frame
                    self.wfile.write(b'--FRAME\r\n')
                    self.send_header('Content-Type', 'image/jpeg')
                    self.send_header('Content-Length', len(frame))
                    self.end_headers()
                    self.wfile.write(frame)
                    self.wfile.write(b'\r\n')
            except Exception as e:
                logging.warning(
                    'Removed streaming client %s: %s',
                    self.client_address, str(e))
        else:
            self.send_error(404)
            self.end_headers()

class StreamingServer(socketserver.ThreadingMixIn, server.HTTPServer):
    allow_reuse_address = True
    daemon_threads = True

with picamera.PiCamera(resolution='640x480', framerate=24) as camera:
    output = StreamingOutput()
    #Uncomment the next line if you want to take a picture when the camera is activated 
    #camera.capture("picture.png")  
    #Uncomment the next line if you need to change the orientation of camera
    #camera.rotation = 90
    camera.start_recording(output, format='mjpeg')
    try:
        address = ('', 8000)
        server = StreamingServer(address, StreamingHandler)
        server.serve_forever()
    finally:
        camera.stop_recording()

Step 5: Write android app

The code is simple but you have to download and install Android Studio first.

After the android project is created, search if MainActivity.Java, activity_main.xml and AndroidManifest.xml exist.



Edit activity_main.xml as follows


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">
    
    <LinearLayout
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="30dp">        
        <ProgressBar
            android:id="@+id/myProgressBar"
            android:layout_weight="0.1"
            style="@style/Widget.AppCompat.ProgressBar.Horizontal"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>
        <ImageView
            android:id="@+id/myImageView"
            android:src="@mipmap/ic_launcher"
            android:layout_weight="0.9"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>
    </LinearLayout>

    <WebView
        android:id="@+id/myWebView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>



Edit MainActivity.Java as follows. As port 8000 is chosen in step 4, you must write :8000 after your IP address: 192.168.xx.xxx

package com.example.browser;

import androidx.appcompat.app.AppCompatActivity;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.webkit.WebChromeClient;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.ImageView;
import android.widget.ProgressBar;

public class MainActivity extends AppCompatActivity
{
    ProgressBar superProgressBar;
    ImageView superImageView;
    WebView superWebView;

    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        superProgressBar=findViewById(R.id.myProgressBar);
        superImageView=findViewById(R.id.myImageView);
        superWebView=findViewById(R.id.myWebView);

        superProgressBar.setMax(100);
        superWebView.loadUrl("http://192.168.xx.xxx:8000");
        superWebView.getSettings().setJavaScriptEnabled(true);

        superWebView.getSettings().setLoadWithOverviewMode(true);
        superWebView.getSettings().setUseWideViewPort(true);
        superWebView.setScrollBarStyle(WebView.SCROLLBARS_OUTSIDE_OVERLAY);
        superWebView.setScrollbarFadingEnabled(true);

        superWebView.setWebViewClient(new WebViewClient());
        superWebView.setWebChromeClient(new WebChromeClient()
        {
            @Override
            public void onProgressChanged(WebView view, int newProgress)
            {
                super.onProgressChanged(view, newProgress);
                superProgressBar.setProgress(newProgress);
            }

            @Override
            public void onReceivedTitle(WebView view, String title)
            {
                super.onReceivedTitle(view, title);
                getSupportActionBar().setTitle(title);
            }

            @Override
            public void onReceivedIcon(WebView view, Bitmap icon)
            {
                super.onReceivedIcon(view, icon);
                superImageView.setImageBitmap(icon);
            }
        });
    }

    @Override
    public void onBackPressed()
    {
        if(superWebView.canGoBack())
        {
            superWebView.goBack();
        }
        else        {
            finish();
        }
    }
}

Edit AndroidManifest.xml as follows


<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.browser">    
    <uses-permission android:name="android.permission.INTERNET"/>
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>


Step 6: Build apk

Click "Build" and find "Generate Signed Bundle or APK". Click "Next"



Create key store and fill your information. Then click "Next"



Click "release" and install the apk file in your Android device.
Choose V2 if the Android device is running Android version 5.0 or above. Otherwise please choose V1. If your old Android device was upgraded to version 5.0 or above by yourself, you should choose V1 too.     




Step 7: Run the python code and the Android app. 








Let's stream multiple devices in parallel. The screen on L.H.S is Window device. The screen on R.H.S is Linux device. The mini screen at the middle is Android device.  



*The above code are modified from open source platform 



Comments

  1. How would I change the code to work with a USB camera inside of a pi camera?


    ReplyDelete

Post a Comment