传输AAC的组播RTSP服务

参考:从零开始写一个RTSP服务器(八)一个多播的RTSP服务器 aac文件:test.aac文件地址

和java实现传输AAC的RTSP服务区别

  • 服务端往组播ip+port发送AACRtp数据,循环发送
  • rtsp的响应:DESCRIBE和SETUP修改

代码

  • RtspTcpServer.java
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.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 {
        int rtcpPort = MulticastServer.start();//开启往组播组发送数据
        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;
                    try {
                        System.out.println("TCP已连接===>"+socket.getRemoteSocketAddress());
                        inputStream = socket.getInputStream();//3.通过socket对象获取输入流,要读取客户端发来的数据
                        outputStream = socket.getOutputStream();//3.通过socket对象获取输入流,要读取客户端发来的数据
                        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,rtcpPort);
                            }
                        }
                    } 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();
                            }
                        }
                    }
                }
            }).start();
        }
    }

    public static void handlerReceiveData(OutputStream outputStream, byte[] buffer,int rtcpPort){
        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;multicast;destination=%s;source=%s;port=%d-%d;ttl=255\r\n"+
                                        "Session: 66334873\r\n"+
                                        "\r\n",
                                        cseq,
                                        MulticastServer.MULTICAST_ADDRESS,
                                        url,
                                        MulticastServer.PORT,
                                        rtcpPort);
        }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"+
                                        "a=type:broadcast\r\n"+
                                        "a=rtcp-unicast: reflection\r\n"+
                                        "m=audio %d RTP/AVP 97\r\n"+
                                        "c=IN IP4 %s/255\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,
                                        MulticastServer.PORT,
                                        MulticastServer.MULTICAST_ADDRESS);
                                //sdp指定多播地址和端口
            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----------------------");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}
  • MulticastServer.java
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.MulticastSocket;

/*
 * IP组播:
 *       组播IP地址:用于标识一个IP组播组,范围是从224.0.0.0到239.255.255.255。
 *       组播组可以是永久的也可以是临时的:
 *               - 组播组地址中,有一部分由官方分配的,称为永久组播组。
 *               - 永久组播组保持不变的是它的ip地址,组中的成员构成可以发生变化。
 *               - 永久组播组中成员的数量都可以是任意的,甚至可以为零。
 *               - 那些没有保留下来供永久组播组使用的ip组播地址,可以被临时组播组利用。
 *                   - 224.0.0.0~224.0.0.255为预留的组播地址(永久组地址)。
 *                   - 224.0.1.0~238.255.255.255为用户可用的组播地址(临时组地址),全网范围内有效。
 *                   - 239.0.0.0~239.255.255.255为本地管理组播地址,仅在特定的本地范围内有效。
 *       组播源:信息的发送者,组播源不一定属于组播组,它向组播组发送数据,自己不一定是接收者。多个组播源可以同时向一个组播组发送报文。
 *       组播组:信息接收者,加入同一组播组的接收者成员可以广泛分布在网络中的任何地方,没有地域限制。
 *       组播路由器:支持组播信息传输的所有路由器
 *
 *
 * 1.IP协议规定组播地址的范围是224.0.0.0 ~ 239.255.255.255,协议软件底层设定好的
 * 2.java提供了
 *       - socket=new MulticastSocket(组播port);创建组播成员(port端口)
 *       - socket.joinGroup(InetAddress.getByName(组播ip));加入组播地址
 *       - 该socket可以接收发往组播地址的包
 * 3.往组播地址发送udp包,所有加入该组播ip+port的成员都能收到
 *       - byte[] data="哈哈哈".getBytes();
 *       - DatagramPacket packet=new DatagramPacket(data,data.length,InetAddress.getByName(组播ip),组播port);
 *       - DatagramSocket datagramSocket=new DatagramSocket();//创建DatagramSocket对象
 *       - datagramSocket.send(packet);//向目标ip+port端发送数据报
 *       - datagramSocket.close();
 * */
public class MulticastServer {
    public static String MULTICAST_ADDRESS = "225.0.0.1";//组播ip
    public static int PORT = 5555;//组播端口
    private static MulticastSocket multicastSocket;
    public static int start() throws IOException {
        /*往组播组添加一个成员MulticastSocket,用于发送数据(当然也可以是普通的DatagramSocket)*/
        multicastSocket = new MulticastSocket(PORT);
        multicastSocket.joinGroup(InetAddress.getByName(MULTICAST_ADDRESS));
        /*开启一个专门接收数据的线程(有可能收到谁(-_-)往组播组发的消息)*/
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    try {
                        byte[] buf = new byte[1024*1024];//创建数据包
                        DatagramPacket datagramPacket = new DatagramPacket(buf,buf.length);
                        multicastSocket.receive(datagramPacket);
                        handlerReceiveData(0,datagramPacket);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
        /*开启一个线程专门往组播组发送h264数据rtp包*/
        RTPAACServer rtph264Server = new RTPAACServer(multicastSocket, InetAddress.getByName(MULTICAST_ADDRESS), PORT);
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    rtph264Server.startSendRtpPackage();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }).start();
        /*创建一个rtcp单播Socket*/
        DatagramSocket rtcpUdpSocket = new DatagramSocket();//建立socket服务
        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(1,datagramPacket);
                    } catch (IOException e) {
                        e.printStackTrace();
                        break;
                    }
                }
            }
        });
        return rtcpUdpSocket.getLocalPort();
    }
    public static void handlerReceiveData(int type,DatagramPacket datagramPacket){
        if(type==0){
//            System.out.println("组播");
//            System.out.println("接收到参数:"+datagramPacket.getSocketAddress());
//            System.out.println(datagramPacket.getData());
//            System.out.println(datagramPacket.getLength());
        }else{
            System.out.println("rtcp");
//            System.out.println("接收到参数:"+datagramPacket.getSocketAddress());
//            System.out.println(datagramPacket.getData());
//            System.out.println(datagramPacket.getLength());
        }
    }
}
  • RTPAACServer.java
import java.io.IOException;
import java.io.RandomAccessFile;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.List;

public class RTPAACServer {
    private RandomAccessFile in;
    private List<ADTSHeader> ADTSIndexs = new ArrayList<>() ;//用来记录每个ADTS帧信息以及起始位置
    DatagramSocket rtpUdpSocket;//发送数据的socket
    InetAddress clientAddress;//目标地址
    int clientRtpPort;//目标端口
    public RTPAACServer(DatagramSocket socket, InetAddress address, int port) throws SocketException {
        rtpUdpSocket = socket;
        clientAddress = address;
        clientRtpPort = port;
    }
    public void close(){
        if(rtpUdpSocket!=null){
            rtpUdpSocket.close();
        }
    }

    public void startSendRtpPackage() throws Exception {
        String fileName = RTPAACServer.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();) {
            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();
                break;
            }
            ts_current+=timestamp_increse;
            seqNum ++;
            Thread.sleep(22);
            /*循环播放*/
            if(i==ADTSIndexs.size()-1){
                i=0;
            }else{
                i++;
            }
        }
    }
    /*
    * 一个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;
    }
    /**
     *  - 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服务端_java

VLC播放器




Java rtsp 保存到本地 java rtsp服务端_rtsp_02

打开URl播放




Java rtsp 保存到本地 java rtsp服务端_rtsp_03

结果