和java传输h264代码rtp头区别

  • rtp头:M位置需要设置为1(标记音频开始);PT=97(有效载荷类型97表示aac)

代码

  • RtspTcpServer.java
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import .InetAddress;
import .ServerSocket;
import .Socket;
import java.util.Arrays;
import java.util.Date;

// Linux内核对TCP连接的识别是通过四元组来区分:源ip,源port,目标ip,目标port
public class RtspTcpServer {
    public static void main(String[] args) throws IOException, InterruptedException {
        ServerSocket serverSocket = new ServerSocket(8888);//1.创建服务端对象
        System.out.println("TCP服务端端启动===>"+serverSocket.getLocalSocketAddress());
        while (true){
            Socket socket = serverSocket.accept();	//阻塞式,2.获取连接过来的客户端对象
            //获取到连接,则开启一个线程处理当前连接
            new Thread(new Runnable() {
                @Override
                public void run() {
                    InputStream inputStream = null;
                    OutputStream outputStream = null;
                    RTPServer rtpServer = null;
                    try {
                        System.out.println("TCP已连接===>"+socket.getRemoteSocketAddress());
                        inputStream = socket.getInputStream();//3.通过socket对象获取输入流,要读取客户端发来的数据
                        outputStream = socket.getOutputStream();//3.通过socket对象获取输入流,要读取客户端发来的数据
                        rtpServer = new RTPServer();
                        rtpServer.setClientAddress(InetAddress.getByName(socket.getInetAddress().getHostAddress()));
                        byte[] buffer = new byte[1024*1024];
                        int readNum = 0;
                        while((readNum=inputStream.read(buffer))!=-1){
                            if(readNum>0){
                                byte[] receive = Arrays.copyOfRange(buffer,0,readNum);
//                                System.out.println("读取的字节数:"+readNum);
//                                System.out.println("读取的字节数:"+receive.length);
//                                System.out.println("缓冲区大小:"+buffer.length);
                                handlerReceiveData(outputStream,receive,rtpServer);
                            }
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    }finally {
                        System.out.println("断开连接");
                        if(inputStream!=null){
                            try {
                                inputStream.close();
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                        }
                        if(outputStream!=null){
                            try {
                                outputStream.close();
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                        }
                        if(socket!=null){
                            try {
                                socket.close();
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                        }
                        rtpServer.close();
                    }
                }
            }).start();
        }
    }

    public static void handlerReceiveData(OutputStream outputStream, byte[] buffer, RTPServer rtpServer){
        String receiveStr=new String(buffer);
        System.out.println("TCP-----------------接收receiveStr----------------------");
        System.out.println(receiveStr);
        System.out.println("TCP-----------------接收receiveStr----------------------");
        String lines[] = receiveStr.split("\\r?\\n");//按行分割
        int cseq=0;
        int clientRtpPort=0;
        int clientRtcpPort=0;
        String url=null;
        String localIp=null;
        {
            for(String line:lines){
                if(line.indexOf("rtsp://")>-1){
                    url = line.split("\\s+")[1];
                    String[] split = line.split(":");
                    localIp = split[1].substring(2);
                }
                if(line.startsWith("CSeq:")){
                    String[] split = line.split(": ");
                    cseq = Integer.parseInt(split[1].trim());
                }
                if(line.startsWith("Transport:")){
                    String[] split = line.split(";");
                    for(String i : split){
                        if(i.startsWith("client_port=")){
                            String substring = i.substring(12);
                            String[] split1 = substring.split("-");
                            clientRtpPort = Integer.parseInt(split1[0].trim());
                            clientRtcpPort = Integer.parseInt(split1[1].trim());
                        }
                    }
                }
            }
        }//获取cseq
        String responseStr=null;
        if (receiveStr.startsWith("OPTIONS")){
            //OPTIONS 请求服务端支持的RTSP方法列表;也可以定时发送这个请求来保活RTSP会话。
            responseStr=String.format("RTSP/1.0 200 OK\r\n"+
                                        "CSeq: %d\r\n"+
                                        "Public: OPTIONS, DESCRIBE, SETUP, PLAY\r\n"+
                                        "\r\n",cseq);
        }else if(receiveStr.startsWith("SETUP")){
            //SETUP:用于配置数据交互的方法(比如制定音视频的传输方式TCP或UDP)。
            responseStr=String.format("RTSP/1.0 200 OK\r\n"+
                                        "CSeq: %d\r\n"+
                                        "Transport: RTP/AVP;unicast;client_port=%d-%d;server_port=%d-%d\r\n"+
                                        "Session: 66334873\r\n"+
                                        "\r\n",
                                        cseq,
                                        clientRtpPort,
                                        clientRtcpPort,
                                        rtpServer.getRtpPort(),
                                        rtpServer.getRtcpPort());
            rtpServer.setClientRtpPort(clientRtpPort);
            rtpServer.setClientRtcpPort(clientRtcpPort);
        }else if(receiveStr.startsWith("DESCRIBE")){
            //DESCRIBE:请求指定的媒体流的SDP描述信息(详细包括音视频流的帧率、编码类型等媒体信息)。
            String sdp=String.format("v=0\r\n"+
                                        "o=- 9%d 1 IN IP4 %s\r\n"+
                                        "t=0 0\r\n"+
                                        "a=control:*\r\n"+
                                        "m=audio 0 RTP/AVP 97\r\n"+
                                        "a=rtpmap:97 mpeg4-generic/44100/2\r\n"+
                                        "a=fmtp:97 SizeLength=13;\r\n"+
                                        "a=control:track0\r\n",
                                        new Date().getTime(), localIp);
            responseStr=String.format("RTSP/1.0 200 OK\r\nCSeq: %d\r\n"+
                                        "Content-Base: %s\r\n"+
                                        "Content-type: application/sdp\r\n"+
                                        "Content-length: %d\r\n\r\n"+
                                        "%s",
                                        cseq,
                                        url,
                                        sdp.length(),
                                        sdp);
        }else if(receiveStr.startsWith("PLAY")){
            //PLAY:用于启动(当暂停时重启)交付数据给客户端。
            responseStr=String.format("RTSP/1.0 200 OK\r\n"+
                                        "CSeq: %d\r\n"+
                                        "Range: npt=0.000-\r\n"+
                                        "Session: 66334873; timeout=60\r\n" +
                                        "\r\n",
                                        cseq);
        }else if(receiveStr.startsWith("PAUSE")){
            //PAUSE:用于临时停止服务端的数据的交互(使用PLAY来重新启动数据交互)。
            responseStr=String.format("RTSP/1.0 200 OK\r\n" +
                                        "CSeq: %d\r\n" +
                                        "\r\n",cseq);
        }else if(receiveStr.startsWith("TEARDOWN")){
            //TEARDOWN:请求终止来自服务端的数据的传输。
            responseStr=String.format("RTSP/1.0 200 OK\r\n" +
                                        "CSeq: %d\r\n" +
                                        "\r\n",cseq);
        }
        try {
            outputStream.write(responseStr.getBytes());
            outputStream.flush();
            System.out.println("TCP-----------------响应responseStr----------------------");
            System.out.println(responseStr);
            System.out.println("TCP-----------------响应responseStr----------------------");
            if(receiveStr.startsWith("PLAY")){
                try {
                    rtpServer.startSendRtpPackage();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}
  • RTPServer.java
import java.io.IOException;
import java.io.RandomAccessFile;
import .DatagramPacket;
import .DatagramSocket;
import .InetAddress;
import .SocketException;
import java.util.ArrayList;
import java.util.List;

public class RTPServer {
    private RandomAccessFile in;
    private List<ADTSHeader> ADTSIndexs = new ArrayList<>() ;//用来记录每个ADTS帧信息以及起始位置
    InetAddress clientAddress;
    int clientRtpPort;
    int clientRtcpPort;
    DatagramSocket rtpUdpSocket;
    DatagramSocket rtcpUdpSocket;
    public RTPServer() throws SocketException {
        rtpUdpSocket = new DatagramSocket();//建立socket服务
        rtcpUdpSocket = new DatagramSocket();//建立socket服务
        System.out.println("rtp UDP socket启动===>"+rtpUdpSocket.getLocalSocketAddress());
        System.out.println("rtcp UDP socket启动===>"+rtcpUdpSocket.getLocalSocketAddress());
        new Thread(new Runnable() {
            @Override
            public void run() {
                byte[] buf = new byte[1024*1024];//创建数据包
                DatagramPacket datagramPacket = new DatagramPacket(buf,buf.length);
                while (true){
                    try {
                        rtpUdpSocket.receive(datagramPacket); //阻塞式,3.使用接收方法将数据存储到数据包中
                        System.out.println("rtp UDP接收包===>"+datagramPacket.getSocketAddress());
                        handlerReceiveData(datagramPacket);
                    } catch (IOException e) {
                        e.printStackTrace();
                        break;
                    }
                }
            }
        });
        new Thread(new Runnable() {
            @Override
            public void run() {
                byte[] buf = new byte[1024*1024];//创建数据包
                DatagramPacket datagramPacket = new DatagramPacket(buf,buf.length);
                while (true){
                    try {
                        rtcpUdpSocket.receive(datagramPacket); //阻塞式,3.使用接收方法将数据存储到数据包中
                        System.out.println("rtp UDP接收包===>"+datagramPacket.getSocketAddress());
                        handlerReceiveData(datagramPacket);
                    } catch (IOException e) {
                        e.printStackTrace();
                        break;
                    }
                }
            }
        });
    }
    public void handlerReceiveData(DatagramPacket datagramPacket){
        System.out.println("接收到参数:"+datagramPacket.getSocketAddress());
        System.out.println(datagramPacket.getData());
        System.out.println(datagramPacket.getLength());
    }
    public void close(){
        if(rtpUdpSocket!=null){
            rtpUdpSocket.close();
        }
        if(rtcpUdpSocket!=null){
            rtcpUdpSocket.close();
        }
    }

    public void startSendRtpPackage() throws Exception {
        String fileName = RTPServer.class.getResource("test.aac").getPath();
        in = new RandomAccessFile(fileName, "r");
        parseIndexs();//获取所有起始下标
        sendADTSRtpPackage();
        in.close();
    }
    /*
     * AAC的音频文件格式有ADIF & ADTS:
     *  - ADIF:Audio Data Interchange Format音频数据交换格式,ADIF只有一个统一的头,所以必须得到所有的数据后解码。
     *      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     *      |  ADIF header  |    AAC data           |
     *      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     *  - ADTS:Audio Data Transport Stream是AAC音频的传输流格式,ADTS每一帧都有头信息,可以在任意帧解码。
     *      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     *      |  ADTS header              |    AAC data           |  ADTS header  |    AAC data           |......
     *      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     *     |fixed28bits|variable28bits|
     *      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     */
    public void parseIndexs() throws Exception {
        boolean isEnd=false;
        while(true) {
            if(in.length()>0) {
                Long index = in.getFilePointer();
                byte[] header=new byte[7];
                in.read(header);
                if(((header[0]&0xFF)==255)&&((header[1] & 0xF0)==240)){
                    ADTSHeader adtsHeader = new ADTSHeader();
                    adtsHeader.index = index;
                    adtsHeader.syncword = 0xFFF;//同步头12bit总是0xFFF,代表着一个ADTS帧的开始
                    adtsHeader.id = (header[1] & 0x08)>>3;//1 bit MPEG 标示符:0=MPEG-4;1=MPEG-2;
                    adtsHeader.layer = (header[1] & 0x06)>>1;     //2 bit 总是00
                    adtsHeader.protectionAbsent = header[1] & 0x01;  //1 bit 1表示没有crc,0表示有crc
                    adtsHeader.profile = (header[2] & 0xc0)>>6;           //2 bit 表示使用哪个级别的AAC
                    adtsHeader.samplingFreqIndex = (header[2] & 0x3c)>>2; //4 bit 表示使用的采样频率
                    adtsHeader.privateBit = (header[2] & 0x02)>>1;        //1 bit
                    adtsHeader.channelCfg = (((header[2] & 0x01)<<2) | ((header[3] & 0xc0)>>6));        //3 bit 表示声道数
                    adtsHeader.originalCopy = (header[3] & 0x20)>>5;     //1 bit
                    adtsHeader.home = (header[3] & 0x10)>>4;                  //1 bit
                    /*可变头信息28bits:帧与帧之间可变*/
                    adtsHeader.copyrightIdentificationBit = (header[3] & 0x08)>>3;   //1 bit
                    adtsHeader.copyrightIdentificationStart = (header[3] & 0x04)>>2; //1 bit
                    adtsHeader.aacFrameLength = (((header[3] & 0x03)<<11) | (header[4]<< 3) | ((header[5]&0xe0)>> 5));//13 bit 一个ADTS帧的长度包括ADTS头和AAC原始流
                    adtsHeader.adtsBufferFullness = (((header[5] & 0x1f)<<6) | ((header[6] & 0xfc)>>2));           //11 bit 0x7FF 说明是码率可变的码流
                    adtsHeader.numberOfRawDataBlockInFrame = header[6] & 0x03; //2 bit
                    ADTSIndexs.add(adtsHeader);//getFilePointer()返回此文件中的当前偏移量。
                    Long nextStart = in.getFilePointer() + adtsHeader.aacFrameLength-7;
                    if(in.length()-1>=nextStart){
                        in.seek(nextStart);
                    }else{
                        isEnd=true;
                    }
                }else{
                    throw new Exception("failed to parse adts header");
                }
            }
            if(isEnd) {
                //读到文件尾部,跳出
                break;
            }
        }
    }

    /*
     * 获取每一帧ADTS
     */
    public void sendADTSRtpPackage() throws IOException, InterruptedException {
        /*
         * 如果采样频率是44100
         * 一般AAC每个1024个采样为一帧
         * 所以一秒就有 44100 / 1024 = 43帧
         * 时间增量就是 44100 / 43 = 1025
         * 一帧的时间为 1 / 43 = 23ms
         */
        int timestamp_increse = 44100/(44100/1024);//时间增量
        int PT = 97;//负载类型号97:AAC
        int packageSize = 1400;//最大负载长度
        int seqNum = 1;//序列号
        int ts_current = 1;//当前时间戳
        for(int i=0;i<ADTSIndexs.size();i++) {
            in.seek(ADTSIndexs.get(i).index+7);//设置文件指针偏移
            int len = ADTSIndexs.get(i).aacFrameLength-7;//读取长度
            byte[] aacADTSDataArr=new byte[len];//正真的数据包
            in.read(aacADTSDataArr);
            byte[] bytes = aacDataToRtp(aacADTSDataArr, PT, seqNum, ts_current, 0x88923423);
            //2.创建数据报,包含响应的数据信息
//            System.out.println(bytes.length+"="+len+"("+in.getFilePointer()+"="+ADTSIndexs.get(i).aacFrameLength+"-7)+"+16);
            DatagramPacket packet2=new DatagramPacket(bytes,bytes.length,clientAddress,clientRtpPort);
            try {
                rtpUdpSocket.send(packet2);//3.响应客户端
            } catch (IOException e) {
                e.printStackTrace();
            }
            ts_current+=timestamp_increse;
            seqNum ++;
        }
    }
    /*
    * 一个ADTS帧数据一个rtp包
    * AAC的RTP打包方式:
    *   Rtp头+4byte(0x00+0x10+aac_data_size13bit)+AAC_Data
    * */
    public byte[] aacDataToRtp(byte[] aacADTSDataArr,int PT,int seq,int timestamp,int ssrc){
        byte[] rtpHeader = initRTPHeader(PT, seq, timestamp, ssrc);//12个字节的rtpHeader
        byte[] rtpPackage=new byte[12+4+aacADTSDataArr.length];
        System.arraycopy(rtpHeader,0,rtpPackage,0,12);//从源数组的第几位,复制到目标数组开始下标,n位
        rtpPackage[12] = 0;
        rtpPackage[13] = 0x10;
        rtpPackage[14] = (byte) ((aacADTSDataArr.length & 0xFF)>>5);//& 0x1FE0)>>5);//aac data大小高8位
        rtpPackage[15] = (byte) ((aacADTSDataArr.length & 0x1F)<<3);//aac data大小底5位,放在字节高位
        System.arraycopy(aacADTSDataArr,0,rtpPackage,16,aacADTSDataArr.length);//从源数组的第几位,复制到目标数组开始下标,n位
        return rtpPackage;
    }
    /*
     *  RTP报文格式:
     *               |===============================================================|
     *              |        0      |       1       |        2      |      3        |
     *             |===============|===============|===============|===============|
     *            |7 6 5 4 3 2 1 0|7 6 5 4 3 2 1 0|7 6 5 4 3 2 1 0|7 6 5 4 3 2 1 0|
     *           |===============|===============|===============================|
     *          |V2|P1|X1|CC4   |M1|     PT7    |       sequence number16       |
     *         |===============================================================|
     *        |                       timestamp时间戳                         |
     *       |===============================================================|
     *      |同步信源(SSRC)标识符synchronization source (SSRC) identifier   |
     *     |===============================================================|
     *    |特约信源(CSRC)标识符contributing source (CSRC) identifiers     |
     *   |                         ....                                  |
     *  |===============================================================|
     */
    public byte[] initRTPHeader(int PT,int seq,int timestamp,int ssrc){
        byte[] headerArr = new byte[12];//rtp固定头部有12个字节
        //1.清空headerArr
        for (int i = 0; i < headerArr.length; i++) { headerArr[i] = (byte) 0; }
        //2.填充数据
        //字节1:|V2|P1|X1|CC4   |
        headerArr[0] = (byte) 0x80;//10000000==>V=1.0,P=0,X=0,CC=0000
        //字节2:|M1|     PT7    |
        headerArr[1] = (byte)(PT & 0xff | 0x80);//11100001==>M=1,PT=1100001
        //字节3-4:|sequence number16|:headerArr[2],headerArr[3]
        System.arraycopy(intToBytes(seq,2),0,headerArr,2,2);//从源数组的第几位,复制到目标数组开始下标,n位
        //字节5-8|timestamp时间戳|:headerArr[4]~headerArr[7]
        System.arraycopy(intToBytes(timestamp,4),0,headerArr,4,4);//从源数组的第几位,复制到目标数组开始下标,n位
        //字节9-12|同步信源(SSRC)标识符|:headerArr[8]~headerArr[11]
        System.arraycopy(intToBytes(ssrc,4),0,headerArr,8,4);//从源数组的第几位,复制到目标数组开始下标,n位
        return headerArr;
    }
    /**
     *   将32位长度转换为n字节。(大端字节序:高位在前,低位在后)
     *   @param ldata 将从中构造n字节数组的int。
     *   @param n 要将长文件转换为的所需字节数。
     *   @return 用长值填充的所需字节数组。
     */
    public byte[] intToBytes(int ldata, int n) {
        byte[] buff = new byte[n];
        for (int i=n-1;i>=0;i--) {
            // 保持将最右边的8位分配给字节数组,同时在每次迭代中移位8位
            buff[i] = (byte)ldata;
            ldata = ldata>>8;
        }
        return buff;
    }


    public int getRtpPort(){
        return rtpUdpSocket.getLocalPort();
    }
    public int getRtcpPort(){
        return rtcpUdpSocket.getLocalPort();
    }

    public InetAddress getClientAddress() {
        return clientAddress;
    }

    public void setClientAddress(InetAddress clientAddress) {
        this.clientAddress = clientAddress;
    }

    public int getClientRtpPort() {
        return clientRtpPort;
    }

    public void setClientRtpPort(int clientRtpPort) {
        this.clientRtpPort = clientRtpPort;
    }

    public int getClientRtcpPort() {
        return clientRtcpPort;
    }

    public void setClientRtcpPort(int clientRtcpPort) {
        this.clientRtcpPort = clientRtcpPort;
    }
    /**
     *  - ADTS:Audio Data Transport Stream是AAC音频的传输流格式,ADTS每一帧都有头信息,可以在任意帧解码。
     *      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     *      |  ADTS header              |    AAC data           |  ADTS header  |    AAC data |......
     *      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     *     |fixed28bits|variable28bits|
     *      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     */
    static class ADTSHeader{
        /*ADTS帧的起始位置*/
        Long index;

        /*固定头信息28bits(每个帧都一样)*/
        int syncword;  //12 bit 同步字1111 1111 1111=0xFFF,说明一个ADTS帧的开始
        int id;        //1 bit MPEG 标示符:0=MPEG-4;1=MPEG-2;
        int layer;     //2 bit 总是00
        int protectionAbsent;  //1 bit 表示是否误码校验1-没有crc,0-有crc
        int profile;           //2 bit 表示使用哪个级别的AAC
        int samplingFreqIndex; //4 bit 表示使用的采样率下标
        int privateBit;        //1 bit
        int channelCfg;        //3 bit 表示声道数
        int originalCopy;     //1 bit
        int home;             //1 bit
        /*可变头信息28bits:帧与帧之间可变*/
        int copyrightIdentificationBit;   //1 bit
        int copyrightIdentificationStart; //1 bit
        int aacFrameLength;               //13 bit 一个ADTS帧的长度包括ADTS头和AAC原始流
        int adtsBufferFullness;           //11 bit 0x7FF 说明是码率可变的码流
        int numberOfRawDataBlockInFrame; //2 bit

        public int formatSamplingFreqIndex(int samplingFreqIndex){
            if((samplingFreqIndex & 0x0f)==0x0){
                return 96000;
            }else if((samplingFreqIndex & 0x0f)==0x1){
                return 88200;
            }else if((samplingFreqIndex & 0x0f)==0x2){
                return 64000;
            }else if((samplingFreqIndex & 0x0f)==0x3){
                return 48000;
            }else if((samplingFreqIndex & 0x0f)==0x4){
                return 44100;
            }else if((samplingFreqIndex & 0x0f)==0x5){
                return 32000;
            }else if((samplingFreqIndex & 0x0f)==0x6){
                return 24000;
            }else if((samplingFreqIndex & 0x0f)==0x7){
                return 22050;
            }else if((samplingFreqIndex & 0x0f)==0x8){
                return 16000;
            }else if((samplingFreqIndex & 0x0f)==0x9){
                return 12000;
            }else if((samplingFreqIndex & 0x0f)==0xa){
                return 11025;
            }else if((samplingFreqIndex & 0x0f)==0xb){
                return 8000;
            }else if((samplingFreqIndex & 0x0f)==0xc){
                return 7350;
            }
            return 0;
        }
    }
}

启动

  • 运行RtspTcpServer.javamian()就跑起来了
  • 使用vlc播放器播放网络流(文件=>打开网络=>输入URL)rtsp://127.0.0.1:8888



java 对rtsp流进行缓存 java rtsp服务端_rtsp

VLC播放器




java 对rtsp流进行缓存 java rtsp服务端_rtsp_02

打开URl播放




java 对rtsp流进行缓存 java rtsp服务端_java 对rtsp流进行缓存_03

结果