android环境下摄像头数据采集及显示

以前项目涉及些摄像头预览及数据处理操作,当时的需求是除了做摄像头预览外,还要显示文字、个性图像等,当初在查找资料实现相关模块时,发现很多资料讲的比较繁琐,不够简洁,这里将自己的实现方式分享出来,希望能够为正在做相关工作的同学提供些思路。不过这里先顺便提一下,如果单纯的做摄像头预览,不在预览数据时做添加文字、图像等额外操作,可以用surfaceview方式,性能上会更好些。

这里将摄像头采集及视频图像绘制放在一个模块中,比较便于管理及维护,同时在使用时,因为该类继承自view类,所以可以向操作很多view类一样,将其添加到任何布局中,在与采集的数据宽高比例保持一致的前提下,在页面显示上可以非常灵活的控制视图尺寸大小。不过使用这种方式实现摄像头预览,最大的瓶颈是在旋转yuv数据及将其转为rgb数据时,计算比较耗时,一般情况下采集640*480数据还好,但对于960*720数据来说,手机性能一般的话,就会显得比较卡了。解决方式在做数据旋转时,可以尝试采用ndk
c的方式,以提高运行效率,在做yuv转rgb时,也可以尝试用ndk c的方式,但是最好的方式是采用gpu shader方式,直接渲染yuv数据,即将采集的yuv数据以纹理的方式上传至gpu,然后由gpu完成yuv转rgb并显示。下面是相关代码:

public class CameraView extends View implements PreviewCallback
{
	// 源视频帧宽/高
	private int srcFrameWidth  = 640;
	private int srcFrameHeight = 480;
	private int frameSize = srcFrameWidth * srcFrameHeight;
	private int qtrFrameSize = srcFrameWidth * srcFrameHeight >> 2;

	// 帧预览贴图
	private Bitmap previewBmp = null;
	private Rect previewRect = null;
	private Camera camera = null;
	// 图层
	private BaseLayer[] layers = null;

	// 数据采集
	private int[] rgb_data = null;
	private byte[] yuvdata = null;

	// 摄像头前置/后置
	public static final int CAMERA_BACK  = 0;
	public static final int CAMERA_FRONT = 1;
	private int curCameraIndex = CAMERA_BACK;

	public CameraView(Context _context)
	{
		super(_context);
	}

	public CameraView(Context _context, AttributeSet _attrs)
	{
		super(_context, _attrs);
	}

	public CameraView(Context context, int previewWidth, int previewHeight, int cameraIndex)
	{
		super(context);

		curCameraIndex = cameraIndex;
		rgb_data = new int[frameSize];
		yuvdata = new byte[frameSize * 3 / 2];

		previewBmp = Bitmap.createBitmap(srcFrameHeight, srcFrameWidth, Config.ARGB_8888);
		previewRect = new Rect(0, 0, previewWidth, previewHeight);

		// 定义图层
		layers = new BaseLayer[2];
		layers[0]  = new TextLayer(context, 0, false);
		layers[1] = new ImageLayer(context, 1, false);

		// 文字
		((TextLayer)layers[0]).setFontParams(32, Color.CYAN);
		((TextLayer)layers[0]).setTextPos(100, 300);
		((TextLayer)layers[0]).setContent("天气还不错....");
		layers[0].setVisible(true);

		// 图像
		((ImageLayer)layers[1]).setImagePos(100, 150);
		layers[1].setVisible(true);

		// 初始化并打开摄像头
		startCamera(cameraIndex);

		this.setBackgroundColor(Color.parseColor("#82858b"));
	}

	// 根据索引初始化摄像头
	public void startCamera(int cameraIndex)
	{
		// 先停止摄像头
		stopCamera();

		// 再初始化并打开摄像头
		if (camera == null)
		{
			camera = Camera.open(cameraIndex);
			Camera.Parameters params = camera.getParameters();
			params.setPreviewSize(srcFrameWidth, srcFrameHeight);
			params.setPreviewFormat(ImageFormat.NV21);
			camera.setParameters(params);
			camera.setPreviewCallback(this);
			camera.startPreview();
		}
	}

	// 停止并释放摄像头
	public void stopCamera()
	{
		if (camera != null)
		{
			camera.setPreviewCallback(null);
			camera.stopPreview();
			camera.release();
			camera = null;
		}
	}

	// 绘制
	@Override
	protected void onDraw(Canvas canvas)
	{
		super.onDraw(canvas);

		// 填充数据(因为数据已经旋转过,此时宽与高需要互换)
		previewBmp.setPixels(rgb_data, 0, srcFrameHeight, 0, 0, srcFrameHeight, srcFrameWidth);

		// 绘制图层
		for (BaseLayer layer : layers)
		{
			if (layer.isVisible())
			{
				layer.drawLayer(previewBmp);
			}
		}

		// 贴图
		canvas.drawBitmap(previewBmp, null, previewRect, null);
	}

	// 获取摄像头视频数据
	@Override
	public void onPreviewFrame(byte[] data, Camera camera)
	{
		int i = 0, j = 0, k = 0;
		int uvHeight = srcFrameHeight >> 1;

		// 旋转yuv数据
		if (curCameraIndex == CAMERA_BACK)
		{
			// 旋转y
			for (i = 0; i < srcFrameWidth; i++)
			{
				for (j = srcFrameHeight - 1; j >= 0; j--)
				{
					yuvdata[k] = data[srcFrameWidth * j + i];
					k++;
				}
			}

			// 旋转uv
			for (i = 0; i < srcFrameWidth; i += 2)
			{
			   for (j = uvHeight - 1; j >= 0; j--)
			   {
				   yuvdata[k] = data[frameSize + srcFrameWidth * j + i + 1];// cb/u
				   yuvdata[k + qtrFrameSize] = data[frameSize + srcFrameWidth * j + i];// cr/v
				   k++;
			   }
			}
		}
		else
		{
			// 旋转y
			for (i = srcFrameWidth - 1; i >= 0; i--)
			{
				for (j = srcFrameHeight - 1; j >= 0; j--)
				{
					yuvdata[k] = data[srcFrameWidth * j + i];
					k++;
				}
			}

			// 旋转uv
			for (i = srcFrameWidth - 2; i >= 0; i -= 2)
			{
			   for (j = uvHeight - 1; j >= 0; j--)
			   {
				   yuvdata[k] = data[frameSize + srcFrameWidth * j + i + 1];// cb/u
				   yuvdata[k + qtrFrameSize] = data[frameSize + srcFrameWidth * j + i];// cr/v
				   k++;
			   }
			}
		}

		// yuv转rgb(因为数据已经旋转过,此时宽与高需要互换)
		int yp = 0;
		for (i = 0, yp = 0; i < srcFrameWidth; i++)
		{
			int uvp = frameSize + (i >> 1) * uvHeight, u = 0, v = 0;
			for (j = 0; j < srcFrameHeight; j++, yp++)
			{
				int y = (0xff & yuvdata[yp]) - 16;
				if ((j & 1) == 0)
				{
					u = (0xff & yuvdata[uvp + (j>>1)]) - 128;
					v = (0xff & yuvdata[uvp + qtrFrameSize + (j>>1)]) - 128;
				}

				int y1192 = 1192 * y;
				int r = (y1192 + 1634 * v);
				int g = (y1192 - 833 * v - 400 * u);
				int b = (y1192 + 2066 * u);

				if (r < 0) r = 0; else if (r > 262143) r = 262143;
				if (g < 0) g = 0; else if (g > 262143) g = 262143;
				if (b < 0) b = 0; else if (b > 262143) b = 262143;

				rgb_data[i*srcFrameHeight + j] = 0xff000000 | ((r << 6) & 0xff0000) | ((g >> 2) & 0xff00) | ((b >> 10) & 0xff);
			}// for
		}// for

		invalidate();
	}
}

工程下载链接:http://download.csdn.net/detail/u013085897/8652979

时间: 2024-10-19 12:51:04

android环境下摄像头数据采集及显示的相关文章

Android环境下使用SocketClient

最近对原来写的SocketClient代码进行优化,从整体架构到具体细节,修改的地方比较多.今天有时间把SocketClient的相关知识整理一下.如果有错误的地方,还望指正!!! 一.整体流程: 描述如下: 1.  在Android环境下,SocketClient长连接,需要使用service. 2.  SocketManagerService是在APK启动时启动. 3.  SocketManagerService启动时则SocketClientThread也启动. 4.  View调用Soc

iOS6和iOS7环境下微信登录未显示问题&amp;微信IOS的SDK:isWXAppInstalled总是返回NO和nil

一.问题描述: iOS6和iOS7 环境下未显示微信登录界面,在其他环境下显示正常. 二.问题解决: iOS6和7未出现微信登录按钮, 原因 [WXApi isWXAppInstalled] 返回nil和NO: if ([WXApi isWXAppInstalled]) { ... } 修改为如下判断URL: if ([[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:@"weixin://"]]) {

cocos2d-x 在android环境下开发遇到的一些bug

今天在弄一个关于android环境下解析xml的东东,遇到了2个比较麻烦问题 1.android的apk下文件是压缩文件,io.open模式无法读取到数据的, 解决思路就是: CCFileUtils::sharedFileUtils()->getFileData(),这个getFileData我做了一些修改,判断一下最后字符是否是"0"结尾:如果不是添加一个相应的结尾符 2.问题解决了,还是不能读取到xml的内容,该死的android环境下也不太好查找c++问题位置,搞了一下午,

android环境下两种md5加密方式

在平时开发过程中,MD5加密是一个比较常用的算法,最常见的使用场景就是在帐号注册时,用户输入的密码经md5加密后,传输至服务器保存起来.虽然md5加密经常用,但是md5的加密原理我还真说不上来,对md5的认知目前仅仅停留在会使用的水平,想搞清楚还是要花点时间的,这是md5加密算法的相关介绍.本文主要介绍android平台下两种md5加密方式,分别为基于java语言的md5加密及ndk环境下基于c语言的md5加密. 下面代码为基于java语言的md5加密: public String getMD5

Linux下打开Windows环境下创建的文本文件显示中文乱码问题

产生原因: Linux下打开Windows环境下创建的文本文件出现中文乱码,因为两种操作系统的中文压缩方式不同,在Windows环境下中文编码一般为GBK,而在Linux环境中为UTF-8,这就导致了在Windows下能正常显示的文件在Linux环境下打开出现了乱码. 解决方法: 使用iconv命令, 命令语法:iconv -f fromcode [-cs] [-t tocode [file ...] 假设乱码文件名为Hello.c,那么在终端可使用如下命令进行格式转换: iconv -f GB

在高通平台Android环境下编译内核模块【转】

本文转载自:http://blog.xeonxu.info/blog/2012/12/04/zai-gao-tong-ping-tai-androidhuan-jing-xia-bian-yi-nei-he-mo-kuai/ 高通Android环境中Linux内核会作为Android的一部分进行编译,直接使用make即可一次性从头编到尾.而有的平台比如Marvell,内核的编译操作相对比较独立,必须使用标准的内核编译命令进行单独编译.一般来说,用高通的这种方式比较傻瓜化,一步到底的感觉:而用Ma

cocos2d-x 3.x 搭建Android环境下的开发环境

所需要的一些工具软件: 1.JDK  官网下载地址:http://www.oracle.com/ttechnetwork/java/javase/downloads/index.html 2.Android SDK  官网下载地址:http://developer.android.com/sdk/index.html 3.Android NDK  官网下载地址:http://developer.android.com/tools/sdk/ndk/index.html 3.ANT  官网下载地址:

android环境下的即时通讯

首先了解一下即时通信的概念.通过消息通道 传输消息对象,一个账号发往另外一账号,只要账号在线,可以即时获取到消息,这就是最简单的即使通讯.消息通道可由TCP/IP UDP实现.通俗讲就是把一个人要发送给另外一个人的消息对象(文字,音视频,文件)通过消息通道(C/S实时通信)进行传输的服务.即时通讯应该包括四种形式,在线直传.在线代理.离线代理.离线扩展.在线直传指不经过服务器,直接实现点对点传输.在线代理指消息经过服务器,在服务器实现中转,最后到达目标账号.离线代理指消息经过服务器中转到达目标账

cocos2dx3.0-tinyxml在Android环境下解析xml失败的问题

正常情况下,我们在用tinyxml读取xml文件的的时候,会像下面这样写. 1 std::string filePath = FileUtils::getInstance()->getWritablePath() + fileName;//获取文件路径. 2 XMLDocument *pDoc = new XMLDocument();//创建一个空的XMLDocument 3 XMLError errorId = pDoc->LoadFile(filePath.c_str());//用XMLD