电商直播系统源码搭建过程中涉及到音视频技术,想要深入研究,需要对音频和视频有一定的了解,这里我们会讨论直播中的技术实现,涉及到必要的底层实现或者必要的音视频知识会有一些相关链接或者概念上的阐述。
1 直播流程概述
先来看下开启一场直播,中间的流程是怎样的。如图:
Fig.1
从上图可以看到,一场直播的流程为:
(1)移动端视频设备、音频设备采集到音视频数据
(2)将采集到的音频数据和视频数据进行编码和封装
(3)将封装后的数据通过网络传输到后端
再经过转码、分发、写入分布式系统等,以及经过CDN(content delivery net,内容分发网)传输给观众端。转码,分发,切片等过程是将数据传给后端,后端进行的一系列操作,而CDN是将处理的音视频数据内容进行分发的网络,这里不讨论。我们只讨论属于移动端的音视频数据处理及传输的1),2),3)过程。
2.数据采集
直播流程中,主播端的数据处理包括:数据采集、编码和封装。数据的采集和处理包括视频和音频的采集处理。
2.1 视频数据的采集
相机的采集及预览可以通过两个方式实现:
(1)SurfaceView+Camera
(2)TextureView+Camera
这里我们详细讨论第一种实现方案。
在Android层,实现从打开相机到得到图像数据以及预览的过程大致可以分为两部分:从硬件得到Camera预览数据、SurfaceView(也可以是TextureView)显示画面,如下图所示。
Fig.2
SurfaceView是Camera数据的显示界面。想要将SurfaceView和Camera联系起来,需要用到Surface和SurfaceHolder。它们之间的关系是:
Fig.3
这里,Surface是用来处理屏幕显示内容合成器所管理的原始缓存区的工具。它通常由图像缓冲区的消费者来创建(如:SurfaceTexture,MediaRecorder,编解码时的MediaCodec),然后被移交给生产者(如:MediaPlayer)或者是显示到其上(如:CameraDevice)。正如Fig.3所示,Google提供了一个SurfaceHolder类来对Surface的属性进行控制。
接下来,分别对这三个部分进行介绍。
2.1.1 SurfaceHolder
一个抽象接口,给持有surface的对象使用。它可以控制surface的大小和格式,编辑surface中的像素格式,以及监听surface的变化。这个接口通常通过SurfaceView类获得,电商直播系统源码有3个回调方法:
//surface第一次创建时回调
surfaceCreated(SurfaceHolder holder)
//surface变化的时候回调(格式/大小),如设置横竖屏
surfaceChanged(SurfaceHolder holder, int format, int width, int height)
//surface销毁的时候回调
surfaceDestroyed(SurfaceHolder holder)
2.1.2 SurfaceView类
SurfaceView继承自View,其中有两个成员变量,一个是Surface对象,一个是SuraceHolder对象。surfaceView用这两个对象实现什么目的呢?
· SurfaceView把Surface显示在屏幕上。Surface是处理原始缓冲区的工具,可以理解为一块“还未看见”的画布。即在SurfaceView显示前一帧的画面时,Surface在准备即将要展示的下一帧的画面。等到下一帧准备好,就可以进行刷新。
· SurfaceView通过SuraceHolder告诉我们Surface的状态(创建、变化、销毁)
· 通过getHolder()方法获得当前SurfaceView的SuraceHolder对象,然后就可以对SuraceHolder对象添加回调来监听Surface的状态
即SuffaceView.getHolder().addCallBack(SurfaceHolder.Callback);
2.1.3 Camera
电商直播系统源码从Camera得到数据这部分来看,Camera初始化需要做的:
//1. 打开摄像头,这里,参数id是指开启前置还是后置摄像头,1代表前置,0代表后置
camera=android.hardware.Camera.open(int id);
//2. 设置各个参数,例如:
Camera.Parameters parameters = mCamera.getParameters(); //获取摄像头参数
// 可以根据情况设置参数
// 镜头缩放
parameters.setZoom();
// 设置预览照片的大小
parameters.setPreviewSize(200, 200);
// 设置预览照片时每秒显示多少帧的最小值和最大值
parameters.setPreviewFpsRange(4, 10);
// 设置图片格式
parameters.setPictureFormat(ImageFormat.JPEG);
// 设置JPG照片的质量 图片的质量[0-100],100最高
parameters.set("jpeg-quality", 85);
// 设置照片的大小
parameters.setPictureSize(200, 200);
camera.setDisplayOrientation(90);// 预览方向,一般是通过相机设置方向来实现。
最后,将参数传给Camera
mCamera.setParameters(parameters);
此外还需要注意一个问题。即相机图像数据来自于相机硬件的图像传感器,这个传感器有一个默认的取景方向。前置摄像头需要设置展示方向为270度(camera.setDisplayOrientation(270)),后置摄像头需要设置展示方向90度(camera.setDisplayOrientation(90))。
以上就是camera的初始化。想要将camera采集到的数据展示出来,还需要一个必不可少的将Surface和Camera联系起来的环节:
mCamera.setPreviewDisplay(holder);
然后,将camera开启预览:
mCamera.startPreview();
此过程在创建Surface成功后即可添加。一般可以添加在SurfaceHolder.Callback接口的surfaceCreated方法或者surfaceChanged接口中。
最后,记得将Camera释放:
mCamera.release();
至于相机开启后,相机初始化,native层变化的过程,可以参考电商直播系统源码:
blog:
2.2 获取相机数据
上述过程是直接将相机得到的数据展示在屏幕上。然而,在开发过程中,有些需求是获取相机得到的像素数据,以实现其他需求。那么如何在android中获取相关图像数据呢?
Google提供了Camera的相关接口:Camera.PreviewCallback
在接口的方法中即可获得byte[]数组。这个数组是将像素(Android 中Google支持的 Camera Preview Callback的YUV常用格式有两种:一个是NV21,一个是YV12。Android一般默认使用YCbCr_420_SP的格式(NV21))按照一定规则排列得到的一维数组。如果想要得到某个格式下的像素byte数组,可以通过相机参数设置来实现:
Camera.Parameters parameters = camera.getParameters();
parameters.setPreviewFormat(ImageFormat.NV21);
camera.setParameters(parameters);
之后,通过Camera.PreviewCallback接口的onPreviewFrame方法中获取到像素数组并展示。具体电商直播系统源码如下:
mCamera.setPreviewCallback(new Camera.PreviewCallback() {
@Override
public void onPreviewFrame(byte[] data, Camera camera) {
// 处理data,这里面的data数据就是NV21格式的数据,将数据显示在ImageView控件上面
mPreviewSize = camera.getParameters().getPreviewSize();// 获取尺寸,格式转换的时候要用到
// 取发YUVIMAGE
YuvImage yuvimage = new YuvImage(
data,
ImageFormat.NV21,
mPreviewSize.width,
mPreviewSize.height,
null);
mBaos = new ByteArrayOutputStream();
// yuvimage转换成jpg格式
yuvimage.compressToJpeg(new Rect(0, 0, mPreviewSize.width, mPreviewSize.height), 100, mBaos);// 80--JPG图片的质量[0-100],100最高
mImageBytes = mBaos.toByteArray();
// 将mImageBytes转换成bitmap
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.RGB_565;
mBitmap = BitmapFactory.decodeByteArray(mImageBytes, 0, mImageBytes.length, options);
mImageView.setImageBitmap(rotateBitmap(mBitmap, getDegree()));
以上,就是通过camera实现预览以及获取数据后转换为Bitmap的全过程。