Android H265版视频通话项目

Posted nullZgy

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android H265版视频通话项目相关的知识,希望对你有一定的参考价值。

    H265视频压缩算法现在无疑是所有视频压缩技术中使用最广泛,最流行的。随着 x264/openH265以及ffmpeg等开源库的推出,大多数使用者无需再对H265的细节做过多的研究,这大降低了人们使用H265的成本。  H265重新利用了H264中定义的很多概念。两者都是基于块的视频编码技术,所以它们有着相同的根
源,和相近的编码方式,包括:


  1、以宏块来细分图片,并最终以块来细分。
  2、使用帧内压缩技术减少空间冗余。
  3、使用帧内压缩技术减少时间冗余(运动估计和补偿)。
  4、使用转换和量化来进行残留数据压缩。
  5、使用熵编码减少残留和运动矢量传输和信号发送中的最后冗余。
  事实上,视频编解码从MPEG-1诞生至今都没有根本性改进,H265也只是H264在一些关键性能上的更强进化以及简单化。

H265压缩技术主要采用了以下几种方法对视频数据进行压缩。包括:

帧内预测压缩,解决的是空域数据冗余问题。
帧间预测压缩(运动估计与补偿),解决的是时域数据冗徐问题。
整数离散余弦变换(DCT),将空间上的相关性变为频域上无关的数据然后进行量化。


CABAC压缩。经过压缩后的帧分为:I帧,P帧和B帧:

I帧:关键帧,采用帧内压缩技术。 (完整编码的帧叫I)
P帧:向前参考帧,在压缩时,只参考前面已经处理的帧。采用帧音压缩技术。(参考之前的I帧生成的只包含差异部分编码的帧叫P)
B帧:双向参考帧,在压缩时,它即参考前而的帧,又参考它后面的帧。采用帧间压缩技术。(参考前后的帧编码的帧叫B)


除了I/P/B帧外,还有图像序列GOP。

GOP:两个I帧之间是一个图像序列,在一个图像序列中只有一个I帧。详细的H265介绍参照简书的介绍  https://www.jianshu.com/p/85ef926e8a00.

先通过一个仿视频通话的列子介绍H265的使用。

先看效果图:

 

代码结构:一个Client 和一个Service

Client:

services

代码不多, 两个的代码99%相似,唯独webSocket连接上不一样。下面详细的介绍实现。

首先,一个SurfaceView显示对方的视频,自顶一个LocalSurfaceView显示自己的视频。

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <Button
        android:id="@+id/call_btn"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:background="#09ac44"
        android:text="呼叫"/>

    <SurfaceView
        android:id="@+id/surface"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <zgy.services.LocalSurfaceView
        android:id="@+id/local_surface"
        android:layout_alignParentBottom="true"
        android:layout_alignParentRight="true"
        android:layout_width="200dp"
        android:layout_height="200dp" />

</RelativeLayout>

自定义一个LocalSurfeceView

package zgy.services;

import android.content.Context;
import android.hardware.Camera;
import android.util.AttributeSet;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

import java.io.IOException;

public class LocalSurfaceView extends SurfaceView implements SurfaceHolder.Callback, Camera.PreviewCallback {
    private Camera.Size size;
    private Camera mCamera;
    EncodecPushLiveH265 encodecPushLiveH265;

    public LocalSurfaceView(Context context, AttributeSet attrs) {
        super(context, attrs);
        getHolder().addCallback(this);
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        startPreview();
    }
    byte[] buffer;
    private void startPreview() {
        mCamera = Camera.open(Camera.CameraInfo.CAMERA_FACING_BACK);

        Camera.Parameters parameters = mCamera.getParameters();  //流程

        size = parameters.getPreviewSize();  //尺寸

        try {
            mCamera.setPreviewDisplay(getHolder());

            mCamera.setDisplayOrientation(90); // 横着
            buffer = new byte[size.width * size.height * 3 / 2];
            mCamera.addCallbackBuffer(buffer);
            mCamera.setPreviewCallbackWithBuffer(this);
            mCamera.startPreview();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { }
    @Override
    public void surfaceDestroyed(SurfaceHolder holder) { }

    //接受到  MainActivity调用
    public void startCaptrue(SocketLive.SocketCallback socketCallback){
        encodecPushLiveH265 = new EncodecPushLiveH265(socketCallback, size.width, size.height);
        encodecPushLiveH265.startLive();
    }

    @Override
    public void onPreviewFrame(byte[] bytes, Camera camera) { //获取到摄像头的原始数据yuv 开始  视频通话
        if (encodecPushLiveH265 != null) {
            encodecPushLiveH265.encodeFrame(bytes);
        }
        mCamera.addCallbackBuffer(bytes);
    }
}

 

其中,解码用的核心。通过MediaCodec吧数据发给DSP芯片,再获取解码好的数dequeueOutputBuffer()

 public int encodeFrame(byte[] input) { // Byte是摄像头采集的数据
        //  旋转  nv21-nv12
        // MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible
        nv12 = YuvUtils.nv21toNV12(input); //摄像头采集的数据是NV21,这里要先转成NV12,Android中MediaCodec只支持NV21数据
        //  旋转
        YuvUtils.portraitData2Raw(nv12, yuv, width, height);//将数据进行旋转

        int inputBufferIndex = mediaCodec.dequeueInputBuffer(100000);
        if (inputBufferIndex >= 0) {
            ByteBuffer inputBuffer = mediaCodec.getInputBuffer(inputBufferIndex);
            inputBuffer.clear();
            inputBuffer.put(yuv);
            long presentationTimeUs = computePresentationTime(frameIndex);
            mediaCodec.queueInputBuffer(inputBufferIndex, 0, yuv.length, presentationTimeUs, 0);
            frameIndex++;
        }
        MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
        int outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 100000);
        while (outputBufferIndex >= 0) {
            ByteBuffer outputBuffer = mediaCodec.getOutputBuffer(outputBufferIndex);
            dealFrame(outputBuffer, bufferInfo);
            mediaCodec.releaseOutputBuffer(outputBufferIndex, false);
            outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 0);

        }
        return 0;
    }

通过Socket发送数据, 一般的第一帧是VPS 或 PPs,先缓存,和I帧P帧一起发送。

private void dealFrame(ByteBuffer bb, MediaCodec.BufferInfo bufferInfo) {
        int offset = 4;
        if (bb.get(2) == 0x01) { //
            offset = 3;
        }
        int type = (bb.get(offset) & 0x7E) >> 1;
        if (type == NAL_VPS) { // VPS 先缓存下,跟I帧一起发送
            vps_sps_pps_buf = new byte[bufferInfo.size];
            bb.get(vps_sps_pps_buf);
        } else if (type == NAL_I) { //I 帧数据
            final byte[] bytes = new byte[bufferInfo.size];
            bb.get(bytes);
            byte[] newBuf = new byte[vps_sps_pps_buf.length + bytes.length];
            System.arraycopy(vps_sps_pps_buf, 0, newBuf, 0, vps_sps_pps_buf.length);
            System.arraycopy(bytes, 0, newBuf, vps_sps_pps_buf.length, bytes.length);
            this.socketLive.sendData(newBuf); //通过socket发送
        } else { //P帧数据 发送
            final byte[] bytes = new byte[bufferInfo.size];
            bb.get(bytes);
            this.socketLive.sendData(bytes);
//            Log.v(TAG, "视频数据  " + Arrays.toString(bytes));
        }
    }

 

代码下载 https://download.csdn.net/download/u011694328/18778144

 

以上是关于Android H265版视频通话项目的主要内容,如果未能解决你的问题,请参考以下文章

Android H265版视频通话项目

Android音视频H265硬编解码&视频通话

Android音视频H265硬编解码&视频通话

入秋,学习音视频视频和文字结合更配哦

Windows/Android/iOS平台视频播放器EasyPlayerEasyPlayer.js调用中无法播放H265视频流如何解决?

Android音视频——H265编码核心技术解析