导图社区 Android直播平台开发编程技术知识框架
Android直播平台开发编程技术知识框架,导图内容简洁、逻辑清晰、重点突出,希望对大家有所帮助~
编辑于2022-11-02 11:44:13 广东Android直播平台开发编程技术知识框架
基础概念
什么叫视频?
概念
静止的画面叫图像(picture)。连续的图像变化每秒超过24帧(frame)画面以上时,根椐视觉暂留原理,人眼无法辨别每付单独的静态画面,看上去是平滑连续的视觉效果。这样的连续画面叫视频。连续图像变化每秒低于24帧画面时,人眼有不连续的感觉叫动画(cartoon)
视频
内容元素
图像 ( Image )
音频 ( Audio )
元信息(Metadata)
对视频信息的说明
编码格式
视频(Video)
主流编码标准
MPEG 标准(由MPEG制定)
Moving Picture Experts Group,动态图像专家组
MPEG-1
MPEG-2
MPEG-3
MPEG-4
MPEG-7
MPEG-21
ITU-T 标准(由VCEG制定)
国际电信联盟电信标准分局
H.261
H.262
H.263
H.263v2
H.264(主流)
H265
主流标准对比
音频(Audio)
AAC
容器封装
格式
FLV
一种新的视频格式,全称为FlashVideo。由于它形成的文件极小、加载速度极快
MP4
编码采用的容器,基于 QuickTime MOV 开发,具有许多先进特性;实际上是对Apple公司开发的MOV格式(也称Quicktime格式)的一种改进
MOV
QuickTime 的容器,恐怕也是现今最强大的容器,甚至支持虚拟现实技术,Java等,它的变种 MP4,3GP都没有这么厉害;广泛应用于Mac OS操作系统,在Windows操作系统上也可兼容,但是远比不上AVI格式流行
AVI
最常见的音频视频容器,音频视频交错(Audio Video Interleaved)允许视频和音频交错在一起同步播放.
3GP
3GPP视频采用的格式, 主要用于流媒体传送;3GP其实是MP4格式的一种简化版本,是手机视频格式的绝对主流.
RMVB
MKV
ASF
WMV
封装流程
视频 Video 文件,从结构上讲,都是这样一种组成方式 由图像和音频构成最基本的内容元素; 图像经过视频编码压缩格式处理(通常是 H.264); 音频经过音频编码压缩格式处理(通常是 AAC); 注明相应的元信息(Metadata);
结构图
什么叫直播
什么叫直播?
就是将视频内容的最小颗粒 ( I / P / B 帧),基于时间序列,以光速进行传送的一种技术 直播就是将每一帧数据 ( Video / Audio / Data Frame ),打上时间戳 ( Timestamp ) 后进行流式传输的过程。发送端源源不断的采集音视频数据,经过编码、封包、推流,再经过中继分发网络进行扩散传播,播放端再源源不断地下载数据并按时序进行解码播放。如此就实现了 “边生产、边传输、边消费” 的直播过程
名词解释
编码
所谓视频编码方式就是指通过特定的压缩技术,将某个视频格式的文件转换成另一种视频格式文件的方式。视频流传输中最为重要的编解码标准有国际电联的H.261、H.263、H.264,运动静止图像专家组的M-JPEG和国际标准化组织运动图像专家组的MPEG系列标准
I frame
常被成为关键帧,k-frame,帧内编码帧 又称intra picture,I 帧通常是每个 GOP(MPEG 所使用的一种视频压缩技术)的第一个帧,经过适度地压缩,做为随机访问的参考点,可以当成图象。I帧可以看成是一个图像经过压缩后的产物。
P frame
前向预测编码帧 又称predictive-frame,通过充分将低于图像序列中前面已编码帧的时间冗余信息来压缩传输数据量的编码图像,也叫预测帧
B frame
双向预测内插编码帧 又称bi-directional interpolated prediction frame,既考虑与源图像序列前面已编码帧,也顾及源图像序列后面已编码帧之间的时间冗余信息来压缩传输数据量的编码图像,也叫双向预测帧
PTS
Presentation Time Stamp。PTS主要用于度量解码后的视频帧什么时候被显示出来
DTS
Decode Time Stamp。DTS主要是标识读入内存中的bit流在什么时候开始送入解码器中进行解码。
这里可以添加一个IPB的图片展示,类似于如何预测如何显示的
什么叫点播
把视频内容已经存放在服务器上,可以进行回放,类似于播放录像,但是录像是远程服务器的,而不是本地视频文件
流媒体
概念
指采用流式传输的方式在Internet / Intranet播放的媒体格式.流媒体的数据流随时传送随 时播放,只是在开始时有些延迟边下载边播入的流式传输方式不仅使启动延时大幅度地缩短,而且对系统缓存容量的需求也大大降低,极大地减少用户用在等待的时间
流媒体协议
RTSP+RTP
Real-time Transport Protocol)是用于Internet上针对多媒体数据流的一种传输层协议。RTP协议详细说明了在互联网上传递音频和视频的标准数据包格式,现在一般直播用的比较少
RTMP(Real Time Messaging Protocol)
实时消息传送协议是Adobe Systems公司为Flash播放器和服务器之间音频、视频和数据传输 开发的开放协议,现在直播当中用的比较多,是一个比较流行的流媒体协议
HLS(HTTP Live Streaming)
实现的基于HTTP的流媒体传输协议,可实现流媒体的直播和点播,主要应用在iOS系统,为iOS设备(如iPhone、iPad)提供音视频直播和点播方案。HLS点播,基本上就是常见的分段HTTP点播,不同在于,它的分段非常小。
相对于常见的流媒体直播协议,例如RTMP协议、RTSP协议,HLS直播最大的不同在于,直播客户端获取到的,并不是一个完整的数据流。HLS协议在服务器端将直播数据流存储为连续的、很短时长的媒体文件(MPEG-TS格式)TS片段(flv和aac合成的),而客户端则不断的下载并播放这些小文件,因为服务器端总是会将最新的直播数据生成新的小文件,这样客户端只要不停的按顺序播放从服务器获取到的文件,就实现了直播。由此可见,基本上可以认为,HLS是以点播的技术方式来实现直播。由于数据通过HTTP协议传输,所以完全不用考虑防火墙或者代理的问题,而且分段文件的时长很短,客户端可以很快的选择和切换码率,以适应不同带宽条件下的播放。不过HLS的这种技术特点,决定了它的延迟一般总是会高于普通的流媒体直播协议
MMS (Microsoft Media Server Protocol)
微软媒体服务器协议,用来访问并流式接收 Windows Media 服务器中 .asf 文件的一种协议。MMS 协议用于访问 Windows Media 发布点上的单播内容
结构图
码流
数据传输时单位时间传送的数据位数,可以理解其为取样率,单位时间内取样率越大,精度就越高,处理出来的文件就越接近原始文件,但是文件体积与取样率是成正比的如何用最低的码率达到最少的失真,一般我们用的单位是kbps即千位每秒
帧率
帧/秒(frames per second)的缩写,也称为帧速率,测量用于保存、显示动态视频的信息数量。每一帧都是静止的图象,快速连续地显示帧便形成了运动的假象。每秒钟帧数 (fps) 愈多,所显示的动作就会愈流畅,可理解为1秒钟时间里刷新的图片的帧数,也可以理解为图形处理器每秒钟能够刷新几次,也就是指每秒钟能够播放(或者录制)多少格画面。
音频采样率
音频采样率是指录音设备在一秒钟内对声音信号的采样次数,采样频率越高声音的还原就越真实越自然。在当今的主流采集卡上,采样频率一般共分为22.05KHz、44.1KHz、48KHz三个等级,22.05KHz只能达到FM广播的声音品质,44.1KHz则是理论上的CD音质界限,48KHz则更加精确一些
直播系统
礼物
聊天
弹幕(注意高并发量)
弹幕开发
1. 集成
1.1. repositories { jcenter() } dependencies { compile 'com.github.ctiao:DanmakuFlameMaster:0.4.9' }
2. 布局文件定义
2.1.
3. 初始化
3.1. private BaseDanmakuParser mParser;//解析器对象 private IDanmakuView mDanmakuView; //实例化 mDanmakuView = (IDanmakuView) findViewById(R.id.sv_danmaku); private DanmakuContext mContext; mContext = DanmakuContext.create(); // 设置弹幕的最大显示行数 HashMap<Integer, Integer> maxLinesPair = new HashMap<Integer, Integer>(); maxLinesPair.put(BaseDanmaku.TYPE_SCROLL_RL, 3); // 滚动弹幕最大显示3行 // 设置是否禁止重叠 HashMap<Integer, Boolean> overlappingEnablePair = new HashMap<Integer, Boolean>(); overlappingEnablePair.put(BaseDanmaku.TYPE_SCROLL_LR, true); overlappingEnablePair.put(BaseDanmaku.TYPE_FIX_BOTTOM, true); mContext.setDanmakuStyle(IDisplayer.DANMAKU_STYLE_STROKEN, 3) //设置描边样式 .setDuplicateMergingEnabled(false) .setScrollSpeedFactor(1.2f) //是否启用合并重复弹幕 .setScaleTextSize(1.2f) //设置弹幕滚动速度系数,只对滚动弹幕有效 .setCacheStuffer(new SpannedCacheStuffer(), mCacheStufferAdapter) // 图文混排使用SpannedCacheStuffer 设置缓存绘制填充器,默认使用{@link SimpleTextCacheStuffer}只支持纯文字显示, 如果需要图文混排请设置{@link SpannedCacheStuffer}如果需要定制其他样式请扩展{@link SimpleTextCacheStuffer}|{@link SpannedCacheStuffer} .setMaximumLines(maxLinesPair) //设置最大显示行数 .preventOverlapping(overlappingEnablePair); //设置防弹幕重叠,null为允许重叠 if (mDanmakuView != null) { mParser = createParser(this.getResources().openRawResource(R.raw.comments)); //创建解析器对象,从raw资源目录下解析comments.xml文本 mDanmakuView.setCallback(new master.flame.danmaku.controller.DrawHandler.Callback() { @Override public void updateTimer(DanmakuTimer timer) { } @Override public void drawingFinished() { } @Override public void danmakuShown(BaseDanmaku danmaku) { } @Override public void prepared() { mDanmakuView.start(); } }); mDanmakuView.prepare(mParser, mContext); mDanmakuView.showFPS(false); //是否显示FPS mDanmakuView.enableDanmakuDrawingCache(true);
3.2.
3.3.
3.4.
3.5.
4. 创建解析器对象
4.1. private BaseDanmakuParser createParser(InputStream stream) { if (stream == null) { return new BaseDanmakuParser() { @Override protected Danmakus parse() { return new Danmakus(); } }; } ILoader loader = DanmakuLoaderFactory.create(DanmakuLoaderFactory.TAG_BILI); try { loader.load(stream); } catch (IllegalDataException e) { e.printStackTrace(); } BaseDanmakuParser parser = new BiliDanmukuParser(); IDataSource<?> dataSource = loader.getDataSource(); parser.load(dataSource); return parser; } 注: DanmakuLoaderFactory.create(DanmakuLoaderFactory.TAG_BILI) //xml解析 DanmakuLoaderFactory.create(DanmakuLoaderFactory.TAG_ACFUN) //json文件格式解析
5. 自定义弹幕背景和边距
5.1. private static class BackgroundCacheStuffer extends SpannedCacheStuffer { // 通过扩展SimpleTextCacheStuffer或SpannedCacheStuffer个性化你的弹幕样式 final Paint paint = new Paint(); @Override public void measure(BaseDanmaku danmaku, TextPaint paint) { danmaku.padding = 10; // 在背景绘制模式下增加padding super.measure(danmaku, paint); } @Override public void drawBackground(BaseDanmaku danmaku, Canvas canvas, float left, float top) { paint.setColor(0x8125309b); //弹幕背景颜色 canvas.drawRect(left + 2, top + 2, left + danmaku.paintWidth - 2, top + danmaku.paintHeight - 2, paint); } @Override public void drawStroke(BaseDanmaku danmaku, String lineText, Canvas canvas, float left, float top, Paint paint) { // 禁用描边绘制 } }
6. 添加文本弹幕
6.1. private void addDanmaku(boolean islive) { BaseDanmaku danmaku = mContext.mDanmakuFactory.createDanmaku(BaseDanmaku.TYPE_SCROLL_RL); if (danmaku == null || mDanmakuView == null) { return; } danmaku.text = "这是一条弹幕" + System.nanoTime(); danmaku.padding = 5; danmaku.priority = 0; //0 表示可能会被各种过滤器过滤并隐藏显示 //1 表示一定会显示, 一般用于本机发送的弹幕 danmaku.isLive = islive; //是否是直播弹幕 danmaku.time = mDanmakuView.getCurrentTime() + 1200; //显示时间 danmaku.textSize = 25f * (mParser.getDisplayer().getDensity() - 0.6f); danmaku.textColor = Color.RED; danmaku.textShadowColor = Color.WHITE; //阴影/描边颜色 danmaku.borderColor = Color.GREEN; //边框颜色,0表示无边框 mDanmakuView.addDanmaku(danmaku); }
7. 添加图文混排弹幕
7.1. private void addDanmaKuShowTextAndImage(boolean islive) { BaseDanmaku danmaku = mContext.mDanmakuFactory.createDanmaku(BaseDanmaku.TYPE_SCROLL_RL); Drawable drawable = getResources().getDrawable(R.drawable.ic_launcher); drawable.setBounds(0, 0, 100, 100); SpannableStringBuilder spannable = createSpannable(drawable); danmaku.text = spannable; danmaku.padding = 5; danmaku.priority = 1; // 一定会显示, 一般用于本机发送的弹幕 danmaku.isLive = islive; danmaku.time = mDanmakuView.getCurrentTime() + 1200; danmaku.textSize = 25f * (mParser.getDisplayer().getDensity() - 0.6f); danmaku.textColor = Color.RED; danmaku.textShadowColor = 0; // 重要:如果有图文混排,最好不要设置描边(设textShadowColor=0),否则会进行两次复杂的绘制导致运行效率降低 danmaku.underlineColor = Color.GREEN; mDanmakuView.addDanmaku(danmaku); } /** * 创建图文混排模式 * @param drawable * @return */ private SpannableStringBuilder createSpannable(Drawable drawable) { String text = "bitmap"; SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder(text); ImageSpan span = new ImageSpan(drawable);//ImageSpan.ALIGN_BOTTOM); spannableStringBuilder.setSpan(span, 0, text.length(), Spannable.SPAN_INCLUSIVE_EXCLUSIVE); spannableStringBuilder.append("图文混排"); spannableStringBuilder.setSpan(new BackgroundColorSpan(Color.parseColor("#8A2233B1")), 0, spannableStringBuilder.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE); return spannableStringBuilder; }
8. 弹幕的隐藏/显示,暂停/继续
8.1. //显示弹幕 mDanmakuView.show();
8.2. //隐藏弹幕 mDanmakuView.hide();
8.3. //暂停 if (mDanmakuView != null && mDanmakuView.isPrepared()) { mDanmakuView.pause(); }
8.4. //继续 if (mDanmakuView != null && mDanmakuView.isPrepared() && mDanmakuView.isPaused()) { mDanmakuView.resume(); }
9. 弹幕的定时发送
9.1. Boolean b = (Boolean) mBtnSendDanmakus.getTag(); timer.cancel(); if (b == null || !b) { mBtnSendDanmakus.setText(R.string.cancel_sending_danmakus); timer = new Timer(); timer.schedule(new AsyncAddTask(), 0, 1000); mBtnSendDanmakus.setTag(true); } else { mBtnSendDanmakus.setText(R.string.send_danmakus); mBtnSendDanmakus.setTag(false); } Timer timer = new Timer(); class AsyncAddTask extends TimerTask { @Override public void run() { for (int i = 0; i < 20; i++) { addDanmaku(true); SystemClock.sleep(20); } } }
10. 创建图文混排的填充适配器
10.1. private BaseCacheStuffer.Proxy mCacheStufferAdapter = new BaseCacheStuffer.Proxy() { private Drawable mDrawable; /** * 在弹幕显示前使用新的text,使用新的text * @param danmaku * @param fromWorkerThread 是否在工作(非UI)线程,在true的情况下可以做一些耗时操作(例如更新Span的drawblae或者其他IO操作) * @return 如果不需重置,直接返回danmaku.text */ @Override public void prepareDrawing(final BaseDanmaku danmaku, boolean fromWorkerThread) { if (danmaku.text instanceof Spanned) { // 根据你的条件检查是否需要需要更新弹幕 // FIXME 这里只是简单启个线程来加载远程url图片,请使用你自己的异步线程池,最好加上你的缓存池 new Thread() { @Override public void run() { String url = "http://www.bilibili.com/favicon.ico"; InputStream inputStream = null; Drawable drawable = mDrawable; if (drawable == null) { try { URLConnection urlConnection = new URL(url).openConnection(); inputStream = urlConnection.getInputStream(); drawable = BitmapDrawable.createFromStream(inputStream, "bitmap"); mDrawable = drawable; } catch (MalformedURLException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { IOUtils.closeQuietly(inputStream); } } if (drawable != null) { drawable.setBounds(0, 0, 100, 100); SpannableStringBuilder spannable = createSpannable(drawable); danmaku.text = spannable; if (mDanmakuView != null) { mDanmakuView.invalidateDanmaku(danmaku, false); } return; } } }.start(); } } @Override public void releaseResource(BaseDanmaku danmaku) { // TODO 重要:清理含有ImageSpan的text中的一些占用内存的资源 例如drawable } };
11. 退出时释放资源
11.1. @Override protected void onDestroy() { super.onDestroy(); if (mDanmakuView != null) { // dont forget release! mDanmakuView.release(); mDanmakuView = null; } }
12.
12.1.
12.2.
12.3.
12.4.
12.5.
13.
13.1.
13.2.
13.3.
13.4.
13.5.
弹幕开源库:https://github.com/Bilibili/DanmakuFlameMaster
即时通讯
友盟即时通信
支付
任务
安全
内容审核
支付系统安全
国家政策
移动直播
热门直播APP
直播模型
技术流程
采集
视频采集
概要
图像采集的图片结果组合成一组连续播放的动画,即构成视频中可肉眼观看的内容。图像的采集过程主要由摄像头等设备拍摄成 YUV 编码的原始数据,然后经过编码压缩成 H.264 等格式的数据分发出去。常见的视频封装格式有:MP4、3GP、AVI、MKV、WMV、MPG、VOB、FLV、SWF、MOV、RMVB 和 WebM 等
技术要点
图像格式:通常采用 YUV 格式存储原始数据信息,其中包含用 8 位表示的黑白图像灰度值,以及可由 RGB 三种色彩组合成的彩色图像。
传输通道:正常情况下视频的拍摄只需 1 路通道,随着 VR 和 AR 技术的日渐成熟,为了拍摄一个完整的 360° 视频,可能需要通过不同角度拍摄,然后经过多通道传输后合成。
分辨率:随着设备屏幕尺寸的日益增多,视频采集过程中原始视频分辨率起着越来越重要的作用,后续处理环节中使用的所有视频分辨率的定义都以原始视频分辨率为基础。视频采集卡能支持的最大点阵反映了其分辨率的性能。
采样频率:采样频率反映了采集卡处理图像的速度和能力。在进行高度图像采集时,需要注意采集卡的采样频率是否满足要求。采样率越高,图像质量越高,同时保存这些图像信息的数据量也越大
采集的方式
摄像头采集
目前摄像头采集是社交直播中最常见的采集方式,比如主播使用手机的前置和后置摄像头拍摄。在现场直播场景中,也有专业的摄影、摄像设备用来采集
核心代码
Camera
主要管理相机设备
1 Obtain an instance of Camera from open(int). 获得相机的实例
2 Get existing (default) settings with getParameters(). 获得设置属性的对象
3 If necessary, modify the returned Camera.Parameters object and call setParameters(Camera.Parameters). 设置属性
4 If desired, call setDisplayOrientation(int). 设置角度
5 Important: Pass a fully initialized SurfaceHolder to setPreviewDisplay(SurfaceHolder). Without a surface, the camera will be unable to start the preview. 设置预览区
6 Important: Call startPreview() to start updating the preview surface. Preview must be started before you can take a picture.打开预览区
7 When you want, call takePicture(Camera.ShutterCallback, Camera.PictureCallback, Camera.PictureCallback, Camera.PictureCallback) to capture a photo. Wait for the callbacks to provide the actual image data. 拍照,等待回调方法返回图片数据
8 After taking a picture, preview display will have stopped. To take more photos, call startPreview() again first.拍照完毕后停下预览,如果需要继续拍照,可以重新调用打开预览的方法。
9 Call stopPreview() to stop updating the preview surface.
10 Important: Call release() to release the camera for use by other applications. Applications should release the camera immediately in onPause() (and re-open() it inonResume()). 释放相机资源
To quickly switch to video recording mode, use these steps: 录制视频
11 Obtain and initialize a Camera and start preview as described above. 首先初始化相机,打开预览区
12 Call unlock() to allow the media process to access the camera. 调用unlock方法将media 和Camera两个进程串起来
13 Pass the camera to setCamera(Camera). See MediaRecorder information about video recording. 把相机设置到MeidaRecorder 中
14 When finished recording, call reconnect() to re-acquire and re-lock the camera. 录制结束后重新设置相机
15 If desired, restart preview and take more photos or videos. 如果想要继续拍照或者录制视频,需要重新打开预览区
16 Call stopPreview() and release() as described above. 像拍照步骤一样,最后需要调用停止预览、释放资源。
步骤
1. 通过Camera.open()来获取Camera实例。
2. 创建Preview类,需要继承SurfaceView类并实现SurfaceHolder.Callback接口。
3. 为相机设置Preview
4. 设置预览回调接口
5. 释放Camera。
重要API
open():通过open方法获取Camera实例。
setPreviewDisplay(SurfaceHolder):设置预览拍照
startPreview():开始预览
stopPreview():停止预览
release():释放Camera实例
Parameters
setPictureFormat() 方法用于设置相机照片的格式,其参数是一个字符型参数,位于PixelFormat类中,如:PixelFormat.JPEG。
setSceneMode() 方法用于设置相机场景类型,其参是是一个字符型参数,位于Parameters类中,以SCENE_MODE_开头。
setZoom() 方法用于设置相机焦距,其参数是一个整型的参数,该参数的范围是0到Camera.getParameters().getMaxZoom()。
setPictureSize() 方法用于设置相机照片的大小,参数为整型。
setWhiteBalance() 方法用于设置相机照片白平衡,其参数是一个字符型,位于Parameters类中,以WHITE_BALANCE开头。
setJpegQuality() 方法用于设置相机照片的质量,其参数是一个整型参数,取值范围为1到100。
setFlashMode() 方法用于设置闪光灯的类型,其参数是一个字符型参数,位于Parameters类中,以FLASH_MODE_开头。
setColorEffect() 方法用于设置照片颜色特效的类型,其参数是一个字符型参数,位于Parameters类中,以EFFECT_开头。
Mainfest
权限
1.声明使用照相机的权限
注:如果你是调用系统Camera程序的话,就不必声明
2.Camera特性,你必须在AndroidMainfest.xml中声明照相机特性
如果你的程序可能需要使用照相机,但并不是一定的,那么可以设置android:required属性
3.存储权限,如果你的程序想在扩展存储设备上(如sd卡)存储你的照片或者拍摄的视频,必须声明如下权限:
4.音频录制权限, 为了录制音频或者视频,必须在AndroidMainfest.xml文件中设置如下权限:
屏幕方向
//禁止横屏 android:configChanges="orientation|keyboardHidden" android:screenOrientation="portrait"
第三方
其他
https://developer.android.com/guide/topics/media/camera.html#intents
基本操作
Camera摄像头的基本操作
Mainfest
权限
1.声明使用照相机的权限
注:如果你是调用系统Camera程序的话,就不必声明
2.Camera特性,你必须在AndroidMainfest.xml中声明照相机特性
如果你的程序可能需要使用照相机,但并不是一定的,那么可以设置android:required属性
3.存储权限,如果你的程序想在扩展存储设备上(如sd卡)存储你的照片或者拍摄的视频,必须声明如下权限:
4.音频录制权限, 为了录制音频或者视频,必须在AndroidMainfest.xml文件中设置如下权限:
屏幕方向
//禁止横屏 android:configChanges="orientation|keyboardHidden" android:screenOrientation="portrait"
初始化相机
打开摄像头,使用open方法
设置摄像头的预览数据界面
获取到Camera.Parameters参数信息
在把添加好的参数信息设置回去,调用startPreview开始预览效果了
销毁相机
第一步:将摄像头的预览清空
第二步:停止预览效果
第三步:释放摄像头,因为系统默认只能同时开启一个摄像头不管是前置摄像头还是后置摄像头,所以不用的时候一定要释放
第四步:置空摄像头对象
Camera摄像头方向和数据尺寸
摄像头方向有两种设置方法
Camera类本身的setDisplayOrientation方法(Camera的预览方向设置了,默认是:横屏方向,旋转度是0,如果想竖着拍摄的话,需要逆时针旋转90度即可)
Camera.Parameters类的setRotation方法
设置尺寸大小也有两个方法
setPreviewSize
setPictureSize
注意:关于尺寸问题,其实是有指定限制的,同样可以通过一个方法来获取Camera所支持的尺寸大小:getSupportedPreviewSizes
Camera摄像头的前置和后置区分
Camera摄像头的数据格式
Camera摄像头的对焦拍照
Camera摄像头的数据采集以及处理
SurfaceView
作用用于控制预览界面 可以直接从内存或者DMA等硬件接口取得图像数据,是个非常重要的绘图容器。 它的特性是:可以在主线程之外的线程中向屏幕绘图上。这样可以避免画图任务繁重的时候造成主线程阻塞,从而提高了程序的反应速度。在游戏开发中多用到SurfaceView,游戏中的背景、人物、动画等等尽量在画布canvas中画出
SurfaceHolder.Callback接口:用于处理预览的事件,需实现如下三个方法:
surfaceCreated(SurfaceHolderholder):预览界面创建时调用,每次界面改变后都会重新创建,需要获取相机资源并设置SurfaceHolder。
surfaceChanged(SurfaceHolderholder, int format, int width, int height):预览界面发生变化时调用,每次界面发生变化之后需要重新启动预览。
surfaceDestroyed(SurfaceHolderholder):预览销毁时调用,停止预览,释放相应资源。
屏幕录制
屏幕录制采集的方式在游戏直播场景中非常常见
音频采集
概要
音频数据既能与图像结合组合成视频数据,也能以纯音频的方式采集播放,后者在很多成熟的应用场景如在线电台和语音电台等起着非常重要的作用。音频的采集过程主要通过设备将环境中的模拟信号采集成 PCM 编码的原始数据,然后编码压缩成 MP3 等格式的数据分发出去。常见的音频压缩格式有:MP3,AAC,OGG,WMA,Opus,FLAC,APE,m4a 和 AMR 等。
核心代码
int bufferSize = AudioRecord.getMinBufferSize(frequency, channelConfiguration, audioEncoding); AudioRecord audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC,frequency, channelConfiguration, audioEncoding, bufferSize); short[] buffer = new short[bufferSize]; audioRecord.startRecording(); isRecording = true ; while (isRecording) { int bufferReadResult = audioRecord.read(buffer, 0, bufferSize); for (int i = 0; i < bufferReadResult; i++) publisAudio(buffer) 这里将buffer做处理成AAC,然后发送
预处理
主要是针对图像处理 例如:美颜,滤镜
难点:图形处理主要用到gpu加速,需要考虑能耗效果的平衡
视频处理<难点>
美颜
水印
滤镜
自定义滤镜
音频处理
混音
降噪
特效
编码
为什么要编码?
原始视频数据存储空间大,一个 1080P 的 7s 视频需要 817 MB
原始视频数据传输占用带宽大,10Mbps 的带宽传输上述 7s 视频需要 11 分钟
而经过 H.264 编码压缩之后,视频大小只有 708 k ,10 Mbps 的带宽仅仅需要 500 ms ,可以满足实时传输的需求,所以从视频采集传感器采集来的原始视频势必要经过视频编码
原理
核心思想去冗余信息
空间冗余:图像相邻像素之间有较强的相关性
视频本质上讲是一系列图片连续快速的播放,最简单的压缩方式就是对每一帧图片进行压缩,这种编码方式只有帧内编码,利用空间上的取样预测来编码。形象的比喻就是把每帧都作为一张图片,采用 JPEG 的编码格式对图片进行压缩,这种编码只考虑了一张图片内的冗余信息压缩
时间冗余:视频序列的相邻图像之间内容相似
编码冗余:不同像素值出现的概率不同
视觉冗余:人的视觉系统对某些细节不敏感
编码方式
硬编码
使用非CPU进行编码,如显卡GPU、专用的DSP、FPGA、ASIC芯片等
软编码
大部分硬件都支持,h.256,aac等,android处理器繁杂,兼容性问题
使用CPU进行编码
对比
软编码:实现直接、简单,参数调整方便,升级易,但CPU负载重,性能较硬编码低,低码率下质量通常比硬编码要好一点。 硬编码:性能高,低码率下通常质量低于硬编码器,但部分产品在GPU硬件平台移植了优秀的软编码算法(如X264)的,质量基本等同于软编码。
难点:平衡分辨率,码率,使得体积画质最优,参数组合必须设置合理
传输
主要是传输协议的选择,CDN技术,云服务器
推流
通过Camera的预览回调获得图像数据
FFmepgFrameRecord的record(Frame frame)方法
通过AudioRecord 得到音频数据,然后编码发送到
recorder.recordSamples(audioData);
解码
硬解码
软解码
渲染
涉及分辨率适配,服务器端客服端配合优化视频音频同步,稳定等功能,音频优化等技术
开源技术
服务器
测试地址:http://ossrs.net/players/srs_player.html?stream=livestream&port=19350
开源
simple rtmp server(SRS)
red5
crtmpserver
erlyvideo
haXevideo
FluorineFX
nginx-rtmp
Cumulus
Server
Mistserver
javacv
概要
JavaCV 提供了在计算机视觉领域的封装库,包括:OpenCV、ARToolKitPlus、libdc1394 2.x 、PGR FlyCapture和FFmpeg。此外,该工具可以很容易地使用Java平台的功能。 JavaCV 还带有硬件加速的全屏幕图像显示(CanvasFrame),易于在多个内核中执行并行代码(并行),用户友好的几何和色彩的相机和投影仪校准(GeometricCalibrator,ProCamGeometricCalibrator,ProCamColorCalibrator ),检测和特征点(ObjectFinder),一类是实现投影,摄像系统(直接图像对齐设置匹配主要GNImageAligner,ProjectiveTransformer,ProjectiveGainBiasTransformer,ProCamTransformer 和ReflectanceInitializer),以及在 JavaCV 类杂项功能
使用
导包
android { //防止程序打包时重复导包出错 packagingOptions { exclude 'META-INF/maven/org.bytedeco.javacpp-presets/opencv/pom.properties' exclude 'META-INF/maven/org.bytedeco.javacpp-presets/opencv/pom.xml' exclude 'META-INF/maven/org.bytedeco.javacpp-presets/ffmpeg/pom.properties' exclude 'META-INF/maven/org.bytedeco.javacpp-presets/ffmpeg/pom.xml' } }
//防止在各平台下开发导致找不到相应的jar configurations { all*.exclude group: 'org.bytedeco', module: 'javacpp-presets' }
dependencies { compile 'org.bytedeco.javacpp-presets:opencv:3.1.0-1.2:android-arm' compile 'org.bytedeco.javacpp-presets:opencv:3.1.0-1.2:android-x86' compile 'org.bytedeco.javacpp-presets:ffmpeg:3.0.2-1.2:android-arm' compile 'org.bytedeco.javacpp-presets:ffmpeg:3.0.2-1.2:android-x86' compile 'org.bytedeco:javacv:1.2' }
代码
FFmpegFrameRecorder 推流核心类
recorder = new FFmpegFrameRecorder(rtmpUrl, imageWidth, imageHeight, 1); //设置视频编码 28 指代h.264 recorder.setVideoCodec(28); //设置视频的封装容器 recorder.setFormat("flv"); //设置采样频率 recorder.setSampleRate(sampleAudioRateInHz); // 设置帧率,即每秒的图像数 recorder.setFrameRate(frameRate);
工具类
private void YUV420spRotate90(byte[] dst, byte[] src, int srcWidth, int srcHeight) { int nWidth = 0, nHeight = 0; int wh = 0; int uvHeight = 0; if (srcWidth != nWidth || srcHeight != nHeight) { nWidth = srcWidth; nHeight = srcHeight; wh = srcWidth * srcHeight; uvHeight = srcHeight >> 1; } //旋转Y int k = 0; for (int i = 0; i < srcWidth; i++) { int nPos = 0; for (int j = 0; j < srcHeight; j++) { dst[k] = src[nPos + i]; k++; nPos += srcWidth; } } for (int i = 0; i < srcWidth; i += 2) { int nPos = wh; for (int j = 0; j < uvHeight; j++) { dst[k] = src[nPos + i]; dst[k + 1] = src[nPos + i + 1]; k += 2; nPos += srcWidth; } } return; } //逆时针旋转90度: private void YUV420spRotateNegative90(byte[] dst, byte[] src, int srcWidth, int height) { int nWidth = 0, nHeight = 0; int wh = 0; int uvHeight = 0; if (srcWidth != nWidth || height != nHeight) { nWidth = srcWidth; nHeight = height; wh = srcWidth * height; uvHeight = height >> 1; } //旋转Y int k = 0; for (int i = 0; i < srcWidth; i++) { int nPos = srcWidth - 1; for (int j = 0; j < height; j++) { dst[k] = src[nPos - i]; k++; nPos += srcWidth; } } for (int i = 0; i < srcWidth; i += 2) { int nPos = wh + srcWidth - 1; for (int j = 0; j < uvHeight; j++) { dst[k] = src[nPos - i - 1]; dst[k + 1] = src[nPos - i]; k += 2; nPos += srcWidth; } } return; }
其他
Github地址:https://github.com/bytedeco/javacv
https://github.com/bytedeco/javacpp-presets
API:http://bytedeco.org/javacv/apidocs/org/bytedeco/javacv/package-summary.html
渲染播放
vlc 有多个平台的
ijkplayer
第三方SDK
腾讯直播:https://www.qcloud.com/doc/product/267
暴风直播
网易直播:http://vcloud.163.com/live.html
七牛直播:http://developer.qiniu.com/article/pili/product-detail.html
百度直播:http://www.rongcloud.cn/docs/
ucloud
https://docs.ucloud.cn/video/ulive/index.html
AnyRTC
https://github.com/AnyRTC/AnyRTC-RTMP(免费)
测试拉流地址
rtmp://live.hkstv.hk.lxdns.com/live/hks