网络摄像头Androi端显示(mjpeg)源码分析
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"
/>
<TextView
android: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" > <TextView
android:id="@+id/hintTv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/login_hint" /> <EditText
android: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"
/> <EditText
android:id="@+id/port"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="8080"
android:gravity="center" /> <Button
android: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.MjpegView
android: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" /> <application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name" >
<activity
android: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> <activity
android:name=".FlashActivity"
android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
android:screenOrientation="portrait" >
<intent-filter> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity> <activity
android: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();
else
Toast.makeText(c, msg, Toast.LENGTH_LONG).show();
} // get sysTime
public 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.decodeStream
Bitmap bitmap = BitmapFactory.decodeStream(
fInputStream, null, options);// 直接根据图片路径转化为bitmap
smallBitmap = 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:相等
*/
@Override
public 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:相等
*/
@Override
public 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 3
c = (byte) in.readUnsignedByte();
if (c == sequence[seqIndex]) {
seqIndex++;
if (seqIndex == sequence.length)//
return i + 1;//
} else
seqIndex = 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_ui
static final int SEAT_UPDATE_UI = 6; //seat_update_ui static 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; @Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
initControl();
initMainHandler(); }
private void initControl() {
// TODO Auto-generated method stub
btnNetwork = (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 {
@Override
public 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) { //接收到数据,存放到strRxBuf
len = 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;
@Override
protected 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 {
@Override
public 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> { @Override
protected 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); //消息实体内容is
break;
}
}
} return null;
} @Override
protected 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)源码分析的更多相关文章
- Netty服务端的启动源码分析
ServerBootstrap的构造: public class ServerBootstrap extends AbstractBootstrap<ServerBootstrap, Serve ...
- 7.源码分析---SOFARPC是如何实现故障剔除的?
我在服务端引用那篇文章里面分析到,服务端在引用的时候会去获取服务端可用的服务,并进行心跳,维护一个可用的集合. 所以我们从客户端初始化这部分说起. 服务连接的维护 客户端初始化的时候会调用cluste ...
- 8.源码分析---从设计模式中看SOFARPC中的EventBus?
我们在前面分析客户端引用的时候会看到如下这段代码: // 产生开始调用事件 if (EventBus.isEnable(ClientStartInvokeEvent.class)) { EventBu ...
- 9.源码分析---SOFARPC是如何实现故障剔除的?
SOFARPC源码解析系列: 1. 源码分析---SOFARPC可扩展的机制SPI 2. 源码分析---SOFARPC客户端服务引用 3. 源码分析---SOFARPC客户端服务调用 4. 源码分析- ...
- 11.源码分析---SOFARPC数据透传是实现的?
先把栗子放上,让大家方便测试用: Service端 public static void main(String[] args) { ServerConfig serverConfig = new S ...
- 12.源码分析—如何为SOFARPC写一个序列化?
SOFARPC源码解析系列: 1. 源码分析---SOFARPC可扩展的机制SPI 2. 源码分析---SOFARPC客户端服务引用 3. 源码分析---SOFARPC客户端服务调用 4. 源码分析- ...
- 10.源码分析---SOFARPC内置链路追踪SOFATRACER是怎么做的?
SOFARPC源码解析系列: 1. 源码分析---SOFARPC可扩展的机制SPI 2. 源码分析---SOFARPC客户端服务引用 3. 源码分析---SOFARPC客户端服务调用 4. 源码分析- ...
- Kafka服务端之网络连接源码分析
#### 简介 上次我们通过分析KafkaProducer的源码了解了生产端的主要流程,今天学习下服务端的网络层主要做了什么,先看下 KafkaServer的整体架构图 ![file](https:/ ...
- Android8.1 MTK平台 SystemUI源码分析之 网络信号栏显示刷新
SystemUI系列文章 Android8.1 MTK平台 SystemUI源码分析之 Notification流程 Android8.1 MTK平台 SystemUI源码分析之 电池时钟刷新 And ...
随机推荐
- 我所使用的Linux软件集合
自从2003-2004春节之际初次尝试使用Linux以来,至今已十年有余了.尤其是整个博士研究期间,坚持在Linux下开展学习与研究工作,前前后后试用了不少桌面环境.窗口管理器.终端程序以及其他应用软 ...
- 查看linux系统版本是32位还是64位
如何查看ubuntu版本是64位还是32位的: 1.# uname -a 如果有x86_64就是64位的,没有就是32位的 2.# uname -mx86_64 3.# archx86_6 如何查看u ...
- Unlocker(强力删除文件工具) 1.9.2 汉化绿色版
软件名称: Unlocker(强力删除文件工具) 1.9.2 汉化绿色版软件语言: 简体中文授权方式: 免费软件运行环境: Win7 / Vista / Win2003 / WinXP 软件大小: 5 ...
- C# for循环嵌套
今天,我主要学习了for循环的嵌套. 第一个部分主要学习了打印直角在不同方向的三角形,求阶乘的和以及打印九九乘法表等. 例:打印直角在左下角的三角形. Console.WriteLine(" ...
- Alyona and a tree
Alyona and a tree time limit per test 2 seconds memory limit per test 256 megabytes input standard i ...
- NDK常见问题
1. 忽略编译警告为错误 APP_CPPFLAGS += -Wno-error=format-security 2. android studio 手动编译 __android_log_print 错 ...
- JPA基本注解介绍
一.@Entity •@Entity 标注用于实体类声明语句之前, 指出该Java 类为实体类,将映射到指定的数据库表. 如声明一个实体类 Customer,它将映射到数据库中的 customer 表 ...
- UpdateData使用说明
UpdateData() MFC的窗口函数,下面是MSDN的说明: Call this member function to initialize data in a dialog box, or t ...
- linux脚本Shell之九九乘法表
说到9*9乘法表,许多朋友在想这是小学二年级就会的东西,不错,确实是这样,那么在linux下,使用shell打印出99乘法表应该如何编写脚本的? 笔者的文档今天就写下来,有需要的朋友可以参考下 代码: ...
- 分布式事务实现-Spanner
Spanner要满足的external consistency 是指:后开始的事务一定可以看到先提交的事务的修改.所有事务的读写都加锁可以解决这个问题,缺点是性能较差.特别是对于一些workload中 ...