王学岗————从零实现手写音视频通话(H265)

webrtc

WebRTC (Web Real-Time Communications) 是一项实时通讯技术,它允许网络应用或者站点,在不借助中间媒介的情况下,建立浏览器之间点对点(Peer-to-Peer)的连接,实现视频流和(或)音频流或者其他任意数据的传输。WebRTC 包含的这些标准使用户在无需安装任何插件或者第三方的软件的情况下,创建点对点(Peer-to-Peer)的数据分享和电话会议成为可能。
A和B进行视频通话,A对摄像头数据(yuv)编码成h264传给B。B也做同样的事情。与投屏不一样的是,A与B是双向的。
为了简单方便,我们今天构建两个项目。两个APK
dsp既可以进行编码,也可以进行解码。

转换

nv21转成nv12

 static  byte[] nv12;
    /**
     * yyyyyyyy uvuvuvuvuv nv21 android摄像头
     * yyyyyyyy uuuuuvvvvv nv12(又叫yuv420)
     */
    public static byte[]  nv21toNV12(byte[] nv21) {
// 数组的长度分成三部分,y的长度是1,u的长度是1/4,v同U
        int  size = nv21.length;
        nv12 = new byte[size];
        //y占总长度的三分之二
        int len = size * 2 / 3;
        System.arraycopy(nv21, 0, nv12, 0, len);
        int i = len;
        while(i < size - 1){
            nv12[i] = nv21[i + 1];
            nv12[i + 1] = nv21[i];
            i += 2;
        }
        return nv12;
    }

摄像头旋转

王学岗————从零实现手写音视频通话(H265)_第1张图片

//摄像头旋转算法,要旋转90度
    public static void portraitData2Raw(byte[] data,byte[] output,int width,int height) {
        int y_len = width * height;
        int uvHeight = height >> 1; // uv数据高为y数据高的一半
        int k = 0;
        for (int j = 0; j < width; j++) {//j是行,i是列
            for (int i = height - 1; i >= 0; i--) {
                output[k++] = data[ width * i + j];
            }
        }
        for (int j = 0; j < width; j += 2) {
            for (int i = uvHeight - 1; i >= 0; i--) {
                output[k++] = data[y_len + width * i + j];
                output[k++] = data[y_len + width * i + j + 1];
            }
        }
    }

音频

1,音频的源数据是pcm,编码后可以是aac。acc是压缩数据。
王学岗————从零实现手写音视频通话(H265)_第2张图片
上图中,从input.mp4中提取aac和pcm文件,可以发现,pcm文件大于aac文件
在这里插入图片描述

2,王学岗————从零实现手写音视频通话(H265)_第3张图片
sin函数,cos函数可以存储声波。
2,声音波形一般比较复杂,需要用一系列数学表达式存储。编码解码会耗费cpu很大算力,这样存储传输不划算。
在这里插入图片描述

3,我们通过采样来存储声波。问题是间隔多少采样合适?人耳能听到的范围是20-20万HZ。很长一个周期就是低频(很长才一个周期的波段)。采样用的是奈奎斯特定理——最高频的两倍。
最高频是值波形最密集的地方。
王学岗————从零实现手写音视频通话(H265)_第4张图片
物理中声音是由物体振动发生的,正在发声的物体叫做声源。物体在一秒钟之内振动的次数叫做频率,单位是赫兹,字母Hz。人的耳朵可以听到2020000Hz的声音,最敏感是200800Hz之间的声音。
王学岗————从零实现手写音视频通话(H265)_第5张图片
三个周期采样三个点,无法还原
王学岗————从零实现手写音视频通话(H265)_第6张图片
三个周期采样四个点,也无法还原
王学岗————从零实现手写音视频通话(H265)_第7张图片
每个周期采样两个点,可以还原。
一秒钟采样22.05kX2 =44100个点 。//人类耳朵能够识别的最大频率是22050HZ,绝大部分人能够识别的最大频率是20000HZ
采样的间隔就叫采样的频率。
4,一个字节8位,两个字节16位(-32678-32767)。采样位数一般是16位(也有8位、24位等),采样位数的值越大,解析度就越高,录制和回放的声音就越真实。
王学岗————从零实现手写音视频通话(H265)_第8张图片
调节视频中的声音,可以把每个值乘以系数。所有的波形增加,而不是某个采样点增加。
5,王学岗————从零实现手写音视频通话(H265)_第9张图片
音色波形是完全不一样的。
6,王学岗————从零实现手写音视频通话(H265)_第10张图片
王学岗————从零实现手写音视频通话(H265)_第11张图片
同一个波形被压缩了就是高音调,同一个波形被拉伸了就是低音调。
7,通道数就是波形数

音频代码

录制的声音16进制数据
王学岗————从零实现手写音视频通话(H265)_第12张图片

package com.maniu.webrtcmaniua;

import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioRecord;
import android.media.AudioTrack;
import android.media.MediaRecorder;
import android.os.Handler;
import android.os.HandlerThread;
import android.util.Log;

import androidx.core.app.ActivityCompat;

import java.io.FileInputStream;

public class AudioRecoderLive {
    private AudioTrack audioTrack;
    SocketLive socketLive;
    AudioRecord audioRecord;
    // 采样率,现在能够保证在所有设备上使用的采样率是44100Hz, 但是其他的采样率(22050, 16000, 11025)在一些设备上也可以使用。
    public static final int SAMPLE_RATE_INHZ = 44100;
    // 声道数。CHANNEL_IN_MONO and CHANNEL_IN_STEREO. 其中CHANNEL_IN_MONO是可以保证在所有设备能够使用的。
    public static final int CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_MONO;
    // 返回的音频数据的格式。 ENCODING_PCM_8BIT, ENCODING_PCM_16BIT, and ENCODING_PCM_FLOAT.
    public static final int AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
//    int bufferSizeInBytes
    boolean isRecording = false;
    HandlerThread handlerThread;
    Handler workHandler;
    public AudioRecoderLive(SocketLive socketLive) {
        this.socketLive = socketLive;
    }
    public void startRecord(Context context) {
        handlerThread = new HandlerThread("handlerThread");
        handlerThread.start();
        workHandler = new Handler(handlerThread.getLooper());
        final int minBufferSize = AudioRecord.getMinBufferSize(SAMPLE_RATE_INHZ, CHANNEL_CONFIG, AUDIO_FORMAT);
        //申请录音权限
        if (ActivityCompat.checkSelfPermission(context, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {

            return;
        }
        //通过AudioRecord采样
        audioRecord = new AudioRecord(
                MediaRecorder.AudioSource.MIC,//音频的来源,我们选择录制MIC麦克风
                SAMPLE_RATE_INHZ,//采样频率
                CHANNEL_CONFIG,//声道数,一般是双通道
                AUDIO_FORMAT,//采样位数,2个字节16位
                minBufferSize);//声音最小数据量

        audioRecord.startRecording();
        //录音是耗时操作,开启个子线程
        isRecording = true;
        // 初始化缓存
        final byte[] data = new byte[minBufferSize];
        // 创建数据流,将缓存导入数据流
        workHandler.post(new Runnable() {
            @Override
            public void run() {
                while (isRecording) {
                    //麦克风数据放到data容器中
                    int length = audioRecord.read(data, 0, minBufferSize);
                    YuvUtils.writeContent(data);
                    YuvUtils.writeBytes(data);
                    socketLive.sendData(data, 0);
                }
            }
        });
    }
    public void initPlay() {
        Log.i("Tag8","go there");
        //配置播放器
        //音乐类型,扬声器播放
        int streamType = AudioManager.STREAM_MUSIC;
        //录音时采用的采样频率,所有播放时同样的采样频率
        int sampleRate = 44100;
        //单声道,和录音时设置的一样
        int channelConfig = AudioFormat.CHANNEL_OUT_MONO;
        // 录音使用16bit,所有播放时同样采用该方式
        int audioFormat = AudioFormat.ENCODING_PCM_16BIT;
        //流模式
        int mode = AudioTrack.MODE_STREAM;

        //计算最小buffer大小
        int minBufferSize=AudioTrack.getMinBufferSize(sampleRate,channelConfig,audioFormat);
        //AudioTrack是用来播放的。
        //构造AudioTrack  不能小于AudioTrack的最低要求,也不能小于我们每次读的大小
        audioTrack=new AudioTrack(streamType,sampleRate,channelConfig,audioFormat,
                minBufferSize,mode);
        audioTrack.setVolume(16f);
        //从文件流读数据
        FileInputStream inputStream = null;
        audioTrack.play();
    }

    public void doPlay( byte[] mBuffer) {
     
        int ret = audioTrack.write(mBuffer, 1, mBuffer.length-1);
        Log.i("Tag8","ret ==="+ret);
        //检查write的返回值,处理错误
        switch(ret) {
            case AudioTrack.ERROR_INVALID_OPERATION:
            case AudioTrack.ERROR_BAD_VALUE:
            case AudioManager.ERROR_DEAD_OBJECT:
                return;
            default:
                break;
        }
        Log.i("Tag8","播放成功。。。。");

    }

}

你可能感兴趣的:(android音视频开发,音视频)