热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

在线直播网站源码开发,视频的采集如何实现?

支撑在线直播网站源码发展的关键就是直播技术的实现,在整个流媒体传输中视频的采集时开始,一般利用到的是设备端的摄像头,我们在实现视频采集时&

支撑在线直播网站源码发展的关键就是直播技术的实现,在整个流媒体传输中视频的采集时开始,一般利用到的是设备端的摄像头,我们在实现视频采集时,不仅需要获取摄像头的调用权限,还要获取到视频录制的数据。

我们先来了解一下在在线直播网站源码开发中需要掌握的音视频基础知识点:


  • ffmpeg 强大的音视频处理库,(cpu软编等)
  • mediaCodec 安卓sdk自带的编解码器,(硬编)
  • opengles 使用gpu进行图像处理
  • h264,h265 图像编码压缩算法
  • yuv420p ,nv21 ,yuv_420_888,I420 需要了解的视频编码格式
  • yuv和rgb的相互转化
  • pcm,acc 需要了解的音频编码格式
  • camerax,mediaRecorder,audioRecorder 采集相关的api

音视频在安卓中一般就有2种表现形式,一种是播放在线或者本地视频(录制和播放),另一种就是在线直播网站源码中的直播(推流和拉流),下面我们首先从视频的采集来对在线直播网站源码做一下详细的探索。

视频的采集


  • Camerax的使用

camerax是jetpack中新加的一个相机库,设计的非常好,不用像之前camera1和camera2使用那么的繁琐,并且是和生命周期绑定,方便开发者管理生命周期。

1.添加依赖

implementation "androidx.camera:camera-camera2:$camerax_version"
implementation "androidx.camera:camera-lifecycle:$camerax_version"
implementation "androidx.camera:camera-view:1.0.0-alpha10"

2.创建预览布局

<androidx.camera.view.PreviewViewandroid:id="@+id/viewFinder"app:layout_constraintTop_toTopOf="parent"app:layout_constraintBottom_toTopOf="@+id/camera_capture_button"android:layout_width="match_parent"android:layout_height="0dp"/>

3.获取相机权限(略)
4.打开相机,获取预览

@SuppressLint("RestrictedApi")
private fun startCamera() {initMediaCodec(480,640,20)val cameraProviderFuture = ProcessCameraProvider.getInstance(this)cameraProviderFuture.addListener(Runnable {bindImage(cameraProviderFuture)}, ContextCompat.getMainExecutor(this))
}private fun bindImage(cameraProviderFuture:ListenableFuture<ProcessCameraProvider>) {val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()preview = Preview.Builder().build()val cameraSelector =CameraSelector.Builder().requireLensFacing(CameraSelector.LENS_FACING_BACK).build()val imageAnalysis = ImageAnalysis.Builder().setTargetResolution(Size(1280, 720)).setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST).build()imageAnalysis.setAnalyzer(cameraExecutor, ImageAnalysis.Analyzer { image ->Thread {val data =ImageUtils.getDataFromImage2(image,ImageUtils.COLOR_FormatI420)val out = FileOutputStream(File(Environment.getExternalStorageDirectory(),"hhh.yuv"))out.write(data)image.close();}.start()})try {cameraProvider.unbindAll()camera = cameraProvider.bindToLifecycle(this, cameraSelector, imageAnalysis, preview)preview?.setSurfaceProvider(viewFinder.createSurfaceProvider(camera?.cameraInfo))} catch (exc: Exception) {Log.e(TAG, "Use case binding failed", exc)}
}

5.获取视频录制数据
使用ImageAnalysis的setAnalyzer方法可以获取到在线直播网站源码录制的视频原始数据,默认的视频数据格式是
yuv_420_888,这种数据格式是不能直接使用ffmepeg或者mediacodec编码的,需要转成i420格式,

yuv_420_888是YCbCr的泛化格式,能够表示任何4:2:0的平面和半平面格式,每个分量用8 bits 表示。带有这种格式的图像使用3个独立的Buffer表示,每一个Buffer表示一个颜色平面(Plane),除了Buffer外,它还提供rowStride、pixelStride来描述对应的Plane。

按照官方的说法,第一个平面全是y数据,第2个平面包含所有的u数据,第3个平面包含所有的v数据,我们只需要解析出原始数据中的yuv分量在按照i420的排列和比例就可以得到我们最终可以使用的数据了。

但是实际解析过程中我发现了在线直播网站源码原始数据中有比较多的填充数据,按照网上的说法是用作对齐处理。

public class ImageUtils {private static final boolean VERBOSE = true;private static final String TAG = "ImageUtils";public static final int COLOR_FormatI420 = 1;public static final int COLOR_FormatNV21 = 2;public static boolean isImageFormatSupported(ImageProxy image) {int format = image.getFormat();switch (format) {case ImageFormat.YUV_420_888:case ImageFormat.NV21:case ImageFormat.YV12:return true;}return false;}public static byte[] getDataFromImage2(ImageProxy image, int colorFormat) {if (colorFormat != COLOR_FormatI420 && colorFormat != COLOR_FormatNV21) {throw new IllegalArgumentException("only support COLOR_FormatI420 " + "and COLOR_FormatNV21");}if (!isImageFormatSupported(image)) {throw new RuntimeException("can&#39;t convert Image to byte array, format " + image.getFormat());}Rect crop = image.getCropRect();int format = image.getFormat();// 888int width = crop.width();int height = crop.height();ImageProxy.PlaneProxy[] planes = image.getPlanes();int yRe = planes[0].getBuffer().remaining();int uvRe = planes[1].getBuffer().remaining();int vuRe = planes[2].getBuffer().remaining();int yStride = planes[0].getRowStride();int uvStride = planes[1].getRowStride();int vuStride = planes[2].getRowStride();int yLength = yRe - (yStride-width)*(height-1);int uvLength = uvRe - (uvStride-width)*(height/2-1);int vuLength = vuRe - (vuStride-width)*(height/2-1);byte[] data = new byte[width * height * 12 /8];byte[] yData = new byte[yLength];byte[] uData = new byte[uvLength/2+1];byte[] vData = new byte[vuLength/2 + 1];//uv 占 2分之1byte[] uvData = new byte[uvLength];//vu 占 2分之1byte[] vuData = new byte[vuLength];byte[] rowData = new byte[planes[0].getRowStride()];for (int i = 0; i < planes.length; i++) {ImageProxy.PlaneProxy plane = planes[i];ByteBuffer buffer = plane.getBuffer();int offset = 0;Log.e("getDataFromImage",plane.getPixelStride() + "");buffer.position(0);int col = height / plane.getPixelStride();if(plane.getPixelStride() == 1){ //原矩阵是i420for (int j = 0; j < col; j++) {if(i == 0){ //yif(j < height/plane.getPixelStride() -1){buffer.get(yData,offset,width);}else {buffer.get(yData,offset,yRe - (plane.getRowStride() * (height-1)));}offset +=width;if(j < height/plane.getPixelStride()-1){buffer.position(buffer.position()+ plane.getRowStride() - width);}}else if(i == 1){ //uvbuffer.get(uData,offset,width);offset +=width;if(j < height/plane.getPixelStride() -1){buffer.position(buffer.position()+ plane.getRowStride() - width);}}else { // vubuffer.get(vData,offset,width);offset +=width;if(j < height/plane.getPixelStride()-1){buffer.position(buffer.position()+ plane.getRowStride() - width);}}}}else { // 原矩阵是 nv12 或者 nv21for (int j = 0; j < col; j++) {if(i == 0){ //yif(j < height/plane.getPixelStride() -1){buffer.get(yData,offset,width);}else {buffer.get(yData,offset,yRe - (plane.getRowStride() * ( height/plane.getPixelStride() -1)));}offset +=width;if(j < height/plane.getPixelStride() -1){buffer.position(buffer.position()+ plane.getRowStride() - width);}}else if(i == 1){ //uvif(j < height/plane.getPixelStride() -1){buffer.get(uvData,offset,width);}else {buffer.get(uvData,offset,uvRe - (plane.getRowStride() * (height/plane.getPixelStride()-1)));}offset +=width;if(j < height/plane.getPixelStride() -1){buffer.position(buffer.position()+ plane.getRowStride() - width);}}else { // vuif(j < height/plane.getPixelStride() -1){buffer.get(vuData,offset,width);}else {buffer.get(vuData,offset,vuRe - (plane.getRowStride() * (height/plane.getPixelStride()-1)));}offset +=width;if(j < height/plane.getPixelStride()-1){buffer.position(buffer.position()+ plane.getRowStride() - width);}}}}}boolean isI420 = image.getPlanes()[1].getPixelStride() == 1;//ydata uvdata vudata//提取 u ,vif(!isI420){//for (int i = 0; i < uvData.length; i+=2) {uData[i/2] = uvData[i];}for (int i = 0; i < vuData.length; i+=2) {vData[i/2] = uvData[i];}Log.e("getDataFromImage",uData.length + "");Log.e("getDataFromImage",vData.length + "");}//组装 i420System.arraycopy(yData,0,data,0,yData.length);System.arraycopy(uData,0,data,yData.length,uData.length);System.arraycopy(vData,0,data,yData.length + uData.length ,vData.length);return data;}
}

以上就是“在线直播网站源码开发,视频的采集如何实现?”的全部内容,希望对大家有帮助,在线直播网站源码在开发过程中需要注意每一个细节,否则可能会导致差之毫米失之千里。


推荐阅读
  • 本文介绍了响应式页面的概念和实现方式,包括针对不同终端制作特定页面和制作一个页面适应不同终端的显示。分析了两种实现方式的优缺点,提出了选择方案的建议。同时,对于响应式页面的需求和背景进行了讨论,解释了为什么需要响应式页面。 ... [详细]
  • 本文介绍了MVP架构模式及其在国庆技术博客中的应用。MVP架构模式是一种演变自MVC架构的新模式,其中View和Model之间的通信通过Presenter进行。相比MVC架构,MVP架构将交互逻辑放在Presenter内部,而View直接从Model中读取数据而不是通过Controller。本文还探讨了MVP架构在国庆技术博客中的具体应用。 ... [详细]
  • Java太阳系小游戏分析和源码详解
    本文介绍了一个基于Java的太阳系小游戏的分析和源码详解。通过对面向对象的知识的学习和实践,作者实现了太阳系各行星绕太阳转的效果。文章详细介绍了游戏的设计思路和源码结构,包括工具类、常量、图片加载、面板等。通过这个小游戏的制作,读者可以巩固和应用所学的知识,如类的继承、方法的重载与重写、多态和封装等。 ... [详细]
  • 本文介绍了C#中生成随机数的三种方法,并分析了其中存在的问题。首先介绍了使用Random类生成随机数的默认方法,但在高并发情况下可能会出现重复的情况。接着通过循环生成了一系列随机数,进一步突显了这个问题。文章指出,随机数生成在任何编程语言中都是必备的功能,但Random类生成的随机数并不可靠。最后,提出了需要寻找其他可靠的随机数生成方法的建议。 ... [详细]
  • Google Play推出全新的应用内评价API,帮助开发者获取更多优质用户反馈。用户每天在Google Play上发表数百万条评论,这有助于开发者了解用户喜好和改进需求。开发者可以选择在适当的时间请求用户撰写评论,以获得全面而有用的反馈。全新应用内评价功能让用户无需返回应用详情页面即可发表评论,提升用户体验。 ... [详细]
  • 拥抱Android Design Support Library新变化(导航视图、悬浮ActionBar)
    转载请注明明桑AndroidAndroid5.0Loollipop作为Android最重要的版本之一,为我们带来了全新的界面风格和设计语言。看起来很受欢迎࿰ ... [详细]
  • 标题: ... [详细]
  • 本文介绍了南邮ctf-web的writeup,包括签到题和md5 collision。在CTF比赛和渗透测试中,可以通过查看源代码、代码注释、页面隐藏元素、超链接和HTTP响应头部来寻找flag或提示信息。利用PHP弱类型,可以发现md5('QNKCDZO')='0e830400451993494058024219903391'和md5('240610708')='0e462097431906509019562988736854'。 ... [详细]
  • 本文讨论了clone的fork与pthread_create创建线程的不同之处。进程是一个指令执行流及其执行环境,其执行环境是一个系统资源的集合。在调用系统调用fork创建一个进程时,子进程只是完全复制父进程的资源,这样得到的子进程独立于父进程,具有良好的并发性。但是二者之间的通讯需要通过专门的通讯机制,另外通过fork创建子进程系统开销很大。因此,在某些情况下,使用clone或pthread_create创建线程可能更加高效。 ... [详细]
  • 深入理解Kafka服务端请求队列中请求的处理
    本文深入分析了Kafka服务端请求队列中请求的处理过程,详细介绍了请求的封装和放入请求队列的过程,以及处理请求的线程池的创建和容量设置。通过场景分析、图示说明和源码分析,帮助读者更好地理解Kafka服务端的工作原理。 ... [详细]
  • RouterOS 5.16软路由安装图解教程
    本文介绍了如何安装RouterOS 5.16软路由系统,包括系统要求、安装步骤和登录方式。同时提供了详细的图解教程,方便读者进行操作。 ... [详细]
  • iOS超签签名服务器搭建及其优劣势
    本文介绍了搭建iOS超签签名服务器的原因和优势,包括不掉签、用户可以直接安装不需要信任、体验好等。同时也提到了超签的劣势,即一个证书只能安装100个,成本较高。文章还详细介绍了超签的实现原理,包括用户请求服务器安装mobileconfig文件、服务器调用苹果接口添加udid等步骤。最后,还提到了生成mobileconfig文件和导出AppleWorldwideDeveloperRelationsCertificationAuthority证书的方法。 ... [详细]
  • 纠正网上的错误:自定义一个类叫java.lang.System/String的方法
    本文纠正了网上关于自定义一个类叫java.lang.System/String的错误答案,并详细解释了为什么这种方法是错误的。作者指出,虽然双亲委托机制确实可以阻止自定义的System类被加载,但通过自定义一个特殊的类加载器,可以绕过双亲委托机制,达到自定义System类的目的。作者呼吁读者对网上的内容持怀疑态度,并带着问题来阅读文章。 ... [详细]
  • 嵌入式处理器的架构与内核发展历程
    本文主要介绍了嵌入式处理器的架构与内核发展历程,包括不同架构的指令集的变化,以及内核的流水线和结构。通过对ARM架构的分析,可以更好地理解嵌入式处理器的架构与内核的关系。 ... [详细]
  • C语言常量与变量的深入理解及其影响
    本文深入讲解了C语言中常量与变量的概念及其深入实质,强调了对常量和变量的理解对于学习指针等后续内容的重要性。详细介绍了常量的分类和特点,以及变量的定义和分类。同时指出了常量和变量在程序中的作用及其对内存空间的影响,类似于const关键字的只读属性。此外,还提及了常量和变量在实际应用中可能出现的问题,如段错误和野指针。 ... [详细]
author-avatar
经典调剂行570
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有