Android音视频【七】H265硬编解码视频通话

2024-03-10 19:48

本文主要是介绍Android音视频【七】H265硬编解码视频通话,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

人间观察
我该如何去表达呢

前面介绍了H265的一些知识,本篇实现利用camera采集进行H265硬编码,利用WebSocket来传输H265裸流,接收到H265的码流后进行H265解码渲染到surfaceview上,从而实现简易的视频通话。

主要有:摄像头如何处理,如何拿到摄像头的yuv数据,yuv数据怎么处理,实现Android H265硬编码和硬解码,vps,sps,pps怎么处理以及如何在网络上传输。

1 .这里用哪种协议不是本文的重点,本文采用java封装好websocket协议的组件,在真实项目中音视频通话可能不用websocket协议,更多的可能是webrtc。

2.没有涉及到音频的编解码和发送传输,音频会后续出系列介绍

3.本篇也是用kotlin来实现,为什么用kotlin?因为工作中没有用到,我想自己练习下。。。

效果图

在这里插入图片描述

实现方案

在这里插入图片描述

Camera的YUV数据采集

简单说下camera,本篇拿camera摄像头来进行数据的采集,当然你也可以用camera2来实现,camera2是提供了更丰富的API(但是我想说真难用,拍个照,获取原始yuv数据写几百行代码),然后Google在jetpack中提供了camerax,camerax的api还是比较简单的。各种camera 花两天研究下就会了,现学现用都没啥,我们主要是介绍编解码和yuv数据的处理,这些基本都是不变的,不像上层camera的api一样。

在camera中主要就是打开camera设置预览画面大小和回调的数据格式(默认是NV21格式的yuv数据,NV21格式的数据基本上所有的摄像头都支持,所以Android默认采用这个)。设置预览回调的数据大小,一般为了方便处理设置的就是一帧yuv数据的大小,也就是y+u+v的数据大小=width * height + width * height的1/4 +width * height的1/4=width * height * 3 / 2。

局部代码如下:

    fun startPreview() {// 临时用后置摄像头,重点是编解码和数据的传输camera = Camera.open(Camera.CameraInfo.CAMERA_FACING_BACK)val parameters: Camera.Parameters = camera.parameters// 摄像头默认NV21Log.e(TAG, "previewFormat:" + parameters.previewFormat)setPreviewSize(parameters)camera.setParameters(parameters)camera.setPreviewDisplay(holder)// 由于硬件安装是横着的,如果是后置摄像头&&正常竖屏的情况下需要旋转90度// 只是预览旋转了,数据没有旋转camera.setDisplayOrientation(90)// 让摄像头回调一帧的数据大小buffer = ByteArray(width * height * 3 / 2)// onPreviewFrame回调的数据大小就是buffer.lengthcamera.addCallbackBuffer(buffer)camera.setPreviewCallbackWithBuffer(this)camera.startPreview()}

摄像头的预览旋转问题,如果是后置摄像头&&正常竖屏拿着,这时候你会发现预览出来的画面是横着的,所以需要旋转90度。当然前后摄像头和人为的旋转手机本身也需要做对应的旋转才行。

开启预览和设置yuv数据回调后,就会在onPreviewFrame回调中回调出来。

 override fun onPreviewFrame(data: ByteArray?, camera: Camera?) {// 摄像头的原始数据yuvcamera!!.addCallbackBuffer(data)
}

YUV数据处理

关于YUV的数据的知识可以参考前一篇。

1.因为摄像头出来的是NV21的数据,H265编码器需要的是NV12,所以需要转换下,也就是Y不变UV交换一下。

    fun nv21toNv12(nv21: ByteArray): ByteArray {val size = nv21.sizeval nv12 = ByteArray(size)val y_len = size * 2 / 3// YSystem.arraycopy(nv21, 0, nv12, 0, y_len)var i = y_len// nv12和nv21是奇偶交替while (i < size - 1) {nv12[i] = nv21[i + 1]nv12[i + 1] = nv21[i]i += 2}return nv12}

2.上文提到了camera摄像头的预览需要旋转,只是预览画面进行旋转了,yuv的数据并没有旋转,所以yuv数据也需要旋转。

    fun dataTo90(data: ByteArray, output: ByteArray, width: Int, height: Int) {val y_len = width * height// uv数据高为y数据高的一半val uvHeight = height shr 1 // kotlin 的shr 1 就是右移1位 height >> 1var k = 0for (j in 0 until width) {for (i in height - 1 downTo 0) {output[k++] = data[width * i + j]}}// uvvar j = 0while (j < width) {for (i in uvHeight - 1 downTo 0) {output[k++] = data[y_len + width * i + j]output[k++] = data[y_len + width * i + j + 1]}j += 2}}

H265硬编码

这个和H264的使用方法一样,唯一的区别就是创建MediaCodec的时候指定是H265编码器。即MediaFormat.MIMETYPE_VIDEO_HEVC(它的值是video/hevc

// H265编码器 video/hevc
mediaCodec = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_HEVC)

具体的编码流程和H264的一样,没啥区别,这里就不多介绍了,可以参考前前面文章H264的编解码的介绍。Android音视频【四】H264硬编码

唯一要特别注意的是指定编码器的参数的时候,视频的宽和高的时候需要对调。因为后置摄像头旋转了90度,yuv数据也旋转了90度,也就是宽和高对调了。

WebSocket通信

WebSocket依赖添加如下

implementation "org.java-websocket:Java-WebSocket:1.4.0"

使用方法很简单,就是API的使用,内部实现感兴趣的可以研究下。

  • WebSocketServer端
// 创建WebSocketServerprivate val webSocketServer: WebSocketServer = object :WebSocketServer(InetSocketAddress(PORT)) {// ...省略其它代码// 接收数据override fun onMessage(conn: WebSocket, message: ByteBuffer) {super.onMessage(conn, message)if (h265ReceiveListener != null) {val buf = ByteArray(message.remaining())message[buf]Log.d(TAG, "onMessage:" + buf.size)h265ReceiveListener?.onReceive(buf)}}
}// 发送数据override fun sendData(bytes: ByteArray?) {if (webSocket?.isOpen == true) {webSocket?.send(bytes)}}// 建立连接override fun start() {webSocketServer.start()}
  • WebSocketClient端
    private inner class MyWebSocketClient(serverUri: URI) : WebSocketClient(serverUri) {// 接收数据override fun onMessage(bytes: ByteBuffer) {if (h265ReceiveListener != null) {val buf = ByteArray(bytes.remaining())bytes.get(buf)Log.i(TAG, "onMessage:" + buf.size)h265ReceiveListener?.onReceive(buf)}}}

发送数据和建立连接

// 发送数据override fun sendData(bytes: ByteArray?) {if (myWebSocketClient?.isOpen == true) {myWebSocketClient?.send(bytes)}}// 建立连接 private const val URL = "ws://172.24.92.58:$PORT"override fun start() {try {val url = URI(URL)myWebSocketClient = MyWebSocketClient(url)myWebSocketClient?.connect()} catch (e: Exception) {e.printStackTrace()}}    

这里就不多介绍了,都是API的使用,很简单。

private const val URL = “ws://172.24.92.58:$PORT” 是另一台手机的ip地址 ,如果跑demo的话,自己改一下哦

H265硬解码

这个和H264的使用方法一样,这里就不多介绍了,可以参考前前面文章H264的编解码的介绍。唯一的区别就是创建MediaCodec的时候指定是H265解码器。

  // H265解码器mediaCodec = MediaCodec.createDecoderByType(MediaFormat.MIMETYPE_VIDEO_HEVC)

怎么渲染到surface呢,在创建完解码器后进行配置阶段指定即可。

// 渲染到surface上
mediaCodec?.configure(mediaFormat, surface, null, 0)
mediaCodec?.start()

然后在解码完数据的时候,指定是否将h265解码后的数据渲染到configure配置阶段的surface上,true渲染,falsse不渲染。

// true渲染到surface上mediaCodec!!.releaseOutputBuffer(outputBufferIndex, true)

VPS,SPS,PPS网络传输

Android中的硬编码器MediaCodec首帧编码出来的是SPS,PPS等数据,在H265数据流中多了 VPS。随后编码出来的是I帧,P帧,B帧后续也不会回调出来VPS,SPS,PPS等数据了。我们想一个问题就是:在网络传输怎么处理VPS,SPS,PPS呢?,其实不止这个例子,所有的网络发送H264/H265数据的时候都需要处理这个问题。

VPS(视频参数集),SPS(序列参数集),PPS(图像参数集)

  1. VPS 、SPS、PPS 包含了在解码端(播放端)所用需要的profile,level,图像的宽和高。
  2. 发送端(直播端/主播)已经直播一小时了,有的用户播放端(用户端)才进入直播间,如果后续没有了VPS 、SPS、PPS那么解码怎么解码怎么渲染呢?对吧。

所以处理方法就是:缓存VPS,SPS,PPS的数据,然后在发送每个关键帧(I帧)前先发送VPS、SPS、PPS的数据即可。这样后续进来的用户等下一个关键帧(I帧)就会立刻看到画面了。

关键代码如下:

    private fun dealFrame(byteBuffer: ByteBuffer) {// H265的nalu的分割符的下一个字节的类型var offset = 4if (byteBuffer[2].toInt() == 0x1) {offset = 3}// VPS,SPS,PPS...  H265的nalu头是2个字节,中间的6位bit是nalu类型// 0x7E的二进制的后8位是 0111  1110// java版本// int naluType = (byteBuffer.get(offset) & 0x7E) >> 1;val naluType = byteBuffer[offset].and(0x7E).toInt().shr(1)// 保存下VPS,SPS,PPS的数据if (NAL_VPS == naluType) {vps_sps_pps_buf = ByteArray(info.size)byteBuffer.get(vps_sps_pps_buf!!)} else if (NAL_I == naluType) {// 因为是网络传输,所以在每个i帧之前先发送VPS,SPS,PPSval bytes = ByteArray(info.size)byteBuffer.get(bytes)val newBuf = ByteArray(info.size + vps_sps_pps_buf!!.size)System.arraycopy(vps_sps_pps_buf!!, 0, newBuf, 0, vps_sps_pps_buf!!.size)System.arraycopy(bytes, 0, newBuf, vps_sps_pps_buf!!.size, bytes.size)// 发送h265DecodeListener?.onDecode(bytes)} else {// 其它bp帧数据val bytes = ByteArray(info.size)byteBuffer.get(bytes)// 发送h265DecodeListener?.onDecode(bytes)}}

源码


https://github.com/ta893115871/H265WithCameraWebSocket

这篇关于Android音视频【七】H265硬编解码视频通话的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/795332

相关文章

Python视频处理库VidGear使用小结

《Python视频处理库VidGear使用小结》VidGear是一个高性能的Python视频处理库,本文主要介绍了Python视频处理库VidGear使用小结,文中通过示例代码介绍的非常详细,对大家的... 目录一、VidGear的安装二、VidGear的主要功能三、VidGear的使用示例四、VidGea

Android数据库Room的实际使用过程总结

《Android数据库Room的实际使用过程总结》这篇文章主要给大家介绍了关于Android数据库Room的实际使用过程,详细介绍了如何创建实体类、数据访问对象(DAO)和数据库抽象类,需要的朋友可以... 目录前言一、Room的基本使用1.项目配置2.创建实体类(Entity)3.创建数据访问对象(DAO

通过C#和RTSPClient实现简易音视频解码功能

《通过C#和RTSPClient实现简易音视频解码功能》在多媒体应用中,实时传输协议(RTSP)用于流媒体服务,特别是音视频监控系统,通过C#和RTSPClient库,可以轻松实现简易的音视... 目录前言正文关键特性解决方案实现步骤示例代码总结最后前言在多媒体应用中,实时传输协议(RTSP)用于流媒体服

Android WebView的加载超时处理方案

《AndroidWebView的加载超时处理方案》在Android开发中,WebView是一个常用的组件,用于在应用中嵌入网页,然而,当网络状况不佳或页面加载过慢时,用户可能会遇到加载超时的问题,本... 目录引言一、WebView加载超时的原因二、加载超时处理方案1. 使用Handler和Timer进行超

流媒体平台/视频监控/安防视频汇聚EasyCVR播放暂停后视频画面黑屏是什么原因?

视频智能分析/视频监控/安防监控综合管理系统EasyCVR视频汇聚融合平台,是TSINGSEE青犀视频垂直深耕音视频流媒体技术、AI智能技术领域的杰出成果。该平台以其强大的视频处理、汇聚与融合能力,在构建全栈视频监控系统中展现出了独特的优势。视频监控管理系统EasyCVR平台内置了强大的视频解码、转码、压缩等技术,能够处理多种视频流格式,并以多种格式(RTMP、RTSP、HTTP-FLV、WebS

综合安防管理平台LntonAIServer视频监控汇聚抖动检测算法优势

LntonAIServer视频质量诊断功能中的抖动检测是一个专门针对视频稳定性进行分析的功能。抖动通常是指视频帧之间的不必要运动,这种运动可能是由于摄像机的移动、传输中的错误或编解码问题导致的。抖动检测对于确保视频内容的平滑性和观看体验至关重要。 优势 1. 提高图像质量 - 清晰度提升:减少抖动,提高图像的清晰度和细节表现力,使得监控画面更加真实可信。 - 细节增强:在低光条件下,抖

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo

Android平台播放RTSP流的几种方案探究(VLC VS ExoPlayer VS SmartPlayer)

技术背景 好多开发者需要遴选Android平台RTSP直播播放器的时候,不知道如何选的好,本文针对常用的方案,做个大概的说明: 1. 使用VLC for Android VLC Media Player(VLC多媒体播放器),最初命名为VideoLAN客户端,是VideoLAN品牌产品,是VideoLAN计划的多媒体播放器。它支持众多音频与视频解码器及文件格式,并支持DVD影音光盘,VCD影

android-opencv-jni

//------------------start opencv--------------------@Override public void onResume(){ super.onResume(); //通过OpenCV引擎服务加载并初始化OpenCV类库,所谓OpenCV引擎服务即是 //OpenCV_2.4.3.2_Manager_2.4_*.apk程序包,存

从状态管理到性能优化:全面解析 Android Compose

文章目录 引言一、Android Compose基本概念1.1 什么是Android Compose?1.2 Compose的优势1.3 如何在项目中使用Compose 二、Compose中的状态管理2.1 状态管理的重要性2.2 Compose中的状态和数据流2.3 使用State和MutableState处理状态2.4 通过ViewModel进行状态管理 三、Compose中的列表和滚动