SurfaceView的简单使用

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

protected Button mBufferBtn

protected Button mVideoBtn

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState)

super.setContentView(R.layout.activity_main)

initView()

}

@Override

public void onClick(View view) {

Intent intent = new Intent()

if (view.getId() == R.id.button) {

intent.setClass(this, BufferActivity.class)

} else if (view.getId() == R.id.button) {

intent.setClass(this, VideoActivity.class)

}

startActivity(intent)

}

private void initView() {

mBufferBtn = (Button) findViewById(R.id.button2)

mBufferBtn.setOnClickListener(MainActivity.this)

mVideoBtn = (Button) findViewById(R.id.button2)

mVideoBtn.setOnClickListener(MainActivity.this)

}

}

public class BufferActivity extends AppCompatActivity implements SurfaceHolder.Callback {

protected SurfaceView mSurfaceView

private SurfaceHolder mHolder

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState)

super.setContentView(R.layout.activity_buffer)

initView()

initSurfaceHolder()

}

// 初始化Surface的管理者

private void initSurfaceHolder() {

mHolder = mSurfaceView.getHolder()

// 添加管理生命周期的接口回调

mHolder.addCallback(this)

}

private void initView() {

mSurfaceView = (SurfaceView) findViewById(R.id.surface_view)

}

// 缓冲区创建

@Override

public void surfaceCreated(SurfaceHolder holder) {

Log.d("1507", "surfaceCreated")

new DrawThread().start()

}

// 缓冲区内容改变(子线程渲染UI的过程)

@Override

public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

Log.d("1507", "surfaceChanged")

}

// 缓冲区销毁

@Override

public void surfaceDestroyed(SurfaceHolder holder) {

Log.d("1507", "surfaceDestroyed")

}

// 绘制UI的子线程

private class DrawThread extends Thread {

@Override

public void run() {

super.run()

// 创建画笔

Paint paint = new Paint()

paint.setColor(Color.GREEN)// 画笔颜色

paint.setStrokeWidth(10)// 画笔粗细。注意:Java中设置的尺寸单位都是px

paint.setStyle(Paint.Style.FILL_AND_STROKE)// 设置实心

paint.setAntiAlias(true)// 设置是否抗锯齿

// 获取SurfaceView的盖度

int height = mSurfaceView.getHeight()

Canvas canvas = null

for (int i = 0i <heighti+= 5) {

// 获取Surface中的画布

canvas = mHolder.lockCanvas()// 锁定画布

// 使用画笔在画布上绘制指定形状

canvas.drawCircle(100, i + 50, 50, paint)// 圆心x坐标,圆心y坐标,半径,画笔

// 缓冲区的画布绘制完毕,需要解锁并提交给窗口展示

mHolder.unlockCanvasAndPost(canvas)

//                try {

//                    Thread.sleep(100)

//                } catch (InterruptedException e) {

//                    e.printStackTrace()

//                }

}

}

}

}

public class VideoActivity extends AppCompatActivity implements View.OnClickListener {

protected MyVideoSurfaceView mSurfaceView

protected Button mPlayBtn

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState)

super.setContentView(R.layout.activity_video)

initView()

}

// 运行、可见

@Override

protected void onStart() {

super.onStart()

}

// 可交互

@Override

protected void onResume() {

super.onResume()

}

private void play() {

String videoPath = Environment.getExternalStorageDirectory().getPath() +

"/VID_20171117_144736.3gp"// 外部存储根路径

mSurfaceView.playVideo(videoPath)

}

private void initView() {

mSurfaceView = (MyVideoSurfaceView) findViewById(R.id.surface_view)

mPlayBtn = (Button) findViewById(R.id.play_btn)

mPlayBtn.setOnClickListener(VideoActivity.this)

}

@Override

public void onClick(View view) {

if (view.getId() == R.id.play_btn) {

play()

}

}

}

public class MyVideoSurfaceView extends SurfaceView implements SurfaceHolder.Callback {

private SurfaceHolder mHolder

private MediaPlayer mMediaPlayer

public MyVideoSurfaceView(Context context, AttributeSet attrs) {

super(context, attrs)

init()

}

private void init() {

// 获取Surface换朝哪个区的持有者

mHolder = getHolder()

mHolder.addCallback(this)

}

// 设置播放源

public void playVideo(String path) {

if (mMediaPlayer == null) {

mMediaPlayer = new MediaPlayer()

}

try {

// 设置播放源

mMediaPlayer.setDataSource(path)

// 设置多媒体的显示部分:使用SurfaceHolder渲染画面

mMediaPlayer.setDisplay(mHolder)

mMediaPlayer.prepare()

mMediaPlayer.start()

} catch (IOException e) {

e.printStackTrace()

}

}

@Override

public void surfaceCreated(SurfaceHolder holder) {

}

@Override

public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

}

@Override

public void surfaceDestroyed(SurfaceHolder holder) {

mMediaPlayer.release()

mMediaPlayer = null

}

}

虽然Camera作为第一代原生android所提供的相机类一直被开发者甚至Google官方开发人员所诟病,但为了兼容和适配Android版本5.0以下的App应用,我们别无选择。因此,有了本篇文档详细阐述1.0版的Camera 是如何使用的。本篇使用的是SurfaceView与Camera类。

文档下文会在拍照流程中的不同的阶段应用到上述四个角度,而“终端自然方向”贯穿整个流程当中。这一个方向、四个角度非常重要,缺一不可,是支撑相机Camera 系列API的关键。在设计NXDesign的相机项目中,经过对官方文档的研读和各路资料的调研之后发现,我们在网络上查到的博客类相关资料有80%的实现方式是存在问题的,当然,这也可以归咎于该API其本身确实不好用,如果不对源码注释进行仔细研究,很容易对开发者产生误导。

更加准确的说,相机的生命周期是依托于SurfaceView的创建和销毁来完成的。SurfaceView的作用是提供相机内容的实时预览。我们需要在surfaceview创建好之后打开相机使用相机资源,在surfaceview被销毁后释放相机资源。

surfaceview 提供了holder机制向调用方通知surfaceview的变化时机,为了在不同的时机对相机资源做不同的事情,需要调用SurfaceHolder.addCallback()方法。

现在的Android手机一般会有多个摄像头,但根据其方向可以归为两类: CAMERA_FACING_BACK 和 CAMERA_FACING_FRONT 。在打开摄像头之前,首先需要获取相机资源,判断相机个数 Camera.getNumberOfCameras() 。每个相机对应一个CameraInfo,它的定义如下:

这里涉及到一个重要概念:相机图像传感器(camera sensor),想要理解上述注释的含义,就需要先理解下图内容。

左图是通常情况下,我们对view的x y方向的认知,以屏幕的左上角为原点向右为x正方向,向下为y正方向;但是,右图描述的是绝大多数情况下, 相机图像传感器 的起始位置和方向判定。与view不同的是,传感器以手机屏幕在自然方向上的右上角为原点,向下为x正方向,向左为y正方向。因此,我们理解上述注释就不难了。如果相机自带的传感器顶部与终端自然方向(手机屏幕的硬件方向,一般手机都是竖直方向,也就是文档中说的naturally tall screen)的右边缘一致,则这个值就是90度。如果前置摄像头传感器的顶部与手机自然方向一致,则这个值就是270度。

当我们定义startCamera()方法时,要做5件事情,1.遍历摄像头cameraId,找到想要打开的摄像头(前置还是后置);2.获取摄像头信息,主要获取orientation;3. 设置相机DisplayOrientation 4.设置相机参数,主要是宽高比、对焦模式、图片格式、setRotation等。5. 向camera设置surfaceview.viewholder,并且startPreview。主要逻辑如下:

拿到cameraInfo.orientation之后,要调用camera.setDisplayOrientation设置进去,保证通过surfaceview预览到的取景跟当前的手机方向保持一致,但是,setDisplayOrientation设置的其实是经过两个角度计算之后的复合角度,而并不单纯是cameraInfo.orientation。正确的做法是这样的:先获取手机屏幕的旋转方向,然后与cameraInfo.orientation加和得到最终角度。通常情况下,如果我们设置相机为portrait,则不用考虑rotation。这也是为什么绝大部分网络资料中都会粗暴的写入一个90度完事儿而并没有解释这么做的道理。

调用camera.takePicture(null, null, pictureCallback)

这里需要做的仅仅是将callback中返回的data存储为File。需要注意的是,data中会包含setRotation()方法中的角度信息,因此如果直接使用Bitmap工具类生成bitmap,再进行存储或者展示,生成出来的图像其实是缺失了旋转角度的原始方向,这十有八九会发生图像展示角度错误的情况。因此,需要直接保存,再通过Exif工具类读取File中的角度信息(当然Exif工具类就是为了读取File中的各种信息而生的,比如拍照时间、经纬度等等)。

基于Camera API,

surfaceview的预览需要setDisplayOrientation(),入参角度与CameraInfo.orientation(传感器偏角)和WindowManager.default.displayOrientation(屏幕旋转角度)两个角度有关。

相机拍照前需要setRotation(),入参角度与CameraInfo.orientation(传感器偏角)和OrientationEventListener返回的orientation(终端自然角度偏角)有关,二者的换算结果就是图像写入偏角,该偏角意味着图像被顺时针旋转该角度就能够回正展示。

LayoutParams lp = surfaceView.getLayoutParams()

lp.height = 720

lp.width = 480

surfaceView.setLayoutParams(lp) setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE)


欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/tougao/11314105.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-05-15
下一篇 2023-05-15

发表评论

登录后才能评论

评论列表(0条)

保存