热门标签 | 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;}
}

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


推荐阅读
  • 本文详细解析了Python中的os和sys模块,介绍了它们的功能、常用方法及其在实际编程中的应用。 ... [详细]
  • 在Java中,this是一个引用当前对象的关键字。如何通过this获取并显示其所指向的对象的属性和方法?本文详细解释了this的用法及其背后的原理。 ... [详细]
  • 选择适合生产环境的Docker存储驱动
    本文旨在探讨如何在生产环境中选择合适的Docker存储驱动,并详细介绍不同Linux发行版下的配置方法。通过参考官方文档和兼容性矩阵,提供实用的操作指南。 ... [详细]
  • 2018年3月31日,CSDN、火星财经联合中关村区块链产业联盟等机构举办的2018区块链技术及应用峰会(BTA)核心分会场圆满举行。多位业内顶尖专家深入探讨了区块链的核心技术原理及其在实际业务中的应用。 ... [详细]
  • 在创建新的Android项目时,您可能会遇到aapt错误,提示无法打开libstdc++.so.6共享对象文件。本文将探讨该问题的原因及解决方案。 ... [详细]
  • 利用Selenium与ChromeDriver实现豆瓣网页全屏截图
    本文介绍了一种使用Selenium和ChromeDriver结合Python代码,轻松实现对豆瓣网站进行完整页面截图的方法。该方法不仅简单易行,而且解决了新版Selenium不再支持PhantomJS的问题。 ... [详细]
  • 本文探讨了如何通过预处理器开关选择不同的类实现,并解决在特定情况下遇到的链接器错误。 ... [详细]
  • TWEN-ASR 语音识别入门:运行首个程序
    本文详细介绍了如何使用TWEN-ASR ONE开发板运行第一个语音识别程序,包括开发环境搭建、代码编写、下载和调试等步骤。 ... [详细]
  • 从 .NET 转 Java 的自学之路:IO 流基础篇
    本文详细介绍了 Java 中的 IO 流,包括字节流和字符流的基本概念及其操作方式。探讨了如何处理不同类型的文件数据,并结合编码机制确保字符数据的正确读写。同时,文中还涵盖了装饰设计模式的应用,以及多种常见的 IO 操作实例。 ... [详细]
  • 本文介绍百度AI Studio这一集成开发平台,涵盖丰富的AI教程、经典数据集及云端计算资源。通过具体示例——在AI Studio上构建线性回归项目,帮助初学者快速掌握其核心功能与操作方法。 ... [详细]
  • NVIDIA Titan RTX深度评测
    NVIDIA的Titan RTX被誉为当前最强大的桌面显卡之一,其卓越的性能和高昂的价格吸引了众多专业人士和技术爱好者的关注。本文将详细介绍Titan RTX的技术规格、性能表现及应用场景。 ... [详细]
  • 本文详细介绍了Linux内核中misc设备驱动框架的实现原理及应用方法,包括misc设备的基本概念、驱动框架的初始化过程、数据结构分析以及设备的注册与注销流程。 ... [详细]
  • 本文介绍了如何在WildFly 10中配置MySQL数据源时遇到的服务依赖问题及其解决方案。 ... [详细]
  • 2017年人工智能领域的十大里程碑事件回顾
    随着2018年的临近,我们一同回顾过去一年中人工智能领域的重要进展。这一年,无论是政策层面的支持,还是技术上的突破,都显示了人工智能发展的迅猛势头。以下是精选的2017年人工智能领域最具影响力的事件。 ... [详细]
  • 本文总结了MySQL的一些实用技巧,包括查询版本、修改字段属性、添加自动增长字段、备份与恢复数据库等操作,并提供了一些常见的SQL语句示例。 ... [详细]
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社区 版权所有