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

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


推荐阅读
  • 尽管我们尽最大努力,任何软件开发过程中都难免会出现缺陷。为了更有效地提升对支持部门的协助与支撑,本文探讨了多种策略和最佳实践,旨在通过改进沟通、增强培训和支持流程来减少这些缺陷的影响,并提高整体服务质量和客户满意度。 ... [详细]
  • 在软件开发过程中,经常需要将多个项目或模块进行集成和调试,尤其是当项目依赖于第三方开源库(如Cordova、CocoaPods)时。本文介绍了如何在Xcode中高效地进行多项目联合调试,分享了一些实用的技巧和最佳实践,帮助开发者解决常见的调试难题,提高开发效率。 ... [详细]
  • 为了确保iOS应用能够安全地访问网站数据,本文介绍了如何在Nginx服务器上轻松配置CertBot以实现SSL证书的自动化管理。通过这一过程,可以确保应用始终使用HTTPS协议,从而提升数据传输的安全性和可靠性。文章详细阐述了配置步骤和常见问题的解决方法,帮助读者快速上手并成功部署SSL证书。 ... [详细]
  • 为了在Hadoop 2.7.2中实现对Snappy压缩和解压功能的原生支持,本文详细介绍了如何重新编译Hadoop源代码,并优化其Native编译过程。通过这一优化,可以显著提升数据处理的效率和性能。此外,还探讨了编译过程中可能遇到的问题及其解决方案,为用户提供了一套完整的操作指南。 ... [详细]
  • Web开发框架概览:Java与JavaScript技术及框架综述
    Web开发涉及服务器端和客户端的协同工作。在服务器端,Java是一种优秀的编程语言,适用于构建各种功能模块,如通过Servlet实现特定服务。客户端则主要依赖HTML进行内容展示,同时借助JavaScript增强交互性和动态效果。此外,现代Web开发还广泛使用各种框架和库,如Spring Boot、React和Vue.js,以提高开发效率和应用性能。 ... [详细]
  • 通过使用CIFAR-10数据集,本文详细介绍了如何快速掌握Mixup数据增强技术,并展示了该方法在图像分类任务中的显著效果。实验结果表明,Mixup能够有效提高模型的泛化能力和分类精度,为图像识别领域的研究提供了有价值的参考。 ... [详细]
  • 从用户转型为开发者:一场思维升级的旅程 | 专访 StarRocks Committer 周威
    从用户转变为开发者,不仅是一次角色的转换,更是一场深刻的思维升级之旅。本次专访中,StarRocks Committer 周威分享了他如何在这一过程中逐步提升技术能力与思维方式,为开源社区贡献自己的力量。 ... [详细]
  • 如何将TS文件转换为M3U8直播流:HLS与M3U8格式详解
    在视频传输领域,MP4虽然常见,但在直播场景中直接使用MP4格式存在诸多问题。例如,MP4文件的头部信息(如ftyp、moov)较大,导致初始加载时间较长,影响用户体验。相比之下,HLS(HTTP Live Streaming)协议及其M3U8格式更具优势。HLS通过将视频切分成多个小片段,并生成一个M3U8播放列表文件,实现低延迟和高稳定性。本文详细介绍了如何将TS文件转换为M3U8直播流,包括技术原理和具体操作步骤,帮助读者更好地理解和应用这一技术。 ... [详细]
  • 在Android平台中,播放音频的采样率通常固定为44.1kHz,而录音的采样率则固定为8kHz。为了确保音频设备的正常工作,底层驱动必须预先设定这些固定的采样率。当上层应用提供的采样率与这些预设值不匹配时,需要通过重采样(resample)技术来调整采样率,以保证音频数据的正确处理和传输。本文将详细探讨FFMpeg在音频处理中的基础理论及重采样技术的应用。 ... [详细]
  • 在 Linux 环境下,多线程编程是实现高效并发处理的重要技术。本文通过具体的实战案例,详细分析了多线程编程的关键技术和常见问题。文章首先介绍了多线程的基本概念和创建方法,然后通过实例代码展示了如何使用 pthreads 库进行线程同步和通信。此外,还探讨了多线程程序中的性能优化技巧和调试方法,为开发者提供了宝贵的实践经验。 ... [详细]
  • 在Android 4.4系统中,通过使用 `Intent` 对象并设置动作 `ACTION_GET_CONTENT` 或 `ACTION_OPEN_DOCUMENT`,可以从相册中选择图片并获取其路径。具体实现时,需要为 `Intent` 添加相应的类别,并处理返回的 Uri 以提取图片的文件路径。此方法适用于需要从用户相册中选择图片的应用场景,能够确保兼容性和用户体验。 ... [详细]
  • 如何高效启动大数据应用之旅?
    在前一篇文章中,我探讨了大数据的定义及其与数据挖掘的区别。本文将重点介绍如何高效启动大数据应用项目,涵盖关键步骤和最佳实践,帮助读者快速踏上大数据之旅。 ... [详细]
  • 深入解析Spring Boot启动过程中Netty异步架构的工作原理与应用
    深入解析Spring Boot启动过程中Netty异步架构的工作原理与应用 ... [详细]
  • 树莓派 4 HDMI 音频输出问题求解:无声音解决方案探讨 ... [详细]
  • 在处理分享功能时,以往通常会首先考虑使用第三方SDK,如友盟等。然而,通过实际项目经验发现,利用iOS原生的UIActivityViewController不仅可以实现高效、稳定的分享功能,还能更好地控制用户体验。本文将详细介绍如何利用iOS原生技术缓存HTML内容,并结合URL分享的最佳实践,帮助开发者在实际开发中提升应用性能和用户满意度。 ... [详细]
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社区 版权所有