音视频开发采集部分需要的知识前三章中已经写明,这章是对前面三章的整理简化,首先上实现类,代码很简单,文末为基类代码
class LiveBroadcastActivity : BaseVideoActivity() {
//预览CaptureRequest.Builder
private lateinit var previewCaptureRequest: CaptureRequest.Builder
override fun getLayoutId(): Int {
return R.layout.activity_live_broadcast;
}
override fun init() {
//开启surfaceTextureListener监听
textureView.surfaceTextureListener = surfaceTextureListener
//切换相机
switchCamera.setOnClickListener {
switchCamera()
}
}
/**
* 创建一个Session
*/
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
override fun createCaptureSession(camera: CameraDevice):List<Surface>{
//创建一个预览的CaptureRequest
previewCaptureRequest = camera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW)
// 设置预览输出的 Surface
previewCaptureRequest.addTarget(surface)
val outputs = ArrayList<Surface>()
outputs.add(surface)
return outputs
}
/**
* 返回预览的CaptureRequest
*/
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
override fun setRepeatingRequest(session: CameraCaptureSession): CaptureRequest{
//开始录音
startRecording()
return previewCaptureRequest.build()
}
/**
* 音频编码,注意这里是在子线程中的
*/
override fun audioCoding(buffer: ByteArray, len: Int) {
}
/**
* 老相机API数据回调接口
*/
override fun oldSendVideoData(data: ByteArray) {
}
/**
* 自动修改textureView宽高以适应不同预览比例
*/
override fun onWindowFocusChanged(hasFocus: Boolean) {
super.onWindowFocusChanged(hasFocus)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
val width = textureView.width
val height = textureView.height
val proportion1 = size.width.toFloat() / size.height
val proportion2 = height.toFloat() / width
if (proportion1 > proportion2) {
val layoutParams = textureView.layoutParams
layoutParams.width = (height * proportion1 + .5).toInt()
textureView.layoutParams = layoutParams
} else if (proportion1 < proportion2) {
val layoutParams = textureView.layoutParams
layoutParams.height = (width * proportion1 + .5).toInt()
textureView.layoutParams = layoutParams
}
}
}
}
是不是感觉瞬间解脱,再也不用去写那些繁琐的代码了,接下来总结一下是如何实现的,开启定义好的surfaceTextureListener便会自动采集音视频
textureView.surfaceTextureListener = surfaceTextureListener
使用是不是很简单,然后来看一下基类是怎么实现的
/**
* 获取Surface的回调
*/
val surfaceTextureListener = object : TextureView.SurfaceTextureListener {
//SurfaceTexture大小发生变化时调用
@SuppressLint("Recycle")
override fun onSurfaceTextureSizeChanged(
surfaceTexture: SurfaceTexture,
width: Int,
height: Int
) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
//获取相机属性类
val cameraCharacteristics = getCameraCharacteristics()
//设置预览尺寸
size = setPreviewSize(surfaceTexture, cameraCharacteristics)
surface = Surface(surfaceTexture)
}
}
override fun onSurfaceTextureUpdated(surface: SurfaceTexture?) {
}
override fun onSurfaceTextureDestroyed(surfaceTexture: SurfaceTexture?): Boolean {
//关闭老相机API预览
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
stopPreview()
}
surface.release()
return true
}
override fun onSurfaceTextureAvailable(
surfaceTexture: SurfaceTexture,
width: Int,
height: Int
) {
//初始化AudioRecord
initAudioRecord()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
//初始化Camera2
initCamera2()
//获取相机属性类
val cameraCharacteristics = getCameraCharacteristics()
//设置预览尺寸
size = setPreviewSize(surfaceTexture, cameraCharacteristics)
surface = Surface(surfaceTexture)
//开启摄像头
openCamera()
}else{
//开启老相机API预览
startPreview(surfaceTexture)
}
}
}
在onSurfaceTextureAvailable方法中初始化AudioRecord和Camera,初始化完成后便会开启摄像头
/**
* 打开摄像头
*/
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
fun openCamera() {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) {
cameraManager.openCamera(cameraId, callback, getBackgroundHandler())
} else {
val dialog = AlertDialog.Builder(this)
dialog.setTitle("开启相机失败").setMessage("缺少开启相机的权限").setCancelable(false)
dialog.setNegativeButton("取消") { _, _ ->
}
dialog.setPositiveButton("授权") { _, _ ->
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
intent.data = Uri.parse("package:$packageName")
startActivity(intent)
}
dialog.show()
}
}
/**
* 打开摄像头的回调
*/
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
private val callback = object : CameraDevice.StateCallback() {
//成功打开时的回调,可以得到一个 CameraDevice 实例
override fun onOpened(camera: CameraDevice) {
cameraDevice = camera
outputs = createCaptureSession(camera)
//创建一个Session
camera.createCaptureSession(
outputs,
mSessionCallback,
getBackgroundHandler()
)
}
//当 camera 不再可用时的回调,通常在该方法中进行资源释放的操作
override fun onDisconnected(camera: CameraDevice) {
showToast("camera不再可用")
}
// 当 camera 打开失败时的回调,error 为具体错误原因,通常在该方法中也要进行资源释放的操作
override fun onError(camera: CameraDevice, error: Int) {
camera.close()
showError(error)
releaseBackgroundThread()
}
//相机关闭时回调
override fun onClosed(camera: CameraDevice) {
super.onClosed(camera)
cameraCaptureSession?.close()
}
}
/**
* 创建一个Session
*/
abstract fun createCaptureSession(camera: CameraDevice): List<Surface>
在开启摄像头的回调中可以看到使用了一个抽象方法,继承这个基类需要重写这个方法,便可以得到CameraDevice,接下来你想怎么玩就是你的事了
/**
* 创建一个Session
*/
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
override fun createCaptureSession(camera: CameraDevice):List<Surface>{
//创建一个预览的CaptureRequest
previewCaptureRequest = camera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW)
// 设置预览输出的 Surface
previewCaptureRequest.addTarget(surface)
val outputs = ArrayList<Surface>()
outputs.add(surface)
return outputs
}
接下来是创建预览Session的回调
/**
* 创建预览Session的回调
*/
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
val mSessionCallback = object : CameraCaptureSession.StateCallback() {
override fun onConfigured(session: CameraCaptureSession) {
cameraCaptureSession = session
val captureRequest = setRepeatingRequest(session)
session.setRepeatingRequest(
captureRequest,
captureCallback,
getBackgroundHandler()
)
}
//创建失败
override fun onConfigureFailed(session: CameraCaptureSession) {
showToast("创建Session失败")
}
}
/**
* 开始预览,即设置反复请求
*/
abstract fun setRepeatingRequest(session: CameraCaptureSession):CaptureRequest
同样使用了一个抽象方法,定义这个抽象方法主要是灵活性考虑,当然如果你不考虑可以写在基类中
/**
* 返回预览的CaptureRequest
*/
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
override fun setRepeatingRequest(session: CameraCaptureSession): CaptureRequest{
//开始录音
startRecording()
return previewCaptureRequest.build()
}
为了兼容5.0以下的手机,定义了一个老相机API数据回调接口,只有5.0以下的手机这个方法才会生效
/**
* 老相机API数据回调接口
*/
override fun oldSendVideoData(data: ByteArray) {
}
最后是音频的抽象方法,可以直接在这里得到音频数据
/**
* 音频编码,注意这里是在子线程中的
*/
override fun audioCoding(buffer: ByteArray, len: Int) {
}
为了防止内存泄露做了一些处理
override fun onDestroy() {
super.onDestroy()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
cameraDevice?.close()
if(outputs != null){
for (surface in outputs!!) {
surface.release()
}
}
endRecording()
releaseBackgroundThread()
}else{
stopPreview()
}
}
最后附上一个自适应比例
/**
* 自动修改textureView宽高以适应不同预览比例
*/
override fun onWindowFocusChanged(hasFocus: Boolean) {
super.onWindowFocusChanged(hasFocus)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
val width = textureView.width
val height = textureView.height
val proportion1 = size.width.toFloat() / size.height
val proportion2 = height.toFloat() / width
if (proportion1 > proportion2) {
val layoutParams = textureView.layoutParams
layoutParams.width = (height * proportion1 + .5).toInt()
textureView.layoutParams = layoutParams
} else if (proportion1 < proportion2) {
val layoutParams = textureView.layoutParams
layoutParams.height = (width * proportion1 + .5).toInt()
textureView.layoutParams = layoutParams
}
}
}
基类
abstract class BaseVideoActivity() : BaseActivity() {
private lateinit var mBackgroundThread: HandlerThread
private var mBackgroundHandler: Handler? = null
//摄像头管理类
lateinit var cameraManager: CameraManager
//摄像头id列表
lateinit var cameraIdList: Array<String>
//第几个摄像头
var index = 0
//当前摄像头id
lateinit var cameraId: String
//Surface集合
private var outputs: List<Surface>? = null
//当前摄像头
private var cameraDevice: CameraDevice? = null
//Session
private var cameraCaptureSession: CameraCaptureSession? = null
//预览Surface
lateinit var surface: Surface
//相机预览分辨率
lateinit var size: Size
/**
* 获取Surface的回调
*/
val surfaceTextureListener = object : TextureView.SurfaceTextureListener {
//SurfaceTexture大小发生变化时调用
@SuppressLint("Recycle")
override fun onSurfaceTextureSizeChanged(
surfaceTexture: SurfaceTexture,
width: Int,
height: Int
) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
//获取相机属性类
val cameraCharacteristics = getCameraCharacteristics()
//设置预览尺寸
size = setPreviewSize(surfaceTexture, cameraCharacteristics)
surface = Surface(surfaceTexture)
}
}
override fun onSurfaceTextureUpdated(surface: SurfaceTexture?) {
}
override fun onSurfaceTextureDestroyed(surfaceTexture: SurfaceTexture?): Boolean {
//关闭老相机API预览
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
stopPreview()
}
surface.release()
return true
}
override fun onSurfaceTextureAvailable(
surfaceTexture: SurfaceTexture,
width: Int,
height: Int
) {
//初始化AudioRecord
initAudioRecord()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
//初始化Camera2
initCamera2()
//获取相机属性类
val cameraCharacteristics = getCameraCharacteristics()
//设置预览尺寸
size = setPreviewSize(surfaceTexture, cameraCharacteristics)
surface = Surface(surfaceTexture)
//开启摄像头
openCamera()
}else{
//开启老相机API预览
startPreview(surfaceTexture)
}
}
}
/**
* 初始化Camera2
*/
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
fun initCamera2() {
cameraManager = application.getSystemService(Context.CAMERA_SERVICE) as CameraManager
cameraIdList = cameraManager.cameraIdList
cameraId = cameraIdList[index]
}
/**
* 获取CameraCharacteristics相机属性类
*/
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
fun getCameraCharacteristics(): CameraCharacteristics {
return cameraManager.getCameraCharacteristics(cameraId)
}
/**
* 设置预设的预览尺寸
*/
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
fun setPreviewSize(@NotNull surfaceTexture: SurfaceTexture, cameraCharacteristics: CameraCharacteristics): Size {
val aspectRatios = ArrayList<Float>()
aspectRatios.add(16.toFloat() / 9)
aspectRatios.add(4.toFloat() / 3)
aspectRatios.add(18.toFloat() / 9)
val size = getPreviewSize(cameraCharacteristics, aspectRatios)
surfaceTexture.setDefaultBufferSize(size.width, size.height)
return size
}
/**
* 获取预览尺寸
* 参数2:预览尺寸比例的集合,按加入顺序寻找预览尺寸并返回
*/
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
fun getPreviewSize(@NotNull cameraCharacteristics: CameraCharacteristics, aspectRatios: ArrayList<Float>): Size {
for (aspectRatio in aspectRatios) {
val size = getPreviewSize(cameraCharacteristics, aspectRatio)
if (size != null) {
return size
}
}
return Size(1280, 720)
}
/**
* 获取预览尺寸
* 参数2:预览尺寸比例,如4:3,16:9
*/
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
fun getPreviewSize(@NotNull cameraCharacteristics: CameraCharacteristics, aspectRatio: Float): Size? {
val streamConfigurationMap =
cameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)
val supportedSizes = streamConfigurationMap.getOutputSizes(SurfaceTexture::class.java)
for (size in supportedSizes) {
if (size.width.toFloat() / size.height == aspectRatio) {
return size
}
}
return null
}
/**
* 打开摄像头
*/
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
fun openCamera() {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) {
cameraManager.openCamera(cameraId, callback, getBackgroundHandler())
} else {
val dialog = AlertDialog.Builder(this)
dialog.setTitle("开启相机失败").setMessage("缺少开启相机的权限").setCancelable(false)
dialog.setNegativeButton("取消") { _, _ ->
}
dialog.setPositiveButton("授权") { _, _ ->
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
intent.data = Uri.parse("package:$packageName")
startActivity(intent)
}
dialog.show()
}
}
/**
* 打开摄像头的回调
*/
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
private val callback = object : CameraDevice.StateCallback() {
//成功打开时的回调,可以得到一个 CameraDevice 实例
override fun onOpened(camera: CameraDevice) {
cameraDevice = camera
outputs = createCaptureSession(camera)
//创建一个Session
camera.createCaptureSession(
outputs!!,
mSessionCallback,
getBackgroundHandler()
)
}
//当 camera 不再可用时的回调,通常在该方法中进行资源释放的操作
override fun onDisconnected(camera: CameraDevice) {
}
// 当 camera 打开失败时的回调,error 为具体错误原因,通常在该方法中也要进行资源释放的操作
override fun onError(camera: CameraDevice, error: Int) {
camera.close()
showError(error)
releaseBackgroundThread()
}
//相机关闭时回调
override fun onClosed(camera: CameraDevice) {
super.onClosed(camera)
cameraCaptureSession?.close()
}
}
/**
* 创建一个Session
*/
abstract fun createCaptureSession(camera: CameraDevice): List<Surface>
/**
* 创建预览Session的回调
*/
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
val mSessionCallback = object : CameraCaptureSession.StateCallback() {
override fun onConfigured(session: CameraCaptureSession) {
cameraCaptureSession = session
val captureRequest = setRepeatingRequest(session)
session.setRepeatingRequest(
captureRequest,
captureCallback,
getBackgroundHandler()
)
}
//创建失败
override fun onConfigureFailed(session: CameraCaptureSession) {
showToast("创建Session失败")
}
}
/**
* 开始预览,即设置反复请求
*/
abstract fun setRepeatingRequest(session: CameraCaptureSession):CaptureRequest
/**
* Session进度的回调
*/
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
val captureCallback = object : CameraCaptureSession.CaptureCallback() {
override fun onCaptureCompleted(
session: CameraCaptureSession,
request: CaptureRequest,
result: TotalCaptureResult
) {
super.onCaptureCompleted(session, request, result)
}
override fun onCaptureFailed(
session: CameraCaptureSession,
request: CaptureRequest,
failure: CaptureFailure
) {
super.onCaptureFailed(session, request, failure)
}
}
/**
* 切换摄像头
*/
fun switchCamera() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
if (cameraDevice != null) {
if (index < cameraIdList.size - 1) {
index++
} else {
index = 0
}
cameraId = cameraIdList[index]
cameraDevice?.close()
openCamera()
} else {
showToast("请先开启摄像头")
}
}else{
oldSwitchCamera()
}
}
/**
* 获取BackgroundHandler
*/
fun getBackgroundHandler(): Handler {
if (mBackgroundHandler == null) {
//设置摄像头线程
mBackgroundThread = HandlerThread("CameraBackground")
mBackgroundThread.start()
mBackgroundHandler = Handler(mBackgroundThread.looper)
}
return mBackgroundHandler as Handler
}
/**
* 释放线程资源
*/
fun releaseBackgroundThread() {
mBackgroundHandler?.removeCallbacksAndMessages(null)
mBackgroundHandler = null
mBackgroundThread.quitSafely()
mBackgroundThread.join()
}
/**
* 开启摄像头错误提示
*/
fun showError(error: Int) {
when (error) {
CameraDevice.StateCallback.ERROR_CAMERA_IN_USE -> {
showToast("当前相机设备已经在一个更高优先级的地方打开了")
}
CameraDevice.StateCallback.ERROR_MAX_CAMERAS_IN_USE -> {
showToast("已打开相机数量到上限了,无法再打开新的相机了")
}
CameraDevice.StateCallback.ERROR_CAMERA_DISABLED -> {
showToast("由于相关设备策略该相机设备无法打开")
}
CameraDevice.StateCallback.ERROR_CAMERA_DEVICE -> {
showToast("相机设备发生了一个致命错误")
}
CameraDevice.StateCallback.ERROR_CAMERA_SERVICE -> {
showToast("相机服务发生了一个致命错误")
}
}
}
override fun onDestroy() {
super.onDestroy()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
cameraDevice?.close()
if(outputs != null){
for (surface in outputs!!) {
surface.release()
}
}
endRecording()
releaseBackgroundThread()
}else{
stopPreview()
}
}
// 采样率
private val sampleRateInHz = 44100
// 音频通道 立体声:
val stereo = AudioFormat.CHANNEL_IN_STEREO
lateinit var audioRecord: AudioRecord
//audioRecord能接受的最小的buffer大小
private var bufferSizeInBytes: Int = 0
//录音线程
private var recordingJob: Job? = null
/**
* 初始化AudioRecord
*/
fun initAudioRecord(channelConfig: Int = AudioFormat.CHANNEL_IN_MONO) {
//audioRecord能接受的最小的buffer大小
bufferSizeInBytes = AudioRecord.getMinBufferSize(sampleRateInHz, channelConfig, AudioFormat.ENCODING_PCM_16BIT)
audioRecord = AudioRecord(MediaRecorder.AudioSource.MIC, sampleRateInHz, channelConfig, AudioFormat.ENCODING_PCM_16BIT, bufferSizeInBytes)
}
/**
* 开始录音
*/
fun startRecording() {
recordingJob = GlobalScope.launch(Dispatchers.IO) {
if (bufferSizeInBytes > 0) {
audioRecord.startRecording()
while (isActive) {
val buffer = ByteArray(bufferSizeInBytes)
val len = audioRecord.read(buffer, 0, buffer.size)
if (len > 0) {
//音频编码
audioCoding(buffer, len)
}
}
} else {
launch(Dispatchers.Main) {
showToast("请先初始化AudioRecord类")
}
}
}
}
/**
* 音频编码
*/
abstract fun audioCoding(buffer: ByteArray, len: Int)
/**
* 结束录音
*/
fun endRecording() {
if (audioRecord.recordingState == AudioRecord.RECORDSTATE_RECORDING) {
audioRecord.stop()
}
if (audioRecord.state == AudioRecord.STATE_INITIALIZED) {
audioRecord.release()
}
recordingJob?.cancel()
}
//老相机id
private var oldCameraId = Camera.CameraInfo.CAMERA_FACING_BACK
//老相机SurfaceTexture
private var oldSurfaceTexture:SurfaceTexture? = null
//老相机数据存储数组
private var oldBuffers:ByteArray? = null
//老相机
private var oldCamera:Camera? = null
//老相机预览尺寸
lateinit var oldSize:Camera.Size
/**
* 老相机API开始预览
*/
fun startPreview(surfaceTexture:SurfaceTexture){
if (oldSurfaceTexture == null){
oldSurfaceTexture = surfaceTexture;
}
// 打开摄像头并将展示方向旋转90度
oldCamera = Camera.open(oldCameraId)
oldCamera!!.setDisplayOrientation(90)
val parameters = oldCamera!!.parameters
// 选择合适的预览尺寸
val sizeList = parameters.supportedPreviewSizes
oldSize = getOldSize(sizeList)
parameters.previewFormat = ImageFormat.NV21
//设置预览图像参数
parameters.setPictureSize(oldSize.width,oldSize.height)
parameters.setPreviewSize(oldSize.width,oldSize.height)
oldCamera!!.parameters = parameters
oldCamera!!.setPreviewTexture(oldSurfaceTexture)
//获取预览数据
oldBuffers = ByteArray(oldSize.width * oldSize.height * 4)
oldCamera!!.addCallbackBuffer(oldBuffers)
oldCamera!!.setPreviewCallbackWithBuffer(previewCallback)
oldCamera!!.startPreview()
}
/**
* 获取老相机预览数据回调
*/
private val previewCallback = Camera.PreviewCallback { data, camera ->
camera?.addCallbackBuffer(oldBuffers)
oldSendVideoData(data)
}
/**
* 老相机API预览数据回调
*/
abstract fun oldSendVideoData(data:ByteArray)
/**
* 停止预览
*/
fun stopPreview(){
if (oldCamera != null){
oldCamera!!.stopPreview()
oldCamera!!.release()
oldCamera = null
}
}
/**
* 老camera切换摄像头
*/
private fun oldSwitchCamera(){
if (oldSurfaceTexture != null){
oldCameraId = if (oldCameraId == Camera.CameraInfo.CAMERA_FACING_BACK){
Camera.CameraInfo.CAMERA_FACING_FRONT
}else{
Camera.CameraInfo.CAMERA_FACING_BACK
}
stopPreview()
startPreview(oldSurfaceTexture!!)
}
}
/**
* 获取老相机API的预览大小
*/
private fun getOldSize(sizeList:List<Camera.Size>):Camera.Size{
val aspectRatios = ArrayList<Float>()
aspectRatios.add(16.toFloat() / 9)
aspectRatios.add(4.toFloat() / 3)
aspectRatios.add(18.toFloat() / 9)
for (aspectRatio in aspectRatios){
for (size in sizeList){
if (size.width.toFloat() / size.height == aspectRatio) {
return size
}
}
}
return sizeList[0]
}
}