网络摄像头Androi端显示(mjpeg)源码分析

2024-04-09 06:32

本文主要是介绍网络摄像头Androi端显示(mjpeg)源码分析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

转自:http://www.cnblogs.com/yihujiu/p/5997859.html


main.xml

复制代码
复制代码
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="fill_parent"android:layout_height="fill_parent"android:orientation="vertical" ><TextView android:id="@+id/Tips"android:layout_width="fill_parent"android:layout_height="wrap_content"android:text="@string/init_tips"android:textSize="40px"android:textColor="#00ff00"/><Button android:id="@+id/btn_network" android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="@string/login"android:textSize="40px"android:textColor="#00ff00"/><TextViewandroid:id="@+id/statc001" android:layout_width="wrap_content"android:layout_height="wrap_content"      android:textSize="40px"android:textColor="#00ff00"/><Button android:id="@+id/btn_video"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="@string/move"android:textSize="40px"/></LinearLayout>
复制代码

 

复制代码

 

flash.xml

复制代码
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent" android:gravity="center"android:orientation="vertical" ><TextViewandroid:id="@+id/hintTv"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="@string/login_hint" /><EditTextandroid:id="@+id/ip"android:hint="@string/ip_hint"android:layout_width="fill_parent"android:layout_height="wrap_content"android:gravity="center"android:text="192.168.0.10"/><EditTextandroid:id="@+id/port"android:layout_width="fill_parent"android:layout_height="wrap_content"android:text="8080"android:gravity="center"/><Buttonandroid:id="@+id/connect"android:layout_width="fill_parent"android:layout_height="40.0dip"android:layout_marginLeft="10.0dip"android:layout_marginRight="10.0dip"android:layout_marginTop="20.0dip"android:text="@string/connect"android:textColor="#ffffffff"android:textSize="18.0sp" /></LinearLayout>
复制代码

mainactivity.xml

复制代码
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="fill_parent"android:layout_height="fill_parent" ><TextView android:layout_width="fill_parent"android:layout_height="wrap_content"android:text="视频显示"/><com.mjpeg.view.MjpegViewandroid:id="@+id/mjpegview"android:layout_width="fill_parent"android:layout_height="fill_parent"/>    </RelativeLayout>
复制代码

strings.xml

复制代码
<?xml version="1.0" encoding="utf-8"?>
<resources><string name="login">连接服务器</string><string name="app_name">梧州学院图书馆刷卡入座系统</string><string name="move">视频</string><string name="init_tips">提示:请先打开WiFi或GPRS再连接网络</string><string name="people1">空座</string><string name="people2">有人</string><string name="login_hint">connecting......;</string><string name="ip">IP:</string><string name="ip_hint">请输入IP地址</string><string name="port">Port:</string><string name="port_hint">端口1000到65535</string><string name="connect">链接</string><string name="connect_failed">链接失败</string></resources>
复制代码

 

AndroidManifest.xml

复制代码
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"package="my.work.Library"android:versionCode="1"android:versionName="1.0" ><uses-sdk android:minSdkVersion="15" /><applicationandroid:icon="@drawable/ic_launcher"android:label="@string/app_name" ><activityandroid:name=".WsnActivty"android:label="@string/app_name" ><intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" /></intent-filter></activity><activityandroid:name=".FlashActivity"android:theme="@android:style/Theme.NoTitleBar.Fullscreen"android:screenOrientation="portrait" ><intent-filter><category android:name="android.intent.category.LAUNCHER" /></intent-filter></activity><activityandroid:name=".LinearLayout_activity"android:theme="@android:style/Theme.NoTitleBar.Fullscreen"android:screenOrientation="portrait" ><intent-filter><category android:name="android.intent.category.LAUNCHER" /></intent-filter></activity></application><uses-permission android:name="android.permission.INTERNET"/><uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/><uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/><uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
</manifest>
复制代码

 

Generic.java

复制代码
package tools;import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.Enumeration;
import java.util.List;import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.media.ThumbnailUtils;
import android.os.Environment;
import android.text.format.Time;
import android.util.Log;
import android.widget.Toast;public class Generic {public static void showMsg(Context c, String msg, boolean flag){if(flag)/*** Toast是已经用于显示给用户的控件,显示一段时间后消失,可以多久消失* LENGTH_SHORT:断的显示时间* LENGTH_LONG :长的显示时间*/Toast.makeText(c, msg, Toast.LENGTH_SHORT).show(); elseToast.makeText(c, msg, Toast.LENGTH_LONG).show();}// get sysTimepublic static String getSysNowTime() {Time localTime = new Time();localTime.setToNow();String strTime = localTime.format("%Y-%m-%d-%H-%M-%S");return strTime;}/*** 得到sdcard的路径* @return 失败返回null*/public static File getSdCardFile(){if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){return Environment.getExternalStorageDirectory();}return null;}/*** 获取所有连接到本wifi热点的手机IP地址*/  public static ArrayList<String> getConnectedIP() {  ArrayList<String> connectedIP = new ArrayList<String>();  try {  BufferedReader br = new BufferedReader(new FileReader("/proc/net/arp"));  String line; br.readLine();while ((line = br.readLine()) != null) {  String[] splitted = line.split(" ");  if (splitted != null && splitted.length >= 4) {  String ip = splitted[0];  connectedIP.add(ip);  }  }  } catch (Exception e) {  e.printStackTrace();  }  return connectedIP;  } /*** 得到照片的缩略图* @param f 照片文件* @param w 图片缩小的目标宽度* @param h 图片缩小的目标高度* @return* 1.根据android提供的BitmapFactory.Options类创建并设置好options* 2.根据File获得流对象* 3.根据BitmapFactory.decodeStream获得位图* 4.改变图片为居中缩放,返回位图*/public static Bitmap getShrinkedPic(File f){Bitmap smallBitmap = null;// 直接通过图片路径将图片转化为bitmap,并将bitmap压缩,避免内存溢出BitmapFactory.Options options = new BitmapFactory.Options();options.inSampleSize = 10;// 图片宽高都为原来的十分之一options.inPreferredConfig = Bitmap.Config.ARGB_4444;// 每个像素占用2byte内存options.inPurgeable = true;// 如果 inPurgeable// 设为True的话表示使用BitmapFactory创建的Bitmap// 用于存储Pixel的内存空间在系统内存不足时可以被回收options.inInputShareable = true;FileInputStream fInputStream;try {fInputStream = new FileInputStream(f);// 建议使用BitmapFactory.decodeStreamBitmap bitmap = BitmapFactory.decodeStream(fInputStream, null, options);// 直接根据图片路径转化为bitmapsmallBitmap = ThumbnailUtils.extractThumbnail(bitmap, 64, 48);// 创建所需尺寸居中缩放的位图} catch (FileNotFoundException e) {e.printStackTrace();return null;}return smallBitmap;}/*** Integer值越大,则排在前面* @author Administrator**/public static class DescendSortByIndex implements Comparator<Integer>{/*** @return 负数:object2<object1,正数:object2>object1,0:相等*/@Overridepublic int compare(Integer object1, Integer object2) {return object2.compareTo(object1);}}/*** File的最后修改时间值越大,则排在前面* @author Administrator**/public static class DescendSortByTime implements Comparator<File>{/*** @return 负数:object2<object1,正数:object2>object1,0:相等*/@Overridepublic int compare(File object1, File object2) {return (int) (object2.lastModified() - object1.lastModified());}}
}
复制代码

Mjpeg.java

复制代码
package com.mjpeg.view;import java.io.IOException;import tools.Generic;import my.work.Library.R;
import com.mjpeg.io.MjpegInputStream;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;
/*** 此类继承了SurfaceView实现了SurfaceHolder.Callback接口* SurfaceView是视图类(view)的继承类,这个视图里内嵌入了一个专门用于绘制的Surface    ,可以控制这个Surface的格式和尺寸* SurfaceView控制这个Surface的绘制位置* surface是纵深排序(Z-ordered)的,这表明它总在自己所在窗口的后面。surfaceview提供了一个可见区域* 只有在这个可见区域内 的surface部分内容才可见,可见区域外的部分不可见。surface的排版显示受到视图层级关系的影响* 它的兄弟视图结点会在顶端显示,这意味者 surface的内容会被它的兄弟视图遮挡,这一特性可以用来放置遮盖物(overlays)(例如,文本和按钮等控件)* 可以通过SurfaceHolder接口访问这个surface,getHolder()方法可以得到这个接口* surfaceview变得可见时    ,surface被创建;surfaceview隐藏前,surface被销毁;这样能节省资源。如果你要查看 surface被创建和销毁的时机* 可以重载surfaceCreated(SurfaceHolder)和 surfaceDestroyed(SurfaceHolder)* surfaceview的核心在于提供了两个线程:UI线程和渲染线程,这里应注意:* 1> 所有SurfaceView和SurfaceHolder.Callback的方法都应该在UI线程里调用,一般来说就是应用程序主线程,渲染线程所要访问的各种变量应该作同步处理。* 2> 由于surface可能被销毁,它只在SurfaceHolder.Callback.surfaceCreated()和 SurfaceHolder.Callback.surfaceDestroyed()之间有效,* 所以要确保渲染线程访问的是合法有效的surface* 整个过程:继承SurfaceView并实现SurfaceHolder.Callback接口 ----> SurfaceView.getHolder()获得SurfaceHolder对象(Surface控制器) * ---->SurfaceHolder.addCallback(callback)添加回调函数---->SurfaceHolder.lockCanvas()获得Canvas对象并锁定画布* ----> Canvas绘画 ---->SurfaceHolder.unlockCanvasAndPost(Canvas canvas)结束锁定画图,并提交改变,将图形显示。*/
public class MjpegView extends SurfaceView implements SurfaceHolder.Callback {/*fps显示位置*/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 STANDARD_MODE = 1;//标准尺寸public final static int KEEP_SCALE_MODE = 4;//保持宽高比例public final static int FULLSCREEN_MODE = 8;//全屏private Context mContext = null;private MjpegViewThread mvThread = null;private MjpegInputStream mIs = null;private Paint overlayPaint = null;//用于fps涂层绘画笔private boolean bIsShowFps = true;private boolean bRun = false;private boolean bsurfaceIsCreate = false;private int overlayTextColor;private int overlayBackgroundColor;private int ovlPos;private int dispWidth;//MjpegView的宽度private int dispHeight;//MjpegView的高度private int displayMode;//覆盖模式public MjpegView(Context context) {super(context);init(context);}/*** 因为在res/layout目录下的main.xml中作为自定义的控件使用了这个类,所以需要给此类提供带有属性形参的构造函数* 当在MainActivity通过ID找到这自定义的控件时,该构造函数将被调用,所以将该构造函数设为public    * @param context* @param attrs*/public MjpegView(Context context, AttributeSet attrs) {super(context, attrs);init(context);}/*** 类的私有方法* 1.获得Surface控制器,为Surface控制器添加回调接口* 2.新建渲染线程MjpegViewThread* 3.新建覆盖画笔,设置文本的对齐方式、文本长度、字体、画笔文本颜色、画笔背景* 4.设置覆盖动态文本的覆盖位置 //如果你只需要实现监控画面的功能,3和4步可以省略* 5.设置MjpegView显示模式* @param context*/private void init(Context context) {mContext = context;SurfaceHolder holder = getHolder();holder.addCallback(this);mvThread = new MjpegViewThread(holder, context);setFocusable(true);overlayPaint = new Paint();overlayPaint.setTextAlign(Paint.Align.LEFT);overlayPaint.setTextSize(12);overlayPaint.setTypeface(Typeface.DEFAULT);overlayTextColor = Color.RED;overlayBackgroundColor = Color.TRANSPARENT;ovlPos = MjpegView.POSITION_UPPER_RIGHT;displayMode = MjpegView.KEEP_SCALE_MODE;}/***  Surface的任何结构性结构性的改变(如格式,大小)将激发此方法*  主要调用渲染线程的setSurfaceSize来设置Surface的宽和高*/public void surfaceChanged(SurfaceHolder holder, int f, int w, int h) {mvThread.setSurfaceSize(w, h);}/*** Surface被销毁之前将激发此方法,这里只设置标记位,表示Surface“被销毁了”*/public void surfaceDestroyed(SurfaceHolder holder) {bsurfaceIsCreate = false;}/*** Surface被第一次创建后将激发此方法,这里只设置标记位,表示Surface“被创建了”*/public void surfaceCreated(SurfaceHolder holder) {bsurfaceIsCreate = true;}/*** setFps,getFps,set source都在MaiActivity使用* @param b*/public void setFps(boolean b) {bIsShowFps = b;}public boolean getFps(){return bIsShowFps;}public void setSource(MjpegInputStream source) {mIs = source;}/*** 开始播放线程* 设置标记,表示“Surface被创建了”,然后调用渲染线程的的run方法启动渲染*/public void startPlay() {if (mIs != null) {bRun = true;mvThread.start();}}/*** 停止播放线程* 1.先设置标记,表示"停止播放"* 2.等待播放线程的退出* 3.关闭输入流*/public void stopPlay() {bRun = false;boolean retry = true;while (retry) {try {mvThread.join();retry = false;} catch (InterruptedException e) {}}//线程停止后关闭Mjpeg流(很重要)
        mIs.closeInstance();}/*** mjpegview的获取位图方法,调用渲染线程的获取位图方法* @return*/public Bitmap getBitmap(){return mvThread.getBitmap();}/*** 设置显示模式,在MainActivity的initview调用* @param s*/public void setDisplayMode(int s) {displayMode = s;}/*** 既然有设置显示模式,就应该也有获得显示模式,这是java在设置方法方面的风格* @return*/public int getDisplayMode() {return displayMode;}/*** 此渲染线程类在主类上是重点,应该重点掌握* @author Administrator**/public class MjpegViewThread extends Thread {private SurfaceHolder mSurfaceHolder = null;private int frameCounter = 0;private long start = 0;private Canvas c = null;private Bitmap overlayBitmap = null;private Bitmap mjpegBitmap = null;private PorterDuffXfermode mode = null;/*** 用一个变量来保存传进来的surfaceHolder* 新建一个目的图层和覆盖图层的相交模式,mjpegview为目的图层,覆盖图层为右上角的动态"文本"* mode在calculateFps方法里使用* @param surfaceHolder:Surfaceview控制器* @param context : 上下文环境*/public MjpegViewThread(SurfaceHolder surfaceHolder, Context context) {mSurfaceHolder = surfaceHolder;mode = new PorterDuffXfermode(PorterDuff.Mode.DST_OVER);/*相交时动态文本覆盖mjpegview*/}public Bitmap getBitmap(){return mjpegBitmap;}/*** 计算图像尺寸* @param bmw bitmap宽* @param bmh bitmap高* @return 图像矩阵*/private Rect destRect(int bmw, int bmh) {int tempx;int tempy;/*** 显示模式只会在全屏和半屏模式之间切换,根本不会进入STANDARD_MODE模式,故下面的if分支可以去掉*/if (displayMode == MjpegView.STANDARD_MODE) {tempx = (dispWidth / 2) - (bmw / 2);tempy = (dispHeight / 2) - (bmh / 2);return new Rect(tempx, tempy, bmw + tempx, bmh + tempy);}/*** 一开始,程序处于KEEP_SCALE_MODE模式,表示半屏显示画面*/if (displayMode == MjpegView.KEEP_SCALE_MODE) {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);/*** Rect(左边,顶边,右边,下边),功能是绘制一个特定坐标的矩形* 简单说就是左上角坐标为(0,0),右下角坐标为(bmw,bmh)*/return new Rect(0, 0, bmw + 0, bmh + 0);}/*** 如果显示模式为全屏,则全屏显示画面* dispWidth和dispHeight在下面的setSurfaceSize方法使用,它们表示mjpegview的宽和高*/if (displayMode == MjpegView.FULLSCREEN_MODE)return new Rect(0, 0, dispWidth, dispHeight);return null;}/*** 当mjpegview发生任何结构性的改变时,将激发此方法,前面也提到,渲染线程使用的各种变量需做同步处理* synchronized内的就是同步代码块,为了防止线程之间对临界资源的竞争* @param width* @param height*/public void setSurfaceSize(int width, int height) {synchronized (mSurfaceHolder) {dispWidth = width;dispHeight = height;}}/*** 此方法被calculateFps使用,calculateFps又被渲染线程的run方法使用* 功能是返回一个位图* @param p:覆盖"文本"用的画笔* @param text:要绘制的字符 如:帧* @return bm*/private Bitmap makeFpsOverlay(Paint p, String text) {int nWidth, nHeight;Rect b = new Rect();//int  a = b.left ;/*** 功能是获得从原点开始,字符围绕的最小的矩形* text:字符* 0:表示第一个字符* text.length:测量的最后一个字符* b:用于存放获得的字符矩形* 获得了text的边界后就可以得到矩形的宽和高*/p.getTextBounds(text, 0, text.length(), b);nWidth = b.width() + 2;nHeight = b.height() + 2;/*** 每一个像素4字节,根据上面获得的宽和高返回一个位图*/Bitmap bm = Bitmap.createBitmap(nWidth, nHeight,Bitmap.Config.ARGB_8888);/*** Canvas :画布,这是图像处理的基本单元* 画图时,需要4个重要的元素:* 1.操作像素的位图* 2.绘图到位图的画布 * 3.矩形 * 4. 描述颜色和绘制风格的画笔 * Canvas(bm):构造出一个要绘制到位图的画布*/Canvas c = new Canvas(bm);/*** Paint类介绍  * Paint即画笔,在绘图过程中起到了极其重要的作用,画笔主要保存了颜色,  * 样式等绘制信息,指定了如何绘制文本和图形,画笔对象有很多设置方法,  * 大体上可以分为两类,一类与图形绘制相关,一类与文本绘制相关。         *   * 1.图形绘制        * setColor(int color);  * 设置绘制的颜色,使用颜色值来表示,该颜色值包括透明度和RGB颜色。    * setDither(boolean dither);       * setXfermode(Xfermode xfermode);  * 设置图形重叠时的处理方式,如合并,取交集或并集,经常用来制作橡皮的擦除效果  *   * 2.文本绘制  * setFakeBoldText(boolean fakeBoldText);  * 模拟实现粗体文字,设置在小字体上效果会非常差     * setSubpixelText(boolean subpixelText);  * 设置该项为true,将有助于文本在LCD屏幕上的显示效果  *   * setTextAlign(Paint.Align align);  * 设置绘制文字的对齐方向    * setTextSize(float textSize);  * 设置绘制文字的字号大小  * setTypeface(Typeface typeface);  * 设置Typeface对象,即字体风格,包括粗体,斜体以及衬线体,非衬线体等    */  p.setColor(overlayBackgroundColor);// 背景颜色c.drawRect(0, 0, nWidth, nHeight, p);/*绘制矩形*/p.setColor(overlayTextColor);// 文字颜色/*** 画布的绘制文字方法* test:要绘制的字符* -b.left:字符起始位置的x坐标,这里是矩形的左边* (nHeight / 2) - ((p.ascent() + p.descent()) / 2) + 1:字符起始位置的y坐标* p:用到的画笔* 关于涉及的矩形属性可看博客  http://mikewang.blog.51cto.com/3826268/871765*/c.drawText(text, -b.left + 1,(nHeight / 2) - ((p.ascent() + p.descent()) / 2) + 1, p);return bm;}/*** 重头戏* 如果线程是运行的,SurfaceView也创建了的* 则锁定画布com/mjpeg/io/MjpegInputStream.java中的readMjpegFrame方法获得mjpeg视频流的内容* mjpeg视频的内容就是类位图,然后根据类位图绘制矩形,再绘制相应的位图,这个位图才是我们需要的* 如果设置了帧率文本,就在mjpegview上覆盖,最后解锁画布*/public void run() {start = System.currentTimeMillis();Rect destRect;Paint p = new Paint();//        String fps = "";while (bRun) {if (bsurfaceIsCreate) {c = mSurfaceHolder.lockCanvas();try {mjpegBitmap = mIs.readMjpegFrame();/*调用Inputstrean的方法*//*同步图像的宽高设置*/synchronized (mSurfaceHolder) {destRect = destRect(mjpegBitmap.getWidth(),mjpegBitmap.getHeight());}/*** 当主activity点击相册和设置跳转时,Surfaceview被销毁,此时c将为空*/if(c != null){c.drawPaint(new Paint());c.drawBitmap(mjpegBitmap, null, destRect, p);if (bIsShowFps)calculateFps(destRect, c, p);mSurfaceHolder.unlockCanvasAndPost(c);}} catch (IOException e) {}}else {try {Thread.sleep(500);//线程休眠,让出调度} catch (InterruptedException e) {e.printStackTrace();}}}}/*** 使用前面的方法,绘制出“显示帧率”文本,效果为"i帧",i自增* @param destRect* @param c* @param p*/public void calculateFps(Rect destRect, Canvas c, Paint p) {int width;int height;String fps;p.setXfermode(mode);/* 设置两个画面相交时的模式*/if (overlayBitmap != null) {/*** 计算好文本的宽和高* 然后调用画布的绘制位图方法绘图*/height = ((ovlPos & 1) == 1) ? destRect.top: destRect.bottom - overlayBitmap.getHeight();width = ((ovlPos & 8) == 8) ? destRect.left : destRect.right - overlayBitmap.getWidth();c.drawBitmap(overlayBitmap, width, height, null);}p.setXfermode(null);frameCounter++;/*** currentTimeMillis表示系统从January 1, 1970 00:00:00.0 UTC开始的毫秒数* start在前面已经设置好,表示渲染线程开始的系统时间*/if ((System.currentTimeMillis() - start) >= 1000) {fps = frameCounter+ "fps";start = System.currentTimeMillis();overlayBitmap = makeFpsOverlay(overlayPaint, fps);/*真正的绘制这个"文本"*/frameCounter = 0;                }}}}
复制代码

 

MjpegInputStream.java

复制代码
package com.mjpeg.io;import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.util.Properties;import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.params.CoreConnectionPNames;import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Parcelable;
import android.util.Log;
/*** 该类继承了DataInputStream实现了Serializable接口* 1. 实例化流,获取初始化流和关闭实例流的方法* 2. 一个构造函数* 3. 一个根据帧数据大小获得位图方法*/
public class MjpegInputStream extends DataInputStream implements Serializable{/*** */private static final long serialVersionUID = 1L;/*** 用UE打开发现 每一个jpg格式的图片 开始两字节都是 0xFF,0xD8*/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;private static MjpegInputStream mis = null;/*** 调用该类的构造方法 创建MjpegInputStream流* @param is*/public static void initInstance(InputStream is){if(mis == null)mis = new MjpegInputStream(is);}/*** 获得创建的mjpegInputsteam流* @return*/public static MjpegInputStream getInstance(){if(mis != null)return mis;return null;}/*** 因为mpjeginputstream继承了datainputstream* 所以可以调用mpjeginputstream的关闭流方法*/public static void closeInstance(){try {mis.close();} catch (IOException e) {e.printStackTrace();}mis = null;}private MjpegInputStream(InputStream in) {super(new BufferedInputStream(in, FRAME_MAX_LENGTH));}/*** 在数据流里面找SOI_MARKER={(byte)0xFF,(byte) 0xD8}* 所有对IO流的操作都会抛出异常* @param in* @param sequence* @return* @throws IOException*/private int getEndOfSeqeunce(DataInputStream in, byte[] sequence)throws IOException {int seqIndex = 0;byte c;for (int i = 0; i < FRAME_MAX_LENGTH; i++) {// 0 1 2 3c = (byte) in.readUnsignedByte();if (c == sequence[seqIndex]) {seqIndex++;if (seqIndex == sequence.length)//2return i + 1;//3} elseseqIndex = 0;}return -1;}/*** 此方法功能是找到索引0xFF,0XD8在字符流的位置* 整个数据流形式:http头信息 帧头(0xFF 0xD8) 帧数据 帧尾(0xFF 0xD9)* 1、首先通过0xFF 0xD8找到帧头位置* 2、帧头位置前的数据就是http头,里面包含Content-Length,这个字段指示了整个帧数据的长度* 3、帧头位置后面的数据就是帧图像的开始位置* @param in* @param sequence* @return* @throws IOException*/private int getStartOfSequence(DataInputStream in, byte[] sequence)throws IOException {int end = getEndOfSeqeunce(in, sequence);return (end < 0) ? (-1) : (end - sequence.length);}/*** 从http的头信息中获取Content-Length,知道一帧数据的长度* @param headerBytes* @return* @throws IOException* @throws NumberFormatException*/private int parseContentLength(byte[] headerBytes) throws IOException,NumberFormatException {/*** 根据字节流创建ByteArrayInputStream流* Properties是java.util包里的一个类,它有带参数和不带参数的构造方法,表示创建无默认值和有默认值的属性列表* 根据流中的http头信息生成属性文件,然后找到属性文件CONTENT_LENGTH的value,这就找到了要获得的帧数据大小* 创建一个 ByteArrayInputStream,使用 headerBytes作为其缓冲区数组*/ByteArrayInputStream headerIn = new ByteArrayInputStream(headerBytes);Properties props = new Properties();/*创建一个无默认值的空属性列表*/props.load(headerIn);/*从输入流中生成属性列表(键和元素对)。*/return Integer.parseInt(props.getProperty(CONTENT_LENGTH));/*用指定的键在此属性列表中搜索属性。*/}/*** * @return* @throws IOException*/public Bitmap readMjpegFrame() throws IOException {mark(FRAME_MAX_LENGTH);/*流中当前的标记位置*/int headerLen = getStartOfSequence(this, SOI_MARKER);reset();/*将缓冲区的位置重置为标记位置*/byte[] header = new byte[headerLen];readFully(header);/*会一直阻塞等待,直到数据全部到达(数据缓冲区装满)*/
//        String s = new String(header);try {mContentLength = parseContentLength(header);// ?} catch (NumberFormatException e) {return null;}/*** 根据帧数据的大小创建字节数组*/byte[] frameData = new byte[mContentLength];readFully(frameData);/*** 根据不同的源(file,stream,byte-arrays)创建位图* 把输入字节流流转为位图*/return BitmapFactory.decodeStream(new ByteArrayInputStream(frameData));}
}
复制代码

 

WsnActivty.java

复制代码
package my.work.Library;import java.util.Timer;
import java.util.TimerTask;
import java.util.regex.Matcher;
import java.util.regex.Pattern;import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.TextView;public class WsnActivty extends Activity {/** Called when the activity is first created. */private Button btnNetwork,btnVideo;private String strIpAddr = null;static TextView textTips,seat;private ClientThread clientThread = null;private Message MainMsg;public static Handler mainHandler;static final int TIPS_UPDATE_UI = 3;   //tips_update_uistatic final int SEAT_UPDATE_UI = 6;   //seat_update_uistatic final int MAX_NODE = 4;static byte NodeData[][] = new byte[MAX_NODE][5];; // [5] 0=温度 1=湿度 2=气体 3=灯static final int RX_DATA_UPDATE_UI = 1;@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.main);initControl();initMainHandler();}private void initControl() {// TODO Auto-generated method stubbtnNetwork = (Button) findViewById(R.id.btn_network);btnNetwork.setOnClickListener(new ButtonClick());textTips = (TextView) findViewById(R.id.Tips);textTips.setText(R.string.init_tips);seat = (TextView) findViewById(R.id.statc001);seat.setText(R.string.people1);btnVideo = (Button) findViewById(R.id.btn_video);btnVideo.setOnClickListener(new ButtonClick());}class ButtonClick implements OnClickListener {@Overridepublic void onClick(View v) {switch (v.getId()) {case R.id.btn_network: // 连接网络showDialog(WsnActivty.this);break;case R.id.btn_video: // 暂时用作停止自动刷新功能  ,跳转到FlashActivity去// mainTimer.cancel(); //看视频关闭定时查询数据 要判断if (clientThread != null) {MainMsg = ClientThread.childHandler.obtainMessage(ClientThread.RX_EXIT);  //关闭线程
                    ClientThread.childHandler.sendMessage(MainMsg);}/*new一个Intent对象,并指定class*/ Intent intent = new Intent();  intent.setClass(WsnActivty.this,FlashActivity.class);  /*new一个Bundle对象,并将要传递的数据传入*/ Bundle bundle = new Bundle();  bundle.putString("IP",strIpAddr);  /*将Bundle对象assign给Intent*/ intent.putExtras(bundle);  /*调用Activity FlashActivity*/ startActivity(intent); //startActivity(new Intent(WsnActivity.this, FlashActivity.class));//简单跳转break;}}}// 显示连接对话框private void showDialog(Context context) {final EditText editIP = new EditText(context);editIP.setText("192.168.0.10");AlertDialog.Builder builder = new AlertDialog.Builder(context);// builder.setIcon(R.drawable.ic_launcher);builder.setTitle("请输入服务器IP地址");builder.setView(editIP);builder.setPositiveButton("连接", new DialogInterface.OnClickListener() {public void onClick(DialogInterface dialog, int whichButton) {strIpAddr = editIP.getText().toString();boolean ret = isIPAddress(strIpAddr);if (ret) {textTips.setText("服务器IP地址:" + strIpAddr);} else {strIpAddr = null;textTips.setText("IP地址不合法,请重新设置");return;}clientThread = new ClientThread(strIpAddr);// 建立客户端线程
                        clientThread.start();//mainTimer = new Timer();// 定时查询所有终端信息//setTimerTask();
                    }});builder.setNeutralButton("取消", new DialogInterface.OnClickListener() {public void onClick(DialogInterface dialog, int whichButton) {if (clientThread != null) {MainMsg = ClientThread.childHandler.obtainMessage(ClientThread.RX_EXIT);ClientThread.childHandler.sendMessage(MainMsg);textTips.setText("与服务器断开连接");}}});builder.show();}// 判断输入IP是否合法private boolean isIPAddress(String ipaddr) {boolean flag = false;Pattern pattern = Pattern.compile("\\b((?!\\d\\d\\d)\\d+|1\\d\\d|2[0-4]\\d|25[0-5])\\.((?!\\d\\d\\d)\\d+|1\\d\\d|2[0-4]\\d|25[0-5])\\.((?!\\d\\d\\d)\\d+|1\\d\\d|2[0-4]\\d|25[0-5])\\.((?!\\d\\d\\d)\\d+|1\\d\\d|2[0-4]\\d|25[0-5])\\b");Matcher m = pattern.matcher(ipaddr);flag = m.matches();return flag;}void initMainHandler() {mainHandler = new Handler() {// 主线程消息处理中心public void handleMessage(Message msg) {switch (msg.what) {case TIPS_UPDATE_UI:String str = (String) msg.obj;  //连接成功
                        textTips.setText(str);break;case SEAT_UPDATE_UI:String strseat = (String) msg.obj;  //rfid                            
                            seat.setText(strseat);break;}super.handleMessage(msg);}};}}
复制代码

 

ClientThread.java

复制代码
package my.work.Library;import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.widget.Toast;public class ClientThread extends Thread {private OutputStream outputStream = null;private InputStream inputStream = null;private Socket socket;private SocketAddress socketAddress;public static Handler childHandler;private boolean RxFlag = true;final int TEXT_INFO = 12;static final int RX_EXIT = 11;static final int TX_DATA = 10;Context mainContext;Message msg;private String strIP;final int SERVER_PORT = 33333;byte cNodeData[][] = new byte[4][5]; // [5] 0=温度 1=湿度 2=气体 3=灯int RxCount = 0, nRecvLen, index = 0;byte CheckSum;// byte strRxBuf[] = new byte[256];int ucRecvLen = 7;private RxThread rxThread;//获取WsnActivty.java 开辟子线程clientThread对象线程传递过来的ip地址public ClientThread(String ip) {strIP = ip;}// 连接网络void connect() {RxFlag = true;socketAddress = new InetSocketAddress(strIP, SERVER_PORT);socket = new Socket();try {socket.connect(socketAddress, SERVER_PORT);inputStream = socket.getInputStream();outputStream = socket.getOutputStream();msg = WsnActivty.mainHandler.obtainMessage(WsnActivty.TIPS_UPDATE_UI, "连接成功");WsnActivty.mainHandler.sendMessage(msg);rxThread = new RxThread();rxThread.start();} catch (IOException e) {try {sleep(10);} catch (InterruptedException e1) {e1.printStackTrace();}msg = WsnActivty.mainHandler.obtainMessage(WsnActivty.TIPS_UPDATE_UI, "无法连接到服务器");WsnActivty.mainHandler.sendMessage(msg);e.printStackTrace();} catch (NumberFormatException e) {}}void initChildHandler() {Looper.prepare(); // 在子线程中创建Handler必须初始化Looper
childHandler = new Handler() {// 子线程消息处理中心public void handleMessage(Message msg) {// 接收主线程及其他线程的消息并处理.../*** MainMsg = ClientThread.childHandler.obtainMessage(ClientThread.TX_DATA,* len, 0, (Object) buffer);* SendData(SendBuf, 7);*/switch (msg.what) {case RX_EXIT:RxFlag = false;try {if (socket.isConnected()) {inputStream.close();outputStream.close();socket.close();}} catch (IOException e1) {e1.printStackTrace();}childHandler.getLooper().quit();// 结束消息队列break;default:break;}}};// 启动该线程的消息队列
        Looper.loop();}public void run() {connect();initChildHandler();msg = WsnActivty.mainHandler.obtainMessage(WsnActivty.TIPS_UPDATE_UI,"与服务器断开连接");WsnActivty.mainHandler.sendMessage(msg);}// socket 接收线程public class RxThread extends Thread {public void run() {try {while (socket.isConnected() && RxFlag) {byte strRxBuf[] = new byte[30];byte i;int RxIndex, len, readBytes = 0;    while (readBytes < ucRecvLen) {           //接收到数据,存放到strRxBuflen = inputStream.read(strRxBuf,readBytes, 8);readBytes += len;if (len == -1)break;}String strRead =new String(strRxBuf);String str=new String("822350C2");String str1="822350C2";if(str.equals(str1)){msg = WsnActivty.mainHandler.obtainMessage(WsnActivty.SEAT_UPDATE_UI, "有人");WsnActivty.mainHandler.sendMessage(msg);}msg = WsnActivty.mainHandler.obtainMessage(WsnActivty.SEAT_UPDATE_UI, strRead);WsnActivty.mainHandler.sendMessage(msg);}if (socket.isConnected())socket.close();} catch (IOException e) {e.printStackTrace();}}}byte GetDataLen(byte fc) {byte len = 0;switch (fc) {case 0x01:len = 22;break;case 0x07:case 0x08:case 0x0A:case 0x0B:case 0x0C:case 0x0D:len = 7;break;}return len;}}
复制代码

 

FlashActivity.java

复制代码
package my.work.Library;import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;import my.work.Library.WsnActivty.ButtonClick;import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.params.CoreConnectionPNames;import tools.Generic;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.net.DhcpInfo;
import android.net.wifi.WifiManager;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.ArrayAdapter;
import android.widget.AutoCompleteTextView;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;import com.mjpeg.io.MjpegInputStream;/*** 应用程序执行时,该类首先被调用*/
public class FlashActivity extends Activity {private Context mContext = this;private EditText ipEdt = null;private EditText portEdt = null;private TextView hintTv = null;private DhcpInfo dpInfo = null;private WifiManager wifi = null;private InputStream is = null;private SharedPreferences sp = null;private Editor editor = null;private String port = "8080";/* 用来保存获得用户输入的端口 */private Bundle bundle;private Button connectin;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.flash);/* 设置布局为res/layout/flash.xml*/init();int state = wifi.getWifiState();/* 获得wifi当前状态 */if (state != WifiManager.WIFI_STATE_ENABLED) {/*** 为了程序的扩展性和可读性,单独在tools目录定义一个Generic类,它有很多方法* 1.有showMsg方法,用于控制显示时间来显示一个Toast* 2.有getSysNowTime方法,用于获取当前的系统时间* 3.有getSdCardFile方法,用于获取SD卡的绝对路径,成功返回File值,失败返回NULL* 4.有getConnectedIP方法,用于获取连接到wifi热点的所有的手机ip,成功返回ArrayList<String>型的容器* 5.有getShrinkedPic方法,用于获取照片的缩略图* 6.定义了一个DescendSortByIndex类:实现了整型比较器* 7.定义个DescendSortByTime类:实现了File比较器*/Generic.showMsg(this, "请打开wifi", false);finish();} else/* 取得Intent中的Bundle对象 */ bundle = this.getIntent().getExtras();  /* 取得Bundle对象中的数据 */ String strIP = bundle.getString("IP");  //autoConnect(strIP);
    }@Override/*** 调用finish方法时,这方法将被激发* 设置输入流为空,调用父类的onDestroy销毁资源*/protected void onDestroy() {is = null;super.onDestroy();}private void init() {/*** 获取在本Activity要使用的控件和WiFi*/hintTv = (TextView) findViewById(R.id.hintTv);ipEdt = (EditText) findViewById(R.id.ip);portEdt = (EditText) findViewById(R.id.port);connectin = (Button) findViewById(R.id.connect);connectin.setOnClickListener(new ButtonClick());/*** 因为要用到WIFI和Internet所以在AndroidMenufest.xml 中添加如下权限 <uses-permission* android:name="android.permission.INTERNET"/> <uses-permission* android:name="android.permission.ACCESS_WIFI_STATE"/>* <uses-permission* android:name="android.permission.CHANGE_WIFI_STATE"/>*/wifi = (WifiManager) getSystemService(Context.WIFI_SERVICE);//initSp();/* 主要是方便查找以前登录成功了的IP */
    }class ButtonClick implements OnClickListener {@Overridepublic void onClick(View v) {String ip = ipEdt.getText().toString();/* 获得输入的IP */port = portEdt.getText().toString();/* 获得输入的端口 */// port不能为空,ip地址格式正确if (!port.equals("") && checkAddr(ip, Integer.valueOf(port))) {new ConnectTask().execute(ip);} else {Generic.showMsg(mContext, "请检查ip或port", true);}}}
//    /**
//     * 生成配置文件config,它在 /data/data/<package name>/shared_prefs/config.xml
//     * 取出配置文件的ip用冒号隔开,并为自动完成列表设置适配器
//     */
//    private void initSp() {
//        sp = getSharedPreferences("config", MODE_PRIVATE);
//        /* 创建好配置文件后,以后就可以用它的edit来操作配置文件了 */
//        editor = sp.edit();
//        String names[] = sp.getString("ip", "").split(":");
//        ArrayAdapter<String> adapter = new ArrayAdapter<String>(mContext,
//                android.R.layout.simple_dropdown_item_1line, names);
//        //ipEdt.setAdapter(adapter);
//    }
//
//    /**
//     * 自动连接 先将获取到的wifi热点服务器地址和连接到wifi热点的设备的ip放入容器,启动连接线程扫描容器中的ip
//     * 
//     * @return
//     */
//    private void autoConnect(String strIP) {
//        ArrayList<String> addr = new ArrayList<String>();/* 创建容器 用于存放ip */
//
//        dpInfo = wifi.getDhcpInfo();
//        addr.add(int32ToIp(dpInfo.serverAddress));/* 把服务IP放入容器的尾部 */
//        addr.addAll(Generic.getConnectedIP());// Adds the objects in the specified collection to this ArrayList
//
//        // 为了在执行连接时 不会卡住UI,故采用异步任务方式,若读者想减缩程序,也可不使用异步任务
//        if (strIP != null) {
//            new ConnectTask().execute(strIP);
//        } else {
//            //因为连接线程的执行方法必须String类型,所以要toArray    
//            new ConnectTask().execute(addr.toArray(new String[addr.size()]));
//        }
//    }/*** 按照一定的格式返回输入的Ip* * @param ip* @return*/private String int32ToIp(int ip) {return (ip & 0xff) + "." + (ip >> 8 & 0xff) + "." + (ip >> 16 & 0xff)+ "." + (ip >> 24 & 0xff);}//    /**
//     * 手动连接 为控件绑定监听器有2种方法 1.给出布局文件并设置,findViewById()找到控件,调用API为其绑定相应监听器
//     * 2.给出布局文件并设置,在布局文件里设置相应控件的OnClick,然后在源文件里具体实现相应控件的OnClick//本类用的就是这方法
//     * 在layout目录下的flash.xml里声明了connectBtn的Button控件 点击"连接"按钮将调用此方法
//     * 
//     * @param v
//     */
//    public void connectBtn() {
//        String ip = ipEdt.getText().toString();/* 获得输入的IP */
//        port = portEdt.getText().toString();/* 获得输入的端口 */
//
//        // port不能为空,ip地址格式正确
//        if (!port.equals("") && checkAddr(ip, Integer.valueOf(port))) {
//            new ConnectTask().execute(ip);
//        } else {
//            Generic.showMsg(mContext, "连接失败", true);
//        }
//    }/*** 分割的ip是4段,ip端口范围在1000-65535* * @param ip* @param port* @return*/private boolean checkAddr(String ip, int port) {if (ip.split("\\.").length != 4)return false;if (port < 1000 || port > 65535)return false;return true;}/*** 连接线程 此类的作用是在后台线程里执行http连接,连接卡住不会影响UI运行,适合于运行时间较长但又不能影响前台线程的情况* 异步任务,有3参数和4步* :onPreExecute(),doInBackground(),onProgressUpdate(),onPostExecute()* onPreExecute():运行于UI线程,一般为后台线程做准备,如在用户接口显示进度条* doInBackground():当onPreExecute执行后,马上被触发,执行花费较长时间的后台运算,将返回值传给onPostExecute* onProgressUpdate():当用户调用 publishProgress()将被激发,执行的时间未定义,这个方法可以用任意形式显示进度* 一般用于激活一个进度条或者在UI文本领域显示logo onPostExecute():当后台进程执行后在UI线程被激发,把后台执行的结果通知给UI* 参数一:运行于后台的doInBackground的参数类型* 参数二:doInBackground计算的通知给UI线程的单元类型,即运行于UI线程onProgressUpdate的参数类型,这里没用到* 参数三:doInBackground的返回值,将传给onPostExecute作参数* * @author Administrator* */private class ConnectTask extends AsyncTask<String, Integer, String> {@Overrideprotected String doInBackground(String... params) {for (int i = 0; i < params.length; i++) {String ip = params[i];/* 取出每一个ip */if (ip.split("\\.").length == 4) {/*** 在浏览器观察画面时,也是输入下面的字符串网址*/String action = "http://" + ip + ":" + port+ "/?action=stream";is = http(action);if (is != null) { /* 第一次必须输入IP,下次登录时才可找到之前登录成功后的IP *///writeSp(ip);MjpegInputStream.initInstance(is);  //消息实体内容isbreak;}}}return null;}@Overrideprotected void onPostExecute(String result) {if (is != null) {/*** Intent是Android特有的东西,可以在Intent指定程序要执行的动作(比如:view,edit,dial)* 都准备好程序执行该工作所需要的材料后* ,只要调用startActivity,Android系统会自动寻找最符合你指定要求的应用程序 并执行该程序*/startActivity(new Intent(FlashActivity.this, LinearLayout_activity.class));finish();/* 结束本Activity */} else {hintTv.setText(getResources().getString(R.string.connect_failed));Generic.showMsg(mContext, "连接失败", true);}super.onPostExecute(result);}/*** 功能:http连接 Android提供两种http客户端, HttpURLConnection 和 Apache HTTP* Client,它们都支持HTTPS,能上传和下载文件 配置超时时间,用于IPV6和 connection pooling, Apache* HTTP client在Android2.2或之前版本有较少BUG* 但在Android2.2或之后,HttpURLConnection是更好的选择,在这里我们用的是 Apache HTTP Client* 凡是对IO的操作都会涉及异常,所以要try和catch* * @param url* @return InputStream*/private InputStream http(String url) {HttpResponse res;DefaultHttpClient httpclient = new DefaultHttpClient();/** 创建http客户端,* 才能调用它的各种方法*/httpclient.getParams().setParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, 500);/* 设置超时时间 */try {HttpGet hg = new HttpGet(url);/** 这是GET方法的http API,* GET方法是默认的HTTP请求方法*/res = httpclient.execute(hg);return res.getEntity().getContent(); // 从响应中获取消息实体内容} catch (IOException e) {}return null;}}
}//    /**
//     * 更新SharedPreferences 1.先判断ip是否有"ip"值,没有就将传进来的data赋值给ip 2.ip有值就取出,然后用冒号分隔开
//     * 3.sp数组只能存放10组ip,如果超过了10组,先清零配置文件再更新 4.遍历数组,如果已有当前登录成功的ip,则返回
//     * 5.数组里不包含登录成功的ip,则将当前登录成功的ip添加至sp数组并提交
//     * 
//     * @param ip
//     */
//    private void writeSp(String data) {
//        if (!sp.contains("ip")) {
//            editor.putString("ip", data);
//            editor.commit();
//            return;
//        }
//
//        /**
//         * 配置文件里有ip,表示之前登录成功了
//         */
//        String ip = sp.getString("ip", "");
//        String[] ips = ip.split(":");
//
//        if (ips.length >= 10) {
//            editor.clear();
//            editor.commit();
//            editor.putString("ip", data);
//            editor.commit();
//            return;
//        }
//
//        for (int i = 0; i < ips.length; i++) {
//            if (ips[i].equals(data))
//                return;
//        }
//        editor.putString("ip", data + ":" + ip);/* 放在以前成功了的ip的前面 */
//        editor.commit();
//    }
//
//    /**
//     * 自动完成框的下拉选项 当点击"history_user"ImageView控件时将调用该方法 这里只是具体实现xml文件的Onclick
//     */
//    public void showDropDown(View v) {
//        ipEdt.showDropDown();
//    }
复制代码

 

LinearLayout_activity.java

复制代码
package my.work.Library;import com.mjpeg.io.MjpegInputStream;
import com.mjpeg.view.MjpegView;import android.app.Activity;
import android.os.Bundle;import my.work.Library.R;public class LinearLayout_activity extends Activity {public static LinearLayout_activity instance = null;private MjpegInputStream mis = null;private MjpegView mjpegView = null;protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.mainactivty);/*构造RadioGroup的5的RadioButton*/instance = this;mis = MjpegInputStream.getInstance();mjpegView = (MjpegView) findViewById(R.id.mjpegview);initMjpegView();}    private void initMjpegView() {if (mis != null) {mjpegView.setSource(mis);// 设置数据来源mjpegView.setDisplayMode(mjpegView.getDisplayMode());/*设置mjpegview的显示模式*//*** setFps和getFps方法是为了在屏幕的右上角动态显示当前的帧率* 如果我们只需观看画面,下面这句完全可以省去*/mjpegView.setFps(mjpegView.getFps());/*** 调用mjpegView中的线程的run方法,开始显示画面*/mjpegView.setDisplayMode(MjpegView.FULLSCREEN_MODE);/*全屏*/mjpegView.startPlay();}}}
复制代码


这篇关于网络摄像头Androi端显示(mjpeg)源码分析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/887413

相关文章

第10章 中断和动态时钟显示

第10章 中断和动态时钟显示 从本章开始,按照书籍的划分,第10章开始就进入保护模式(Protected Mode)部分了,感觉从这里开始难度突然就增加了。 书中介绍了为什么有中断(Interrupt)的设计,中断的几种方式:外部硬件中断、内部中断和软中断。通过中断做了一个会走的时钟和屏幕上输入字符的程序。 我自己理解中断的一些作用: 为了更好的利用处理器的性能。协同快速和慢速设备一起工作

性能分析之MySQL索引实战案例

文章目录 一、前言二、准备三、MySQL索引优化四、MySQL 索引知识回顾五、总结 一、前言 在上一讲性能工具之 JProfiler 简单登录案例分析实战中已经发现SQL没有建立索引问题,本文将一起从代码层去分析为什么没有建立索引? 开源ERP项目地址:https://gitee.com/jishenghua/JSH_ERP 二、准备 打开IDEA找到登录请求资源路径位置

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟&nbsp;开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚&nbsp;第一站:海量资源,应有尽有 走进“智听

Linux 网络编程 --- 应用层

一、自定义协议和序列化反序列化 代码: 序列化反序列化实现网络版本计算器 二、HTTP协议 1、谈两个简单的预备知识 https://www.baidu.com/ --- 域名 --- 域名解析 --- IP地址 http的端口号为80端口,https的端口号为443 url为统一资源定位符。CSDNhttps://mp.csdn.net/mp_blog/creation/editor

安卓链接正常显示,ios#符被转义%23导致链接访问404

原因分析: url中含有特殊字符 中文未编码 都有可能导致URL转换失败,所以需要对url编码处理  如下: guard let allowUrl = webUrl.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) else {return} 后面发现当url中有#号时,会被误伤转义为%23,导致链接无法访问

C#实战|大乐透选号器[6]:实现实时显示已选择的红蓝球数量

哈喽,你好啊,我是雷工。 关于大乐透选号器在前面已经记录了5篇笔记,这是第6篇; 接下来实现实时显示当前选中红球数量,蓝球数量; 以下为练习笔记。 01 效果演示 当选择和取消选择红球或蓝球时,在对应的位置显示实时已选择的红球、蓝球的数量; 02 标签名称 分别设置Label标签名称为:lblRedCount、lblBlueCount

ASIO网络调试助手之一:简介

多年前,写过几篇《Boost.Asio C++网络编程》的学习文章,一直没机会实践。最近项目中用到了Asio,于是抽空写了个网络调试助手。 开发环境: Win10 Qt5.12.6 + Asio(standalone) + spdlog 支持协议: UDP + TCP Client + TCP Server 独立的Asio(http://www.think-async.com)只包含了头文件,不依

Java ArrayList扩容机制 (源码解读)

结论:初始长度为10,若所需长度小于1.5倍原长度,则按照1.5倍扩容。若不够用则按照所需长度扩容。 一. 明确类内部重要变量含义         1:数组默认长度         2:这是一个共享的空数组实例,用于明确创建长度为0时的ArrayList ,比如通过 new ArrayList<>(0),ArrayList 内部的数组 elementData 会指向这个 EMPTY_EL

如何在Visual Studio中调试.NET源码

今天偶然在看别人代码时,发现在他的代码里使用了Any判断List<T>是否为空。 我一般的做法是先判断是否为null,再判断Count。 看了一下Count的源码如下: 1 [__DynamicallyInvokable]2 public int Count3 {4 [__DynamicallyInvokable]5 get

poj 3181 网络流,建图。

题意: 农夫约翰为他的牛准备了F种食物和D种饮料。 每头牛都有各自喜欢的食物和饮料,而每种食物和饮料都只能分配给一头牛。 问最多能有多少头牛可以同时得到喜欢的食物和饮料。 解析: 由于要同时得到喜欢的食物和饮料,所以网络流建图的时候要把牛拆点了。 如下建图: s -> 食物 -> 牛1 -> 牛2 -> 饮料 -> t 所以分配一下点: s  =  0, 牛1= 1~