技术分享:Android视频的操作

上节课我们着重介绍了Android中的音频的处理,通过学习,我们已经熟悉并掌握了多媒体开发的几个操作,大致可以分为:a

  • 播放和采集
  • 编解码处理
  • 算法处理,实现特殊功能
  • 标准协议以及播放器工具类的开发

本节课我们来看一下Android的视频的相关操作。Android提供了常见的视频的编码、解码机制。使用Android自带的MediaPlayer、MediaController等类可以很方便的实现视频播放的功能。支持的视频格式有MP4和3GP等。这些多媒体数据可以来自于Android应用的资源文件,也可以来自于外部存储器上的文件,甚至可以是来自于网络上的文件流。

一、视频的播放

1.1 MediaController + VideoView

这种方式是最简单的实现方式。其中两个核心的API是:

  • VideoView:继承了SurfaceView同时实现了MediaPlayerControl接口。
  • MediaController:Android封装的辅助控制器,带有暂停,播放,停止,进度条等控件。

通过VideoView+MediaController可以很轻松的实现视频播放、停止、快进、快退等功能。

a、什么是SurfaceView

在之前的课程中,我们接触到的所有的UI控件或者布局都是View的子类,常用的View有布局组件 ConstraintLayout、RelativeLayout 与基本的显示组件ImageView、TextView 等。我们再来回顾下Android的视图渲染机制:View是通过刷新来重绘视图,系统通过发信号来进行屏幕的重绘,刷新的时间间隔是16ms,如果我们可以在16ms以内将绘制工作完成,则没有任何问题,如果我们绘制过程逻辑很复杂,并且我们的界面更新还非常频繁,这时候就会造成界面的卡顿。

在一些要求高效率的视图更新和绘制的场景中,该如何保证视图的流畅效果呢?Android为开发者提供了SurfaceView。我们可以对SurafceView和View做一个简单的对比说明:

  • 1、View适用于主动更新的情况,而SurfaceView则适用于被动更新的情况,比如频繁刷新界面。
  • 2、 View在主线程中对页面进行刷新,而SurfaceView则开启一个子线程来对页面进行刷新。
  • 3、View在绘图时没有实现双缓冲机制,SurfaceView在底层机制中就实现了双缓冲机制。

要想使用SurfaceView需要经过创建、初始化、使用三个步骤。

Surface用来处理由屏幕合成器管理的原始缓冲区。Surface通常由图像缓冲区的使用者(例如SurfaceTexture、MediaRecorder 或 Allocation)创建或由其创建,并交给某种类型的生产者(例如OpenGL、MediaPlayer 或 CameraDevice)进行绘制。

结论:Android 平台上,无论开发者使用什么渲染 API,一切内容都会渲染到 Surface。Surface 表示缓冲队列中的生产方,在 Android 平台上创建的每个窗口都由 Surface 提供支持。SurfaceView 是对 Surface 的包装,用来向 View 树提供Surface的一个 View。

b、什么是TextureView

通过上面的内容我们已经了解到:SurfaceView 的工作方式是创建一个置于应用窗口之后的新窗口,这种方式的效率非常高,因为 SurfaceView 窗口刷新的时候不需要重绘应用程序的窗口。但是同时,使用SurfaceView也有一些非常不便的限制。因为 SurfaceView 的内容不在应用窗口上,所以不能使用变换(平移、缩放、旋转等),不能使用UI控件的一些特性。

为了解决上述SurfaceView可能出现的这个问题,Android 4.0中引入了TextureView。与SurfaceView相比,TextureView并没有创建一个单独的 Surface用来绘制,这使得它可以像一般的View一样执行一些变换操作。需要注意的一点是:Textureview 必须在硬件加速开启的窗口中使用,而且 TextureView 使用也更加耗电,平均 TextureView 比 SurfaceView 会多用30%的电量。

1.2 MediaPlayer + SurfaceView + 自定义控制器

VideoView的实现方式很简单,但是因为API都是系统自带的封装好的类,所以无论是播放器的大小、位置以及控制都不受我们控制,也就是说没有办法自定义。如果想要实现自定义的视频播放和控制,可以使用第二种方式:MediaPlayer + SurfaceView + 自定义控制器。具体的操作步骤如下:

  • 1、建MediaPlayer对象,加载视频文件。数据来源支持三种方式,前文已经介绍过,此处不再赘述。
  • 2、使用SurfaceView组件,设置SurfaceView的SurfaceHolder的Callback监听器。
  • 3、通过setDisplay将MeddiaPlayer和SurfaceHolder进行绑定。
  • 4、prepareAsync方法准备视频数据。
  • 5、通过start方法开始播放

1.3 Mediaplayer + SurfaceView + MediaController

MediaController是Android系统自带的控制器。其实就是前两种的结合。此处不再赘述。

二、图像和视频的采集

视频的采集需要调用硬件摄像头,对应的API是Camera。使用Camera又分为两类:

  • Android 5.0以下:Camera
  • Android 5.0以上:Camera2

Camera相关API也体现了Android生态碎片化的特点,有时候还会遇到各家手机厂商对Camera2的支持程度也各不相同,这就导致开发者在相机开发中要花费很大精力来处理兼容性问题。

2.1 相机的使用和开发流程

  • 检测并访问相机资源:检查手机是否存在相机资源,如果存在则请求访问相机资源。
  • 创建预览界面:创建继承自SurfaceView并实现SurfaceHolder接口的拍摄预览类。有了拍摄预览类,即可创建一个布局文件,将预览画面与设计好的用户界面控件融合在一起,实时显示相机的预览图像。
  • 设置拍照监听器:给用户界面控件绑定监听器,使其能响应用户操作, 开始拍照过程。
  • 处理:拍照并保存文件,将拍摄获得的图像转换成位图文件,最终输出保存成各种常用格式的图片或者可以采集视频信息。
  • 释放相机资源:相机是一个共享资源,当相机使用完毕后,必须正确地将其释放,以免其它程序访问使用时发生冲突。

2.2 相机开发注意事项

从API到硬件,再到厂商的兼容性问题,相机开发中需要格外注意的几个点首先要有心理准备。

  • API兼容性问题:Android 5.0以下的API是Camera,Android5.0以上是Camera2;4.0以下只能使用SurfaceView,4.0以上多了选择TextureView;Android 权限升级后使用相机资源时的动态权限适配问题。
  • 硬件设备兼容性问题:前文已经提到过,,Camera/Camera2里的各种特性在有些手机厂商的设备实现方式和支持程度是不一样的,这个需要做兼容性测试,坑要一点点填。
  • 应用开发时业务场景下的硬件资源的管理问题。比如应用进入到后台或者锁屏等操作,相机资源该如何管理和操作。应用界面该如何绘制和管理等。

2.3 Camera和Camera2

Camera

Camera API中主要涉及以下几个关键类:

  • Camera:操作和管理相机资源,支持相机资源切换,设置预览和拍摄尺寸,设置光圈、曝光等相关参数。
  • SurfaceView:用于绘制相机预览图像,提供实时预览的图像。
  • SurfaceHolder:用于控制Surface的一个抽象接口,它可以控制Surface的尺寸、格式与像素等,并可以监视Surface的变化。
  • SurfaceHolder.Callback:用于监听Surface状态变化的接口。Callback中有三个函数:
    • surfaceCreated: 当Surface第一次创建的时候调用,可以在这个方法里调用camera.open()、camera.setPreviewDisplay()来实现打开相机以及连接Camera与Surface 等操作。
    • surfaceChanged:当Surface的size、format等发生变化的时候调用,可以在这个方法里调用camera.startPreview()开启预览。
    • surfaceDestroyed:当Surface被销毁的时候调用,可以在这个方法里调用camera.stopPreview(),camera.release()等方法来实现结束预览以及释放。

相机的分类和操作:

  • 分类:前置和后置
    • Camera.CameraInfo.CAMERA_FACING_FRONT:前置
    • Camera.CameraInfo.CAMERA_FACING_BACK:后置
  • open:打开相机
  • getParameters:获取相机参数
    • FLASH_MODE_AUTO:自动模式,当光线较暗时自动打开闪光灯;
    • FLASH_MODE_OFF:关闭闪光灯
    • FLASH_MODE_ON:拍照时闪光灯
    • FOCUS_MODE_AUTO:自动对焦模式
    • FOCUS_MODE_FIXED:固定焦距模式
    • SCENE_MODE_NIGHT:夜间场景;
    • ...
  • takePicture:拍照
    • ShutterCallback:拍照的瞬间被回调,播放拍照音效;
    • PictureCallback raw:未经压缩的图像数据
    • PictureCallback postview:postview类型的图像数据
    • PictureCallback jpeg:经过JPEG压缩的图像数据
  • release():释放相机资源

Camera2

Camera2 API中主要涉及的关键API:

  • CameraManager:摄像头管理器,用于打开和关闭系统摄像头
  • CameraCharacteristics:描述摄像头的各种特性,可通过CameraManager的getCameraCharacteristics(id)方法获取
  • CameraDevice:系统摄像头,类似于早期的Camera
  • CameraCaptureSession:Session类,当使用拍照、预览等功能时,需要先创建该类的实例,然后通过该实例里的方法进行控制(例如:拍照 capture())
  • CaptureRequest:拍照、预览等操作都需要先传入CaptureRequest参数,具体的参数控制也是通过其成员变量来设置
  • CaptureResult:描述拍照完成后的结果。

以上的核心API,Camera2的运行原理如下图所示

 

通过管道将安卓设备和摄像头之间联通起来,系统向摄像头发送 Capture 请求,摄像头会返回 CameraMetadata,中间的通信建立在CameraCaptureSession的会话中。

Camera2的使用流程大致如下:

  • 工作线程:创建一个专门的线程用于Camera的具体操作,可以使用HandlerThread,Android提供的具备Handler的Thread,用于通信。
  • 预览画面:可以是SurfaceView或者TextureView,用于显示采集的数据画面。
  • 获取Camera设备:使用CameraManager找到合适的Camera设备,得到相关参数,调整预览画面等操作。
  • 开启设备:调用CameraManager的openCamera,打开指定摄像头
    • cameraId:摄像头id
    • CameraDevice.StateCallback:相机回调状态
    • handler:消息数据处理

 

  • CameraDevice:通过StateCallback获取到设备Camera对象
  • Camera与视图绑定:调用CameraDevice.createCaptureRequest方法,用于将Surface和Camera进行绑定,让Surface可以接收Camera的数据。
  • 建立会话:调用CameraDevice的createCaptureSession方法创建session对象,创建成功后会回调StateCallback接口方法onConfigured,表明会话已经建立。
  • 发送请求:在创建好的会话中通过setRepeatingRequest发送请求,即向Camera发送命令。

2.4 视频的采集

路线既可以调用系统实现,也可以自定来实现。

Intent

我们都知道Intent是Android中的组件之间的通信工具,如果想要调用系统的录像功能,可以设置Intent意图,如下所示:

Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
intent.putExtra(MediaStore.EXTRA_DURATION_LIMIT, 5);//限制时长 s
intent.putExtra(MediaStore.EXTRA_SIZE_LIMIT, 1024*1024);//限制大小 
intent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 1);//设置质量
intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);//设置输出位置
startActivityForResult(intent, 1);
​

另外在onActivityResult中接收:

protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if(resultCode==RESULT_OK){
        Uri uri = data.getData();
        //视频地址
        String videoPath = getPathFromUri(this,uri);
    }
}
​
public static String getPathFromUri(Context mContext,Uri contentUri){
    String[] proj = { MediaStore.Images.Media.DATA };
    CursorLoader loader = new CursorLoader(mContext, contentUri, proj, null, null, null);
    Cursor cursor = loader.loadInBackground();
    int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
    cursor.moveToFirst();
    return cursor.getString(column_index);
}

MediaRecorder

如果需要自定义录制视频,需要使用前文讲到的MediaRecorder类来辅助完成。结合Camera2来进行实现。

其操作方法与预览和拍照过程相似,此处直接以案例进行演示和讲解。