Android ICS and MJPEG using AsyncTask

28,396

Solution 1

nice work! For your problem with onResume(), isn't it enough when you move the following code from onCreate() to onResume()?

    //sample public cam 
    String URL = "http://trackfield.webcam.oregonstate.edu/axis-cgi/mjpg/video.cgi?resolution=800x600&amp%3bdummy=1333689998337"; 

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

    new DoRead().execute(URL); 

Then you simply recreate the View and new instance of the AsyncTask... I tried it and it works for me...

Solution 2

It'll be helpful for the newbies that if you want to access your ip camera having a username or password , you might want to add this to your DefaultHttpClient and the above code will work for cameras that require authentication

 CredentialsProvider provider = new BasicCredentialsProvider();
            UsernamePasswordCredentials credentials = new UsernamePasswordCredentials("yourusername", "yourpassword");
            provider.setCredentials(AuthScope.ANY, credentials);
            DefaultHttpClient httpclient = new DefaultHttpClient();
            httpclient.setCredentialsProvider(provider);
Share:
28,396
bbodenmiller
Author by

bbodenmiller

Updated on July 30, 2020

Comments

  • bbodenmiller
    bbodenmiller almost 4 years

    I modified the MJPEG viewer code from Android and MJPEG to work using an AsyncTask (and thus work on Ice Cream Sandwich (ICS), 4.0.4) and here is my code.

    If anyone has any suggestions on how to optimize, cleanup, or do something more proper with the code please let me know. Two issues I'd appreciate help addressing:

    • If you have the device on a stream then lock the screen and unlock the screen it does not resume playing until you either kill and resume the app or rotate the screen. All my attempts at using OnResume() to do something or other resulted in app crashes.

    • In particular I'd like to get the AsyncTask back in MjpegInputStream.java but was not able to get that to work.

    MjpegActivity.java:

    package com.demo.mjpeg;
    
    import java.io.IOException;
    import java.net.URI;
    
    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 com.demo.mjpeg.MjpegView.MjpegInputStream;
    import com.demo.mjpeg.MjpegView.MjpegView;
    import android.app.Activity;
    import android.os.AsyncTask;
    import android.os.Bundle;
    import android.util.Log;
    import android.view.Window;
    import android.view.WindowManager;
    import android.widget.Toast;
    
    public class MjpegActivity extends Activity {
        private static final String TAG = "MjpegActivity";
    
        private MjpegView mv;
    
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
    
            //sample public cam
            String URL = "http://trackfield.webcam.oregonstate.edu/axis-cgi/mjpg/video.cgi?resolution=800x600&amp%3bdummy=1333689998337";
    
            requestWindowFeature(Window.FEATURE_NO_TITLE);
            getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, 
            WindowManager.LayoutParams.FLAG_FULLSCREEN);
    
            mv = new MjpegView(this);
            setContentView(mv);        
    
            new DoRead().execute(URL);
        }
    
        public void onPause() {
            super.onPause();
            mv.stopPlayback();
        }
    
        public class DoRead extends AsyncTask<String, Void, MjpegInputStream> {
            protected MjpegInputStream doInBackground(String... url) {
                //TODO: if camera has authentication deal with it and don't just not work
                HttpResponse res = null;
                DefaultHttpClient httpclient = new DefaultHttpClient();     
                Log.d(TAG, "1. Sending http request");
                try {
                    res = httpclient.execute(new HttpGet(URI.create(url[0])));
                    Log.d(TAG, "2. Request finished, status = " + res.getStatusLine().getStatusCode());
                    if(res.getStatusLine().getStatusCode()==401){
                        //You must turn off camera User Access Control before this will work
                        return null;
                    }
                    return new MjpegInputStream(res.getEntity().getContent());  
                } catch (ClientProtocolException e) {
                    e.printStackTrace();
                    Log.d(TAG, "Request failed-ClientProtocolException", e);
                    //Error connecting to camera
                } catch (IOException e) {
                    e.printStackTrace();
                    Log.d(TAG, "Request failed-IOException", e);
                    //Error connecting to camera
                }
    
                return null;
            }
    
            protected void onPostExecute(MjpegInputStream result) {
                mv.setSource(result);
                mv.setDisplayMode(MjpegView.SIZE_BEST_FIT);
                mv.showFps(true);
            }
        }
    }
    

    MjpegInputStream.java:

    package com.demo.mjpeg.MjpegView;
    
    import java.io.BufferedInputStream;
    import java.io.ByteArrayInputStream;
    import java.io.DataInputStream;
    import java.io.IOException;
    import java.io.InputStream;
    import java.util.Properties;
    
    import android.graphics.Bitmap;
    import android.graphics.BitmapFactory;
    import android.util.Log;
    
    public class MjpegInputStream extends DataInputStream {
        private static final String TAG = "MjpegInputStream";
    
        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 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) { 
                nfe.getStackTrace();
                Log.d(TAG, "catch NumberFormatException hit", nfe);
                mContentLength = getEndOfSeqeunce(this, EOF_MARKER); 
            }
            reset();
            byte[] frameData = new byte[mContentLength];
            skipBytes(headerLen);
            readFully(frameData);
            return BitmapFactory.decodeStream(new ByteArrayInputStream(frameData));
        }
    }
    

    MjpegView.java:

    package com.demo.mjpeg.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.util.Log;
    import android.view.SurfaceHolder;
    import android.view.SurfaceView;
    
    public class MjpegView extends SurfaceView implements SurfaceHolder.Callback {
        private static final String TAG = "MjpegView";
    
        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) {
                                    e.getStackTrace();
                                    Log.d(TAG, "catch IOException hit in run", 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) {
                    e.getStackTrace();
                    Log.d(TAG, "catch IOException hit in stopPlayback", 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; 
        }
    }
    
  • bbodenmiller
    bbodenmiller about 12 years
    Yeah I actually fixed it by moving a bunch of stuff to onResume(). There are also a number of other bugs I found in this code posted here (which was based on even buggier code) that I will post as soon as I get them all fixed. The first one that comes to mind is that in the try block it needs to check if c is null. Another one is that there is no error handling for a source going down midstream.
  • DC84
    DC84 about 12 years
    You are right... how about your experience on stability? I have noticed on some devices that it can happen that they become unresponsive (CPU @ 100%) using this code... have you ever experienced such problems?
  • bbodenmiller
    bbodenmiller about 12 years
    I've only been able to test using emulator and a Galaxy Nexus but its certainly not the most stable code in the world. I think I may have improved on that area slightly, I will see what I can get fixed and posted today. If your mjpeg stream is giving you a lot of frames per second it will be a rather poor and slow stream on the phone... I had to tone my stream down a bit. 10 fps seems to work okay, anything more I think is just too much processing for the phone to do given this isn't hardware accelerated.
  • DC84
    DC84 about 12 years
    Thanks for this information, I will make some tests with different cameras. It would be nice to have a look at your improved code, please let me know if you plan to post it here!
  • bbodenmiller
    bbodenmiller about 12 years
    I'll get to it but it might take a few weeks as a lot of other projects are in crunch time at that moment.
  • DC84
    DC84 about 11 years
    Hi there, it really passed some time in the meantime ;-) I just wanted to ask if you have worked on the above code, as mentioned in the past? I would really be interested if you were able to improve the solution! Thx for your Feedback, bye!
  • Juan Pedro Martinez
    Juan Pedro Martinez over 10 years
    Me too, I am really interested to see if you could improve the code above. I have a lot issues when the reception is low. I am looking for improve it. Could you give us some advices? Thanks you!
  • bbodenmiller
    bbodenmiller over 10 years
    It is unlikely that I will ever work on this again as the application is now obsolete. Consider trying @neuralassembly's code mentioned in the comments of the original question.
  • DC84
    DC84 over 10 years
    OK, anyways thank you for the feedback and your help in the past!
  • Ahmed
    Ahmed over 9 years
    What can we do for ip cameras with usernames and passwords? i'm using this url "admin:[email protected]:81/…" but getting 401, Unauthorized