app passed NULL surface报错以及onPreviewFrame不被调用的原因

本文详细介绍了在Android环境下,如何开发一个能够实时进行人脸识别并仅显示处理后图像的程序。解决了camera.setPreviewDisplay导致的图像冲突问题,通过使用camera.setPreviewTexture方法,实现了图像的实时处理和显示,避免了直接显示原始图像。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前段时间,需要开发一个人脸识别的程序,实现如下功能:程序通过摄像机实时获取图像,但是不直接显示获取的图像,而是显示处理过后的图像。

就这个如何不显示处理前只显示处理后的图像,纠结了很久,网上搜索了很多,对于这个问题,网上的信息都是重复无用的,根本无法解决。接下来我详细的说明遇见的问题和解决方法。

一开始程序见程序段1,这么写的话,屏幕上面只会显示摄像头获取到的图片,处理过后的图片完全不会显示,后面要是强行的向这个surfaceholder控制的surfaceview来输出图片,程序会报错。

public void surfaceCreated(SurfaceHolder holder) {
		//开启相机
		camera=Camera.open();
		try
		{			
			camera.setPreviewDisplay(surfaceholder);
			ShowMessage("开启相机成功");//toast提示
		}
		catch(Exception e)
		{
			ShowMessage("开启相机失败");//toast提示
			camera.release();
			camera=null;
		}
	}//程序段1

后来发现可能是camera.setPreviewDisplay(surfaceholder)这句有问题,相机一直在用这个holder像surfaceview输出图像,后面程序中再使用surfaceholder来绘制新的图片,就会冲突,然后报错。
也试过用两个重叠的surfaceview来实现,一个给相机用,一个给处理后的图片用,通过程序控制将其中给相机用的那个隐藏掉,但是这么做行不通,后面再说为什么不行。也尝试过camera.setPreviewDisplay(null);这么写的话,在模拟器中是可以运行的,但是到了真机上面就不行了。在真机上,这两种办法都不会调用 onPreviewFrame(byte[] data, Camera arg1) ,查资料发现,android的相机获取的图片必须有一个surfaceview来显示,否则就不会调用 onPreviewFrame(byte[] data, Camera arg1) 方法。看到这里都会都会觉得Android好神烦。

在较新的apk版本中可以用camera.setPreviewTexture(surfacetexture)来替代camera.setPreviewDisplay(surfaceholder)。camera.setPreviewTexture(surfacetexture)这个方法允许相机将获取的图片在一个隐藏的surfacetexture上显示,这么一来,便可以实现一开始提到的那个功能。

下面是一个基本的框架:

package com.example.atest01;
 
import java.io.ByteArrayOutputStream;
import java.io.IOException;
 
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ImageFormat;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.SurfaceTexture;
import android.graphics.YuvImage;
import android.graphics.drawable.BitmapDrawable;
import android.hardware.Camera;
import android.hardware.Camera.PreviewCallback;
import android.os.Bundle;
import android.app.Activity;
import android.util.DisplayMetrics;
import android.view.Display;
import android.view.Menu;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.Toast;
 
public class MainActivity extends Activity implements SurfaceHolder.Callback {
	
SurfaceHolder surfaceholder;
SurfaceView surfaceview;
Canvas canvas;
Camera camera;
SurfaceTexture surfacetexture;
int counter=0;
int width,height,sum;
Bitmap img1;

@Override
protected void onCreate(Bundle savedInstanceState) {
	super.onCreate(savedInstanceState);
	requestWindowFeature(Window.FEATURE_NO_TITLE);
	getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
	setContentView(R.layout.activity_main);
	
	img1=((BitmapDrawable)getResources().getDrawable(R.drawable.ic_launcher)).getBitmap();
	surfaceview=(SurfaceView)findViewById(R.id.sv1);
	//获得控件  添加surfaceholder
	surfaceholder=surfaceview.getHolder();
	surfaceholder.addCallback(this);
	surfacetexture=new SurfaceTexture(10);
	//surfaceholder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
	//获得屏幕的长宽像素点数,这边的长宽像素要修改,不然会报错,
			//因为有的机型的长宽像素点数,不是setpreviewSize支持的长宽。
	DisplayMetrics dm=new DisplayMetrics();
	getWindowManager().getDefaultDisplay().getMetrics(dm);
	width=dm.widthPixels;
	height=dm.heightPixels;
	sum=width*height;
	/*/显示屏幕的长宽像素点数
	String s=Integer.toString(width)+"\n"+Integer.toString(height);
	ShowMessage(s);//*/		
}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
	
	getMenuInflater().inflate(R.menu.main, menu);
	return true;
}

public void ShowMessage(String text)
{
	Toast.makeText(this, text, Toast.LENGTH_SHORT).show();
}

@Override
public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) {
	Camera.Parameters parameters=camera.getParameters();	
	parameters.setPictureSize(width, height);
	parameters.setPreviewSize(width, height);
	
	previewcallback pre = new previewcallback();//建立预览回调对象
	camera.setPreviewCallback(pre);
    //camera.setPreviewCallback(pre); //设置预览回调对象
	camera.setParameters(parameters);	
	
	camera.startPreview();
	ShowMessage("开启相机成功22323");
	counter++;
	ShowMessage(Integer.toString(counter));
}

@Override
public void surfaceCreated(SurfaceHolder holder) {
	//开启相机
	camera=Camera.open();
	try
	{			
		camera.setPreviewTexture(surfacetexture);
		ShowMessage("开启相机成功");
	}
	catch(Exception e)
	{
		ShowMessage("开启相机失败");
		camera.release();
		camera=null;
	}

}

@Override
public void surfaceDestroyed(SurfaceHolder arg0) {
	
	camera.stopPreview();
	camera.release();
	camera=null;
}

class previewcallback implements Camera.PreviewCallback
{
	byte tmp;
	public previewcallback()
	{
		
	}
	
	@Override
	public void onPreviewFrame(byte[] data, Camera arg1) {
		// TODO Auto-generated method stub
		//ShowMessage("开启相机成功qwe");
		try
		{
			ShowMessage("here go");
		}
		catch(Exception e)
		{
			ShowMessage("Err 001");
			e.printStackTrace();
		}
		//*///此代码显示出每次都在调用这个onPreviewFrame方法		
		}	
	}
}
<think>我们正在讨论如何在Android应用程序中调用源码中的AIDL服务。根据引用内容,我们可以总结出以下步骤: 1. **定义AIDL接口**:首先在系统源码中定义AIDL接口文件(.aidl),并声明方法。例如,引用[2]中在`framework/base/core/java/android/hardware/`下创建了`ICopyCameraData.aidl`文件。 2. **实现AIDL接口**:在系统服务中实现这个AIDL接口,并将服务注册到系统服务管理器中。 3. **编译生成Java接口**:编译系统源码,AIDL工具会自动生成对应的Java接口文件。生成的Java文件位于`out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/aidl`目录下(具体路径可能因版本而异),并打包到`classes.jar`中(引用[3]提到)。 4. **应用程序调用**: - 在应用程序中,需要将生成的AIDL接口文件(.java或.aidl)复制到应用的源码目录中(通常放在`app/src/main/aidl`目录下,并保持相同的包结构)。 - 在应用程序中通过`ServiceConnection`绑定服务,并调用接口方法。 具体步骤如下: ### 步骤1:将系统AIDL文件复制到应用项目中 - 在应用的`main`目录下创建`aidl`目录(如果存在则创建),然后按照系统AIDL文件的包结构创建相同的子目录。例如,如果系统AIDL文件路径为`android/hardware/ICopyCameraData.aidl`,则在应用的`aidl`目录下创建`android/hardware`目录,并将`ICopyCameraData.aidl`复制到该目录中。 ### 步骤2:在应用中绑定服务 - 在应用程序中,需要知道系统服务的完整类名(通常是实现服务的类名)和动作(action)。如果系统服务已经注册,我们可以通过服务的全限定类名来绑定。 ### 步骤3:实现ServiceConnection - 在应用中实现`ServiceConnection`接口,以便在服务连接成功时获取AIDL接口的实例。 ### 步骤4:调用服务 - 通过绑定服务后获得的AIDL接口实例调用其方法。 ### 示例代码 假设系统服务名为`com.android.server.CopyCameraDataService`,在应用中的绑定过程如下: 1. **AndroidManifest.xml** 中添加权限(如果需要)并声明绑定意图(如果服务是公开的): ```xml <!-- 如果服务需要权限,则添加权限声明 --> <uses-permission android:name="android.permission.ACCESS_COPY_CAMERA_DATA" /> ``` 2. **绑定服务**: ```java Intent intent = new Intent(); intent.setClassName("com.android.server", "com.android.server.CopyCameraDataService"); bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE); ``` 3. **实现ServiceConnection**: ```java private ICopyCameraData copyCameraDataService; private ServiceConnection serviceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { copyCameraDataService = ICopyCameraData.Stub.asInterface(service); } @Override public void onServiceDisconnected(ComponentName name) { copyCameraDataService = null; } }; ``` 4. **调用服务方法**: ```java if (copyCameraDataService != null) { try { byte[] data = copyCameraDataService.onPreviewFrame(); // 处理数据 } catch (RemoteException e) { e.printStackTrace(); } } ``` ### 注意事项 - 由于系统服务通常运行在系统进程中,调用系统服务可能需要应用具有系统权限(platform签名)或特定权限。因此,普通应用可能无法绑定系统服务。如果应用是系统应用(即使用平台签名),则可以在AndroidManifest.xml中声明共享系统用户ID: ```xml <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.myapp" android:sharedUserId="android.uid.system"> ``` 然后使用平台密钥签名应用。 - 如果系统服务没有暴露给普通应用(即没有在`Context`中注册为公开服务),则可能需要修改系统服务的代码,将其注册为公开服务,或者使用隐式Intent绑定(如果服务定义了IntentFilter)。 - 在绑定系统服务时,确保服务已经启动并运行。 ### 引用说明 根据引用[2],我们了解到在系统源码中添加AIDL服务的方法;引用[3]则提示了编译后生成的接口文件位置。这些步骤都是在系统源码层面进行的,而应用调用则需要将AIDL文件复制到应用中,并确保应用有权限绑定该服务[^2][^3]。 §§相关问题§§ 1. 如何确保应用有权限绑定系统AIDL服务? 2. 如果系统服务没有暴露给普通应用,应该如何修改系统服务使其可被绑定? 3. 在绑定系统服务时出现`SecurityException`异常,如何解决?</think>### Android App调用源码中的AIDL服务指南 #### 核心步骤: 1. **获取系统AIDL接口文件** - 从源码中找到目标AIDL文件(如 `ICopyCameraData.aidl`) - 复制到应用的 `app/src/main/aidl` 目录,**保持原始包名路径** (例如:`app/src/main/aidl/android/hardware/ICopyCameraData.aidl`)[^2] 2. **绑定系统服务** ```java // 在Activity/Fragment中声明 private ICopyCameraData mService; private final ServiceConnection mConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { mService = ICopyCameraData.Stub.asInterface(service); // 获取接口实例 } @Override public void onServiceDisconnected(ComponentName name) { mService = null; } }; // 绑定服务(系统服务名需提前确认) void bindSystemService() { Intent intent = new Intent(); // 关键:设置系统服务的ComponentName intent.setComponent(new ComponentName("com.android.server", "com.android.server.CopyCameraDataService")); bindService(intent, mConnection, Context.BIND_AUTO_CREATE); } ``` 3. **调用服务方法** ```java try { if (mService != null) { byte[] frameData = mService.onPreviewFrame(); // 调用AIDL方法 mService.registerCallBack(mCallback); // 注册回调 } } catch (RemoteException e) { e.printStackTrace(); } ``` #### 关键注意事项: 1. **权限要求**: - 需在 `AndroidManifest.xml` 声明系统权限: ```xml <uses-permission android:name="android.permission.ACCESS_SYSTEM_SERVICE"/> ``` - 应用必须使用**平台签名**(在 `Android.mk` 中添加 `LOCAL_CERTIFICATE := platform`) 2. **回调接口实现**: ```java private ICameraDataCallBack.Stub mCallback = new ICameraDataCallBack.Stub() { @Override public void onDataReceived(byte[] data) throws RemoteException { // 处理系统服务回调 } }; ``` 3. **编译依赖**: - 在 `app/build.gradle` 添加AIDL源集配置: ```gradle android { sourceSets { main { aidl.srcDirs = ['src/main/aidl'] } } } ``` #### 常见问题解决: - **ClassNotFoundException**:检查AIDL文件路径是否与原始包名完全一致 - **SecurityException**:确认应用使用平台签名,且声明了正确的权限 - **服务绑定失败**:验证系统服务名称是否匹配源码中的注册名(通常在 `SystemServer.java` 注册) > 提示:系统服务的完整类名可通过 `dumpsys` 命令查询: > `adb shell dumpsys | grep "CopyCameraDataService"` [^1]: 系统AIDL接口需要正确编译到framework.jar中 [^2]: AIDL文件需保持原始包结构 [^3]: 应用需使用平台签名才能绑定系统服务
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值