android 图像解码,Android智能小车视频图像解码解析

该楼层疑似违规已被系统折叠 隐藏此楼查看此楼

在我们的生活和工作当中,不管是智能家居系统还是智能硬件与穿戴,为了在基本功能的基础上增强系统的用户的体验和功能扩充,往往会有视频流采集的强大功能得以实现,视频采集的方式大同小异,这里我们以视频流服务器mjpg-streamer作为视频采集的基础展开。

视频流服务器可能有些技术人员不是很陌生,它是一款轻量级的视频流服务器软件,最基本的功能是从摄像头硬件采集图形图像并且上传到客户端,我们就是利用这一点通过Android客户端应用程序获得底层的视频图形图像的。Android应用上位机程序通过网络编码实现视频流数据的获取和解析,完成如下图片的效果,从而流畅的实现视频的实时采集工作。

android 图像解码,Android智能小车视频图像解码解析_第1张图片

这里视频流服务器是底层系统的一个软件支持,发挥着视频的实时传输的基本功能,系统大多是linux或openwrt的移植版本, 对于有一定的嵌入式基础的技术工程师是比较容易上手的,其间可以用wifi也可以是有线以太网传输,但是根据体验的方便系数我们通常选择wifi作为传输媒介。视频传输的流程图如下:

android 图像解码,Android智能小车视频图像解码解析_第2张图片

终端的app需要通过Android网络编程基本原理实现数据的传输,这里使用URL作为网络资源的统一资源定位符,是指向互联网“资源”的指针。资源可以是简单的目录或文件,也可以是对更复杂的对象引用,例如对数据库或搜索引擎的查询,通常情况而言,URL由协议名,主机,端口号和资源组成,其格式为Protocol://host:port/resourceName,而如果想通过视频流服务器实现数据的采集,假设网卡的ip是192.168.1.1,则URL格式为:http://192.168.1.1:8080/?action=stream,这样一个URL就会请求视频流服务器的图像数据, 为后续的Android数据获取与解析做准备。

Android网络编程以及图像解码方面核心代码步骤如下:

1.我们需要绑定上述的URL(http://192.168.1.1:8080/?action=stream),并且建立URL对象:

URL url = new URL("http://192.168.1.1:8080/?action=stream")。

2.Android客户端通过URL对象和远程的服务器建立连接对象,通过客户端的wifi模块获得视频流服务器的视频图像,即通过Http把数据反馈给端口号为8080的客户端,实现操作如下:

HttpURLConnection urlConn = (HttpURLConnection)url.openConnection();

3.在配置好连接对象的前提下,底层的服务器发送的数据是经过封装的内容,需要我们具体细化的解析和处理,这里我们使用的是io的输入流读操作,通过输入流的read()方法把数据放到buffer缓冲区,同时获得数据的大小read,如下代码实现数据内容和数据内容大小的获得:

int read = urlConn.getInputStream().read(buffer, 0, readSize);

4.数据内容已经获得存储在缓冲区buffer里面,需要对数据进行解析,视频流数据返回的内容的开始是“Content-Length:”,其后是网络传输的图像的特殊标志,一帧图像的特殊标记由2个字节组成,图像的开始标记(包头)是0xFF+0xD8, 图像的结束标记(包尾)是0xFF+0xD9,在0xFFD8和0xFFD9之间的就是我们需要的图像(通常是jpeg图像),如果循环获取逐帧图像就形成了完整的视频流,大家就可以真实的获得摄像头的实时采集信息了。

宏观数据流图如下:

android 图像解码,Android智能小车视频图像解码解析_第3张图片

android 图像解码,Android智能小车视频图像解码解析_第4张图片

解码代码如下:

switch(status){//采集状态记录

case 0:

if (buffer == (byte) 'C')//接收字节'C'

status++;//继续采集状态累加

else

status = 0;//如果不是符合要求的数据封装就丢弃当前的帧,进行下一次数据采集

break;

case 1:

if (buffer == (byte) 'o')//接收字节'o'

status++;//继续采集状态累加

else

status = 0;//如果不是符合要求的数据封装就丢弃当前的帧,进行下一次数据采集

break;

case 2:

if (buffer == (byte) 'n')//接收字节'n'

status++;//继续采集状态累加

else

status = 0;//如果不是符合要求的数据封装就丢弃当前的帧,进行下一次数据采集

break;

case 3:

if (buffer == (byte) 't')//接收字节't'

status++;//继续采集状态累加

else

status = 0;//如果不是符合要求的数据封装就丢弃当前的帧,进行下一次数据采集

break;

case 4:

if (buffer == (byte) 'e')//接收字节'e'

status++;//继续采集状态累加

else

status = 0;//如果不是符合要求的数据封装就丢弃当前的帧,进行下一次数据采集

break;

case 5:

if (buffer == (byte) 'n')//接收字节'n'

status++;//继续采集状态累加

else

status = 0;//如果不是符合要求的数据封装就丢弃当前的帧,进行下一次数据采集

break;

case 6:

if (buffer == (byte) 't')//接收字节't'

status++;//继续采集状态累加

else

status = 0;//如果不是符合要求的数据封装就丢弃当前的帧,进行下一次数据采集

break;

case 7:

if (buffer == (byte) '-')//接收字节'-'

status++;//继续采集状态累加

else

status = 0;//如果不是符合要求的数据封装就丢弃当前的帧,进行下一次数据采集

break;

case 8:

if (buffer == (byte) 'L')//接收字节'L'

status++;//继续采集状态累加

else

status = 0;//如果不是符合要求的数据封装就丢弃当前的帧,进行下一次数据采集

break;

case 9:

if (buffer == (byte) 'e')//接收字节'e'

status++;//继续采集状态累加

else

status = 0;//如果不是符合要求的数据封装就丢弃当前的帧,进行下一次数据采集

break;

case 10:

if (buffer == (byte) 'n')//接收字节'n'

status++;//继续采集状态累加

else

status = 0;//如果不是符合要求的数据封装就丢弃当前的帧,进行下一次数据采集

break;

case 11:

if (buffer == (byte) 'g')//接收字节'g'

status++;//继续采集状态累加

else

status = 0;//如果不是符合要求的数据封装就丢弃当前的帧,进行下一次数据采集

break;

case 12:

if (buffer == (byte) 't')//接收字节't'

status++;//继续采集状态累加

else

status = 0;//如果不是符合要求的数据封装就丢弃当前的帧,进行下一次数据采集

break;

case 13:

if (buffer == (byte) 'h')//接收字节'h'

status++;//继续采集状态累加

else

status = 0;//如果不是符合要求的数据封装就丢弃当前的帧,进行下一次数据采集

break;

case 14:

if (buffer == (byte) ':')//接收字节':'

status++;//继续采集状态累加

else

status = 0;//如果不是符合要求的数据封装就丢弃当前的帧,进行下一次数据采集

break;

case 15:

if (buffer == (byte) 0xFF)//图像开始标示1:0xFF

status++;//继续采集状态累加

jpg_count = 0;//图像数据数组记录下标

jpg_buf[jpg_count++] = (byte) buffer;//进行图像信息数据存储

break;

case 16:

if (buffer == (byte) 0xD8) {//图像开始标示2:0xD8

status++;//继续采集状态累加

jpg_buf[jpg_count++] = (byte) buffer;//进行图像信息数据存储

} else {

if (buffer != (byte) 0xFF)

status = 15;//更改采集状态,重新图片数据内容

}

break;

case 17:

jpg_buf[jpg_count++] = (byte) buffer;//进行图像信息数据存储

if (buffer == (byte) 0xFF)//图像结束标示1:0xFF

status++;//继续采集状态累加

if (jpg_count >= bufSize)//异常处理,安全操作

status = 0;//如果不是符合要求的数据封装就丢弃当前的帧,进行下一次数据采集

break;

case 18:

jpg_buf[jpg_count++] = (byte) buffer;//进行图像信息数据存储

if (buffer == (byte) 0xD9) {//图像结束标示2:0xD9

status = 0;//图片接收完毕,进行下一次数据采集

break;

} // 一帧图像接收完成

5.将获得的帧图像数据流数组jgp_buf通过Bitmap类的decodeByteArray()方法转换为Bitmap图像对象,为后续的图像显示到界面做准备,代码如下:

Bitmap bmp = BitmapFactory.decodeByteArray(jpg_buf,0,jpg_buf.length);

并且调整图片的大小适应现实界面,代码如下:

Bitmap mBitmap = Bitmap.createScaledBitmap(bmp,width, height,false);

6.目前底层的帧图片已经准备就绪,接下来要进行图片在界面的绘制工作,由于循环读取的数据巨大,同时还要保持实时性,所以这里使用一种特殊的视图称为SurfaceView,它拥有独立的绘图表面,即它不与其宿主窗口共享同一个绘图表面。由于拥有独立的绘图表面,因此SurfaceView的UI就可以在一个独立的线程中进行绘制。又由于不会占用主线程资源,SurfaceView一方面可以实现复杂而高效的UI设计,另一方面又不会导致用户输入得不到及时响应。如果要进行SurfaceView的图像UI绘制,我们需要进行如下的步骤:

(1)通过SurfaceView的 getHolder()获得SurfaceHolder对象,代码如下:

SurfaceHolder sfh = new SurfaceView().getHolder();

(2) 通过SurfaceHolder的lockCanvas()方法在绘图表面的基础上建立一块画布,即获得一个Canvas对象,代码如下:

Canvas canvas = sfh.lockCanvas();

(3)通过Canvas对象的方法绘制UI图像画布,代码如下:

canvas.drawBitmap(mBitmap, 0, 0, null);

(4) 将已经填充好的UI数据的画布缓冲区通过SurfaceHolder对象的unlockCanvasAndPost()方法提交给SurfaceFlinger服务,以便SurfaceFlinger服务可以将它合成到屏幕上去,代码如下:

sfh.unlockCanvasAndPost(canvas);

图像效果如下:

android 图像解码,Android智能小车视频图像解码解析_第5张图片

你可能感兴趣的:(android,图像解码)