29

I need to get the mjpeg stream from an IP camera, anyone know the right way do it? I googled a bit and I find this example

http://www.anddev.org/mjpeg_on_android_anyone-t1871.html

but I've been stucked when I tried to get the stream from another activity called by the main activity. Here the code:

Main acitivity

package com.test;


public class IntentTest extends Activity {
    /** Called when the activity is first created. */
    ListView myListView = null;
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        myListView = (ListView)findViewById(R.id.listView);
        final ArrayList<String> items = new ArrayList<String>();
        items.add("00408C944B9A");
        final ArrayAdapter<String> aa;
        aa = new ArrayAdapter<String>(this,
                android.R.layout.simple_list_item_1,
                items);

        myListView.setAdapter(aa);
        myListView.setOnItemClickListener(listClicked);

    }


    private OnItemClickListener listClicked = new OnItemClickListener() {
        public void onItemClick(AdapterView<?> arg0, View arg1, int position, long id) {
            // TODO Auto-generated method stub
            Intent i = new Intent(IntentTest.this, OtherActivity.class);
            i.putExtra("MAC", myListView.getItemAtPosition(position).toString());
            startActivity(i);         
        }
    };
}

Second activity

package com.test;

import com.test.mjpeg.mjpegsample.MjpegView.*;
import com.test.parser.JSONParse;


public class OtherActivity extends Activity {
    /** Called when the activity is first created. */
    private MjpegView mv;
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        Bundle extras = getIntent().getExtras();
        if (extras != null){
            String mac = (String)extras.get("MAC");
            Log.i("Other", "---->" + mac);
            TextView tv = (TextView)findViewById(R.id.textView);
            tv.setText(mac);


            String URL = "myurl";

            requestWindowFeature(Window.FEATURE_NO_TITLE);
            getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, 
                    WindowManager.LayoutParams.FLAG_FULLSCREEN);

            mv = new MjpegView(this);
            setContentView(mv);        

            mv.setSource(MjpegInputStream.read(URL));
            mv.setDisplayMode(MjpegView.SIZE_BEST_FIT);
            mv.showFps(true);      

        }
    }

    public void onPause() {
        super.onPause();
        mv.stopPlayback();
    }


}
bbodenmiller
  • 3,101
  • 5
  • 34
  • 50
bl4d3
  • 345
  • 1
  • 5
  • 8

3 Answers3

40

I found this code over the internet some time ago, maybe it will be of some help to you.

MjpegSample Class

package de.mjpegsample;

import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.Window;
import android.view.WindowManager;
import de.mjpegsample.MjpegView.MjpegInputStream;
import de.mjpegsample.MjpegView.MjpegView;

public class MjpegSample extends Activity {

    private MjpegView mv;
    private static final int MENU_QUIT = 1;

    /* Creates the menu items */
    public boolean onCreateOptionsMenu(Menu menu) {    
    menu.add(0, MENU_QUIT, 0, "Quit");
    return true;
    }

    /* Handles item selections */
    public boolean onOptionsItemSelected(MenuItem item) {    
        switch (item.getItemId()) {
            case MENU_QUIT:
                finish();
                return true;    
            }    
        return false;
    }

    public void onCreate(Bundle icicle) {
        super.onCreate(icicle);
        //sample public cam
        String URL = "http://gamic.dnsalias.net:7001/img/video.mjpeg";

        requestWindowFeature(Window.FEATURE_NO_TITLE);
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN, WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);
        mv = new MjpegView(this);
        setContentView(mv);        
        mv.setSource(MjpegInputStream.read(URL));
        mv.setDisplayMode(MjpegView.SIZE_BEST_FIT);
        mv.showFps(false);
    }

    public void onPause() {
        super.onPause();
        mv.stopPlayback();
    }
}

MjpegView Class

package de.mjpegsample.MjpegView;

import java.io.IOException;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.util.AttributeSet;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

public class MjpegView extends SurfaceView implements SurfaceHolder.Callback {
    public final static int POSITION_UPPER_LEFT  = 9;
    public final static int POSITION_UPPER_RIGHT = 3;
    public final static int POSITION_LOWER_LEFT  = 12;
    public final static int POSITION_LOWER_RIGHT = 6;

    public final static int SIZE_STANDARD   = 1; 
    public final static int SIZE_BEST_FIT   = 4;
    public final static int SIZE_FULLSCREEN = 8;

    private MjpegViewThread thread;
    private MjpegInputStream mIn = null;    
    private boolean showFps = false;
    private boolean mRun = false;
    private boolean surfaceDone = false;    
    private Paint overlayPaint;
    private int overlayTextColor;
    private int overlayBackgroundColor;
    private int ovlPos;
    private int dispWidth;
    private int dispHeight;
    private int displayMode;

    public class MjpegViewThread extends Thread {
        private SurfaceHolder mSurfaceHolder;
        private int frameCounter = 0;
        private long start;
        private Bitmap ovl;

        public MjpegViewThread(SurfaceHolder surfaceHolder, Context context) { mSurfaceHolder = surfaceHolder; }

        private Rect destRect(int bmw, int bmh) {
            int tempx;
            int tempy;
            if (displayMode == MjpegView.SIZE_STANDARD) {
                tempx = (dispWidth / 2) - (bmw / 2);
                tempy = (dispHeight / 2) - (bmh / 2);
                return new Rect(tempx, tempy, bmw + tempx, bmh + tempy);
            }
            if (displayMode == MjpegView.SIZE_BEST_FIT) {
                float bmasp = (float) bmw / (float) bmh;
                bmw = dispWidth;
                bmh = (int) (dispWidth / bmasp);
                if (bmh > dispHeight) {
                    bmh = dispHeight;
                    bmw = (int) (dispHeight * bmasp);
                }
                tempx = (dispWidth / 2) - (bmw / 2);
                tempy = (dispHeight / 2) - (bmh / 2);
                return new Rect(tempx, tempy, bmw + tempx, bmh + tempy);
            }
            if (displayMode == MjpegView.SIZE_FULLSCREEN) return new Rect(0, 0, dispWidth, dispHeight);
            return null;
        }

        public void setSurfaceSize(int width, int height) {
            synchronized(mSurfaceHolder) {
                dispWidth = width;
                dispHeight = height;
            }
        }

        private Bitmap makeFpsOverlay(Paint p, String text) {
            Rect b = new Rect();
            p.getTextBounds(text, 0, text.length(), b);
            int bwidth  = b.width()+2;
            int bheight = b.height()+2;
            Bitmap bm = Bitmap.createBitmap(bwidth, bheight, Bitmap.Config.ARGB_8888);
            Canvas c = new Canvas(bm);
            p.setColor(overlayBackgroundColor);
            c.drawRect(0, 0, bwidth, bheight, p);
            p.setColor(overlayTextColor);
            c.drawText(text, -b.left+1, (bheight/2)-((p.ascent()+p.descent())/2)+1, p);
            return bm;           
        }

        public void run() {
            start = System.currentTimeMillis();
            PorterDuffXfermode mode = new PorterDuffXfermode(PorterDuff.Mode.DST_OVER);
            Bitmap bm;
            int width;
            int height;
            Rect destRect;
            Canvas c = null;
            Paint p = new Paint();
            String fps = "";
            while (mRun) {
                if(surfaceDone) {
                    try {
                        c = mSurfaceHolder.lockCanvas();
                        synchronized (mSurfaceHolder) {
                            try {
                                bm = mIn.readMjpegFrame();
                                destRect = destRect(bm.getWidth(),bm.getHeight());
                                c.drawColor(Color.BLACK);
                                c.drawBitmap(bm, null, destRect, p);
                                if(showFps) {
                                    p.setXfermode(mode);
                                    if(ovl != null) {
                                        height = ((ovlPos & 1) == 1) ? destRect.top : destRect.bottom-ovl.getHeight();
                                        width  = ((ovlPos & 8) == 8) ? destRect.left : destRect.right -ovl.getWidth();
                                        c.drawBitmap(ovl, width, height, null);
                                    }
                                    p.setXfermode(null);
                                    frameCounter++;
                                    if((System.currentTimeMillis() - start) >= 1000) {
                                        fps = String.valueOf(frameCounter)+"fps";
                                        frameCounter = 0; 
                                        start = System.currentTimeMillis();
                                        ovl = makeFpsOverlay(overlayPaint, fps);
                                    }
                                }
                            } catch (IOException e) {}
                        }
                    } finally { if (c != null) mSurfaceHolder.unlockCanvasAndPost(c); }
                }
            }
        }
    }

    private void init(Context context) {
        SurfaceHolder holder = getHolder();
        holder.addCallback(this);
        thread = new MjpegViewThread(holder, context);
        setFocusable(true);
        overlayPaint = new Paint();
        overlayPaint.setTextAlign(Paint.Align.LEFT);
        overlayPaint.setTextSize(12);
        overlayPaint.setTypeface(Typeface.DEFAULT);
        overlayTextColor = Color.WHITE;
        overlayBackgroundColor = Color.BLACK;
        ovlPos = MjpegView.POSITION_LOWER_RIGHT;
        displayMode = MjpegView.SIZE_STANDARD;
        dispWidth = getWidth();
        dispHeight = getHeight();
    }

    public void startPlayback() { 
        if(mIn != null) {
            mRun = true;
            thread.start();         
        }
    }

    public void stopPlayback() { 
        mRun = false;
        boolean retry = true;
        while(retry) {
            try {
                thread.join();
                retry = false;
            } catch (InterruptedException e) {}
        }
    }

    public MjpegView(Context context, AttributeSet attrs) { super(context, attrs); init(context); }
    public void surfaceChanged(SurfaceHolder holder, int f, int w, int h) { thread.setSurfaceSize(w, h); }

    public void surfaceDestroyed(SurfaceHolder holder) { 
        surfaceDone = false; 
        stopPlayback(); 
    }

    public MjpegView(Context context) { 
        super(context); 
        init(context); 
        }    
    public void surfaceCreated(SurfaceHolder holder) { 
        surfaceDone = true; 
        }
    public void showFps(boolean b) { 
        showFps = b; 
        }
    public void setSource(MjpegInputStream source) { 
        mIn = source; 
        startPlayback();
        }
    public void setOverlayPaint(Paint p) { 
        overlayPaint = p; 
        }
    public void setOverlayTextColor(int c) { 
        overlayTextColor = c; 
        }
    public void setOverlayBackgroundColor(int c) { 
        overlayBackgroundColor = c; 
        }
    public void setOverlayPosition(int p) { 
        ovlPos = p; 
        }
    public void setDisplayMode(int s) { 
        displayMode = s; 
        }
}

MjpegInputStream Class

package de.mjpegsample.MjpegView;

import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.util.Properties;

import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;

public class MjpegInputStream extends DataInputStream {
    private final byte[] SOI_MARKER = { (byte) 0xFF, (byte) 0xD8 };
    private final byte[] EOF_MARKER = { (byte) 0xFF, (byte) 0xD9 };
    private final String CONTENT_LENGTH = "Content-Length";
    private final static int HEADER_MAX_LENGTH = 100;
    private final static int FRAME_MAX_LENGTH = 40000 + HEADER_MAX_LENGTH;
    private int mContentLength = -1;

    public static MjpegInputStream read(String url) {
        HttpResponse res;
        DefaultHttpClient httpclient = new DefaultHttpClient();     
        try {
            res = httpclient.execute(new HttpGet(URI.create(url)));
            return new MjpegInputStream(res.getEntity().getContent());              
        } catch (ClientProtocolException e) {
        } catch (IOException e) {}
        return null;
    }

    public MjpegInputStream(InputStream in) { super(new BufferedInputStream(in, FRAME_MAX_LENGTH)); }

    private int getEndOfSeqeunce(DataInputStream in, byte[] sequence) throws IOException {
        int seqIndex = 0;
        byte c;
        for(int i=0; i < FRAME_MAX_LENGTH; i++) {
            c = (byte) in.readUnsignedByte();
            if(c == sequence[seqIndex]) {
                seqIndex++;
                if(seqIndex == sequence.length) return i + 1;
            } else seqIndex = 0;
        }
        return -1;
    }

    private int getStartOfSequence(DataInputStream in, byte[] sequence) throws IOException {
        int end = getEndOfSeqeunce(in, sequence);
        return (end < 0) ? (-1) : (end - sequence.length);
    }

    private int parseContentLength(byte[] headerBytes) throws IOException, NumberFormatException {
        ByteArrayInputStream headerIn = new ByteArrayInputStream(headerBytes);
        Properties props = new Properties();
        props.load(headerIn);
        return Integer.parseInt(props.getProperty(CONTENT_LENGTH));
    }   

    public Bitmap readMjpegFrame() throws IOException {
        mark(FRAME_MAX_LENGTH);
        int headerLen = getStartOfSequence(this, SOI_MARKER);
        reset();
        byte[] header = new byte[headerLen];
        readFully(header);
        try {
            mContentLength = parseContentLength(header);
        } catch (NumberFormatException nfe) { 
            mContentLength = getEndOfSeqeunce(this, EOF_MARKER); 
        }
        reset();
        byte[] frameData = new byte[mContentLength];
        skipBytes(headerLen);
        readFully(frameData);
        return BitmapFactory.decodeStream(new ByteArrayInputStream(frameData));
    }
}

If you need any more info, let me know, I'll help in any way I can.

FYI: I did not write SimpleMjpegView, you can find more up to date code here

owen gerig
  • 6,165
  • 6
  • 52
  • 91
Carlos Portes
  • 478
  • 6
  • 6
  • 4
    to any future people using this code I just wanted to point out my experience with it. if there is an error you will get a black screen. I had certificate problems and without the catches being filled in there was no errors posted to logcat. So all I had was a black screen. once I resolved the cert issues all worked. – owen gerig Apr 23 '12 at 14:01
  • Thanks a lot for sharing this great pieces of code - you saved my day! – herom May 08 '12 at 14:18
  • 1
    and do not forget internet permission in Manifest – Amt87 Sep 06 '12 at 09:28
  • 1
    I cant post the code here @Amt87 try this link http://stackoverflow.com/a/7253396/530933 – owen gerig Sep 06 '12 at 14:34
  • I tried this code too and i have a question, i have a lot of "grow heap", is it normal for this code? is there a way to prevent it? – Dr Glass Nov 20 '12 at 08:45
  • If we want to use multiple cameras, what should we do? So we would have a few different URLs and we need to pass the url as an arguement, so where should we pass it? – Ayse Apr 11 '13 at 09:18
  • @Carlos Portes .Hi I tried your code and its working but my problem is the streaming is very slow. I am using mjpeg streamer on my raspberry Pi to stream video on port 8080, and on the android side I am using the url like "http://192.168.1.30:8080/?action=stream". Any idea ? Thanks in advance.... – Haris Sep 10 '13 at 11:43
  • I'm trying to integrate this in a Dialog or inside a layout.. there are huge bars on top and on the bottom of the video, and I've already tried changing the SIZE_* settings. Can anyone help me on this? Thanks in advance. – Henrique de Sousa Oct 17 '13 at 11:42
  • @owengerig Can you please provide a download link for the code you fixed, this code does not work and it gives me black screen – Lily Oct 31 '13 at 05:32
  • anybody can help me this video streaming with audio too ? – Dhwanik Gandhi Aug 18 '14 at 12:05
  • when i change the frame size on the server, the image becomes purple and with weird colors, has anyone encountered that? – Ziv Kesten Feb 19 '15 at 11:45
  • @owengerig Thanks for the wonderful library, I tried to download and run the sample code and it is working perfectly, however when I tried to copy activities & libraries to an existing project it gave me error at `java.lang.UnsatisfiedLinkError: No implementation found for int com.package.MjpegInputStream.pixeltobmp`. Could you point me in right direction ? – Rohan Kandwal Feb 03 '16 at 09:42
  • @Haris I have imported the code and made necessary changes but still getting `java.lang.UnsatisfiedLinkError: No implementation found for int com.package.MjpegInputStream.pixeltobmp`. Can you help me in fixing the error? – Rohan Kandwal Feb 03 '16 at 09:44
  • 1
    make sure you have the latest of their api (https://github.com/michogar/MjpegView) also maybe try another api like https://bitbucket.org/neuralassembly/simplemjpegview . Sorry its been way to long since i posted this and dont really remember much about it (like if i had that error at some point in time or if its from updates to the framework or api) – owen gerig Feb 03 '16 at 15:11
  • @owengerig Thanks, it worked without need of any OpenCV files or anything else. Will try to post an answer with working code. – Rohan Kandwal Feb 04 '16 at 06:27
  • Love it, nice code block that gives me what I need :) – Doug Nov 20 '19 at 16:14
5

For those looking to get this working with AsyncTask (and thus working on Ice Cream Sandwich (ICS), 4.0.4) see Android ICS and MJPEG using AsyncTask.

Community
  • 1
  • 1
bbodenmiller
  • 3,101
  • 5
  • 34
  • 50
3

I had the same problem and tried using custom Mjpeg viewers. While they worked, they proved unstable and clumsy for me.

I am simply using a WebView. On Android Studio, simply drag and drop a WebView. My WebView is not covering the entire screen, but half of it.

Then, to fit the video best, in your onCreate:

If using Kotlin:

webView.settings.loadWithOverviewMode = true;
webView.settings.useWideViewPort = true;

If using Java:

webView.getSettings().setLoadWithOverviewMode(true);
webView.getSettings().setUseWideViewPort(true);

And then, on button click:

To start:

webView.loadUrl("http://$id:8081/cam.mjpeg") // start

To Stop:

webView.stopLoading()
webView.loadUrl("about:blank")

Hope this helps.

  • Currently, this solutions does not works everytime. Old MJPEG stream are not supported. If we talks about the new, yea this solution is the best and more performant than the chosen answer. But if we talks about "supports" terms, a MJPEG custom View is the best. – Murdok Jul 04 '19 at 10:34