admin

部标 JT/T 1078 协议实时音视频传输视频服务器 RTP 包解析实现
前言第一次搞这种东西,真的是一脸懵逼。首先说下项目的需求:摄像头设备实现直播功能。这种需求很常见,但是公司做的是车...
扫描右侧二维码阅读全文
19
2018/07

部标 JT/T 1078 协议实时音视频传输视频服务器 RTP 包解析实现

前言

第一次搞这种东西,真的是一脸懵逼。
首先说下项目的需求:摄像头设备实现直播功能。
这种需求很常见,但是公司做的是车联网,也就是说这个摄像头设备是装在车上的(一般是公交车),而对于这种设备,国家就出了一套标准的通讯协议,所以和平常的那种摄像头直播实现肯定是不同的了。有关的资料也是少之又少,不过最后通过自己的摸索,勉强能够实现。

实现

首先,平台下发指令给设备那块就不做过多介绍了,按照部标上的协议能够轻松搞定。主要是设备收到视频直播指令后与我们的视频服务器进行连接实时传输视频的问题,有人可能就会说了,视频直播不就是推流吗,RTMP 协议啊,但是,我想说的是,麻麦皮的部标协议并不支持!在 JT/T 1078 协议中视频的传输协议负载包定义如下:
微信截图_20180719170421.png
乍一看,RTP 协议格式?文档上是这么说的:
微信截图_20180719170630.png
真是 RTP ?一开始,公司的人也都以为是 RTP,结果去找 RTP 流媒体服务器搭建,发现大多数都是 RTMP 流媒体服务器,就算有支持 RTP 的流媒体服务器,下发直播指令时,传给设备的参数只有视频服务器 IP 和 端口,那么不同设备的直播地址又是什么?仔细想一下,难道是要自己开发流媒体服务器?
后来经过几天的琢磨,终于有点眉目,上面的负载包格式并不是标准的 RTP 协议格式,再仔细一看上面的介绍:参考 RTP 协议格式!所以说,根本就不可能直接搭一个开源的 RTP 流媒体服务器来使用!
怎么办?那就只能自己开发咯。
首选框架当然是 netty 了,由于个人能力有限,像这种数据传输涉及到分包,UDP 对我来说还是太难了,需要对包进行重组,所以直接选了 TCP 协议。
使用 TCP 协议,一个常见的问题就是粘包问题,但根据上面的负载包格式,我们能够很容易的处理粘包问题:

package net.che_mi.message;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import net.che_mi.util.StringUtil;

import java.util.List;

/***
 * RTP 消息解码器
 *
 * @author 徐万利
 * @date 2018/7/16 0016 18:08
 */
public class RtpMessageDecoder extends ByteToMessageDecoder {

    //RTP 封包头部最大长度(可能某些字段没有,所以应该取最大的那个长度)
    private final int MIN_HEADER_LENGTH = 30;

    @Override
    protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf in, List<Object> list) throws Exception {
        if (in == null || in.readableBytes() <= MIN_HEADER_LENGTH)  //最坏打算,至少30个字节时才能读到数据体长度
            return;

        in.markReaderIndex();
        //跳过无关紧要的数据
        in.skipBytes(1);

        RtpMessage msg = new RtpMessage();
        //M(1 bit)、PT(7 bit) 共占用 1 个字节
        byte b = in.readByte();

        msg.setM((byte)((b >> 7) & 0x1));
        msg.setPT((byte)(b & 0x7f));

        msg.setSeq(in.readShort());
        byte[] simNum = new byte[6];
        in.readBytes(simNum);
        msg.setSimNum(StringUtil.convertByteToHexStringWithoutSpace(simNum));

        msg.setLogicChnnel(in.readByte());

        //数据类型(4 bit)、分包处理标记(4 bit)共占用一个字节
        b = in.readByte();

        msg.setDataType((byte) (b >> 4));
        msg.setFlag((byte) (b & 0x0f));

        if (msg.getDataType() != 4) {   //不为透传数据类型
            msg.setTimestamp(in.readLong());
        }

        if (msg.getDataType() != 3 && msg.getDataType() != 4) { //视频数据类型才有以下字段
            msg.setLIFI(in.readShort());
            msg.setLFI(in.readShort());
        }


        //数据体长度
        msg.setLength(in.readShort());

        if (in.readableBytes() < msg.getLength()) {
            in.resetReaderIndex();
            return;
        }
        //数据体
        byte[] body = new byte[msg.getLength()];
        in.readBytes(body);
        msg.setBody(body);
        list.add(msg);
    }
}

自定义一个Decoder,封装成一个RtpMessage,然后就能在Handler总直接使用了:

package net.che_mi.handler;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import net.che_mi.message.RtpMessage;
import net.che_mi.publish.PublishManager;
import net.che_mi.publish.PublishTask;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/***
 * RTP 封包处理类
 *
 * @author 徐万利
 * @date 2018/7/16 0016 18:08
 */
public class RtpPacketHandler extends SimpleChannelInboundHandler<RtpMessage> {

    private Logger logger = LoggerFactory.getLogger(RtpPacketHandler.class);


    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        super.exceptionCaught(ctx, cause);
        cause.printStackTrace();
    }

    @Override
    public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
        super.channelRegistered(ctx);
        logger.debug(ctx.channel().remoteAddress().toString());
    }

    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, RtpMessage rtpMessage) throws Exception {
        //logger.debug(rtpMessage.toString());

        if (rtpMessage.getDataType() != 3 && rtpMessage.getDataType() != 4) {
            String taskName = rtpMessage.getSimNum() + "_" + rtpMessage.getLogicChnnel();
            PublishTask task = PublishManager.get(taskName);
            if (task == null) {
                task = PublishManager.newPublishTask(taskName);
                task.start();
            }
            task.write(rtpMessage.getBody());
        }
    }
}

以上代码涉及到推流到 RTMP 服务器,因为我们目前只是介绍接收解析设备上传上来的音视频数据包,并不能进行直播,我的做法就是实时的将这些数据包进行转发推流至 RTMP 服务器进行直播,下一篇文章将进行介绍。
源码下载:rtpserver.zip
以上代码只是抛砖引玉,并不能直接用做生产环境。
PS:之前所说的卡顿是推流参数问题,也就是评论中grabber.getFrameRate的数字和recoder.setFrameRate一致

**已弃坑,目前有关1078视频服务器的开发网上的文章也比较多了,不像当时我搞的时候根本搜不到任何相关的文章,仅有的一篇就是 GPS产品经理 的那一篇,大家看其他博客发表时间就知道了,所以有什么问题还是多搜索吧,我这里也给不了过多的帮助了,关键在于多调试设备尝试各种方案**

Last modification:May 5th, 2019 at 10:46 am
If you think my article is useful to you, please feel free to appreciate

126 comments

  1. 小戴

    博主实现了音视频同时播放功能了吗?实现了的话能发我一份吗?

  2. H

    您好,recorder.record(frame);的时候一直提示无法访问avutil,请问您知道是什么原因导致的吗

    1. admin
      @H

      没遇到过

  3. edison

    你好!测试类能给个源码不?

  4. admin

    统一回复下音频流的问题吧。之前解析 RTP 负载包的时候发现音频流和视频流的时间戳一直都是递增的(好像没看到音频流和视频流相同的时间戳,也可能我记错了吧,但这个时间戳的影响应该不大)
    可以参照这篇文章来做:https://blog.csdn.net/eguid_1/article/details/52702385
    首先,联系设备厂商提供音频流编码参数,比如之前我待的那家公司:

    MDVR终端音频参数配置说明
    
    采样率:8K
    采样精度:16位
    采样点:320
    
    编码格式:
        AUDIO_CODEC_G711A,
        AUDIO_CODEC_G726_MEDIA_40K,
        AUDIO_CODEC_G726_MEDIA_32K,
        AUDIO_CODEC_G726_40K,
        AUDIO_CODEC_G726_32K,
    可以选以上几种编码格式。
    
    重要提示:
    1、由于采用海思芯片编码,每帧音频帧都会在标准音频帧前面增加4个字节的音频帧头信息,平台解码时需要去掉前面4个字节。
    2、G726编码格式含有多种压缩率格式,很多标准播放器的解码库只支持AUDIO_CODEC_G726_MEDIA_40K和AUDIO_CODEC_G726_MEDIA_32K两种编码格式,不支持AUDIO_CODEC_G726_40K和AUDIO_CODEC_G726_32K,虽然看上去压缩率一样,但是编码规则不一样,需要留意音频解码参数的配置。
    3、相对G726编码而言G711A编码则比较简单,调试时可以先选择这种编码格式。
    4、举例:采样率8K,采样精度16位,采样点:320
    原始音频数据码率:8K*16bit=128kpbs
    每秒音频帧数:8K/320=25帧
    比如选择AUDIO_CODEC_G726_40K编码,则每帧长度40Kbit/8bit/25=200字节长度,既每帧编码后的音频帧长度是200个字节,那么设备上传的就是204个字节,前面4个字节是海思音频编码私有帧头数据。

    采样率:8Krecorder.recordSamples()方法的第一个参数。
    采样精度:16位:参见上面那篇文章,也就是需要将 byte[] 音频流 转为 short[] 类型。
    采样点:320:这个我们厂商文档已经解释了,用来计算帧率的 8K / 320 = 25 帧,也就是每秒 25 帧。

    有了上面的信息,那么我们就可以进行推流了(音视频分开推流,两个线程):
    根据前面评论区有人说视频设置帧率后就不会出现快进现象了,所以视频还是前面的方法推流。
    但是音频流貌似没有这样的设置,所以做法就是上面那篇文章那样,使用ScheduledThreadPoolExecutor,前面我们知道了音频流是每秒 25 帧,那么我们每帧发送的时间间隔就是 1000 / 25 = 40ms。
    也就是exec.scheduleAtFixedRate(crabAudio, 0, (long) 1000 / FRAME_RATE,TimeUnit.MILLISECONDS);的第二个参数。
    但还有个问题,就是我们需要个音频流建个缓冲区,因为根据抓到的 RTP 负载包可以知道音频流没有声音的情况下是不会发送数据过来的,所以可能出现的情况就是有时候音频流每帧的间隔不是 40ms,因为可能出现其中没有音频流数据的情况,但是我们定时任务线程依然保证是 40ms 执行一次即可,收到的音频流可以放到类似 Netty 的 ByteBuf 里面,在定时任务里面每隔 40ms 我们调用 ByteBuf 的 readableBytes 方法判断是否有音频流数据即可再决定是否调用recorder.recordSamples进行推流。
    好了,目前已离职,所以有关该协议的内容以后不打算再做过多解释了,坑还是大家自己慢慢踩吧,上面的做法也只是我的理论猜想,因为之前公司没有采用 java 来搞了,所以也没做过实践。

    1. tom
      @admin

      没成功,有成功的吗?

      1. tom
        @tom

        if(!aac_sequence_header_flag){

                            aacHead[0] = -81;    //   0xaf;
                            aacHead[1] = 0x0;
                            aacHead[2] = 0x2 << 3 | 0xb >> 1; // 0x15 aacEnc: low; 0x4: 44100 0xb 8000
                            aacHead[3] = -112;//0x90
                            if (recorder != null){
                                int nBytesRead = 4;
                                System.out.println("执行。。。"+nBytesRead);
                                System.arraycopy(aacHead,0,audioBytes,0,nBytesRead);
                                // 因为我们设置的是16位音频格式,所以需要将byte[]转成short[]
                                int nSamplesRead = nBytesRead / 2;
                                short[] samples = new short[nSamplesRead];
                                /**
                                 * ByteBuffer.wrap(audioBytes)-将byte[]数组包装到缓冲区
                                 * ByteBuffer.order(ByteOrder)-按little-endian修改字节顺序,解码器定义的
                                 * ByteBuffer.asShortBuffer()-创建一个新的short[]缓冲区
                                 * ShortBuffer.get(samples)-将缓冲区里short数据传输到short[]
                                 */
                                ByteBuffer.wrap(audioBytes).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer().get(samples);
                                // 将short[]包装到ShortBuffer
                                ShortBuffer sBuff = ShortBuffer.wrap(samples, 0, nSamplesRead);
                                // 按通道录制shortBuffer
        
                                recorder.recordSamples(sampleRate, numChannels, sBuff);
                                aac_sequence_header_flag = true;
                            }
                            return;
                        }
                        if(recorder != null){
                            // 非阻塞方式读取
                            if(bread)
                            {
                                bread = false;
                                int nBytesRead = bosAudio.toByteArray().length;
                                System.out.println("执行。。。"+nBytesRead);
                                System.arraycopy(bosAudio.toByteArray(),0,audioBytes,0,nBytesRead);
                                bosAudio.reset();
                                // 因为我们设置的是16位音频格式,所以需要将byte[]转成short[]
                                int nSamplesRead = nBytesRead / 2;
                                short[] samples = new short[nSamplesRead];
                                /**
                                 * ByteBuffer.wrap(audioBytes)-将byte[]数组包装到缓冲区
                                 * ByteBuffer.order(ByteOrder)-按little-endian修改字节顺序,解码器定义的
                                 * ByteBuffer.asShortBuffer()-创建一个新的short[]缓冲区
                                 * ShortBuffer.get(samples)-将缓冲区里short数据传输到short[]
                                 */
                                ByteBuffer.wrap(audioBytes).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer().get(samples);
                                // 将short[]包装到ShortBuffer
                                ShortBuffer sBuff = ShortBuffer.wrap(samples, 0, nSamplesRead);
                                // 按通道录制shortBuffer
        
                                recorder.recordSamples(sampleRate, numChannels, sBuff);
                            }
                        }

        先发送头,再发送转成AAC的byte,只能偶尔听到一点噪音

        1. admin
          @tom

          上面代码没看到你转 AAC 呀。转了的话你就直接把 byte[] 以追加的形式写到文件,然后播放这个文件看声音对不对,声音不对的话就是转码问题而不是推流的问题。

          1. tom
            @admin

            bosAudio里面是已经转成AAC的字节了,因为这部分代码是原来的场景是从摄像头来的数据,应该是PCM格式的吧,这儿是不是不应该传AAC,而应该是中间格式PCM

            1. admin
              @tom

              不是吧 1078 协议音频传输好像没有直接传 pcm 的吧,你得问下厂商设备那边的音频格式。比如我之前的公司时 G726_32K,所以需要做的转换是 G726_32K -> PCM -> AAC

              1. joxhome
                @admin

                recorder.recordSamples 里面最后一个传输参数不是你这样的,需要再次封装下

              2. tom
                @admin

                我们的是G711A,G711A->PCM->AAC,这都没有问题并且数据都是对的,保存成文件播放都没有问题,我的问题是AAC的格式给这样的推送没有声音,因为我看这个推送的场景是推笔记的录音设备的,应该是PCM格式的吧,我是想是不是应该传PCM,可传PCM也没有声音,真奇怪了

                1. admin
                  @tom

                  别用javacv了 找下看有没有其他的推流工具。

                  1. fox
                    @admin

                    live555,这个都给实现了,好用,没的说

                  2. tom
                    @admin

                    用两个grabber两个管道流,一个视频,一个音频,同一个recorder推音频和视频,这样可以有声音,但是两个管道流会打架造成死锁,程序没有响应了,不知这个思路有问题不

                    1. admin
                      @tom

                      音频是没必要用 grabber 了的吧,你可以参考下这篇文章:https://blog.csdn.net/eguid_1/article/details/52804246 用的就是同一个 recoder 。

                      1. tom
                        @admin

                        用两个grabber两个管道流,一个视频,一个音频,同一个recorder推音频和视频,声音正常了,现在考虑同步的问题,不知有啥好的建议不。

                      2. rickhuan
                        @admin

                        博主你好,能给个联系方式吗,我们想找这方面的人才,或者给个提供外协的方式,价格好谈

                        1. admin
                          @rickhuan

                          微信:xwlcnsy

  5. 花钱少

    终于可以正常播放了,只是偶尔会卡顿一下,总之不影响使用

  6. 仁哥

    我测试了代码,感觉是ffmpeg收流 推流那边每秒的帧数不一致导致的,grabber.getFrameRate的数字和recoder.setFrameRate一致后,播放就没有卡顿了

    1. tom
      @仁哥

      你们的有声音吗,这样的数据转到rtmp,再拉下来,不带声音的吧

      1. tom
        @tom

        看你的意思是音频和视频分开推,能一起推吗?另外音视频推时,先计算出前后帧的时间差值,等过了这个时间差值再推送?

      2. admin
        @tom

        上面的代码只推流了视频流,实际上音频流也是需要推流的,推流的时候需要传入时间参数,设备好像是20ms一帧,可向厂商询问。

        1. tom
          @admin

          不好意思,是个新手,怎么在推时加时间参数,recorder.record(frame);只有一个参数

        2. tom
          @admin

          看你的意思是音频和视频分开推,能一起推吗?另外音视频推时,先计算出前后帧的时间差值,等过了这个时间差值再推送?

        3. 仁哥
          @admin

          博主能给个推音频代码的参考么,我这边推音频卡了一个多月了...

          1. admin
            @仁哥

            写个伪代码给你参考下吧:

            int tick = 0;
            int tickInterval = 40;             //时间戳增量,实际上从设备上传的 RTP 负载包里面的 时间戳 可以知道每两个完整的 RTP 之间的时间间隔是 40ms (我这边音频流对应是两帧[因为根据音频流参数知道每帧 20ms])
            
            //tickInterval 也可以自己动态计算,也就是 RTP 最后一个包的时间戳 - 上一个 RTP 最后一个包的时间戳
            
            //每次发送前叠加增量
            tick += tickInterval;
            sendAAC2Rtmp(aacData, tick);
            1. admin
              @admin

              实际上不仅音频流如此,视频流也是一样,不然就会出现我一开始直接推流那种情况:视频播放速度过快导致卡顿。

              1. 仁哥
                @admin

                同时推音视频,音频是不是要重新开一个线程,然后用recoder.recoderSamples这个方法推呢

                1. admin
                  @仁哥

                  另外需要注意的是接收到的音频流格式是不能直接推送的,我们这边是转成 AAC 音频格式再推的流。编码转换需要先查看设备的音频流格式是什么,然后再找个开源的编码转换工具进行转换吧。

                2. admin
                  @仁哥

                  不需要,我们最后不是用java搞的。

                  1. 仁哥
                    @admin

                    那我可以理解成 博主的rtpserver这个工程里面 如果RtpMessage的dataType=3 的话 ,就要把byte[]转成aac 是嘛OωO

                    1. admin
                      @仁哥

                      是的,但发送aac之前需要先发送一个aac头过去(只要刚开始的时候发送一次,后面直接推送aac音频流)

                      1. 仁哥
                        @admin

                        那就是第一次发送下aac的头,然后把g711的byte[]转为 aac的byte,推流自始至终只需要recoder.record(frame),音视频都是这个方法推

  7. 王者讨饭

    grabber.start(); 这个地方会停顿很长时间 有什么办法可以处理

    1. admin
      @王者讨饭

      这个方法在没有视频流的时候是阻塞的,总之不建议用 javacv 来推流。

      1. 花钱少
        @admin

        不用javacv,那用什么方式推流呢

      2. 花钱少
        @admin

        博主,代码有更新吗,可以发一套新的来吗 57485181@qq.com

        1. admin
          @花钱少

          没有。

  8. 追梦少年

    楼主,麻烦加下我的微信:17158638841,我也在搞这个,想交流一下

    1. admin
      @追梦少年

      准备离职不搞了

      1. LeoHu
        @admin

        这么快就放弃了

        1. admin
          @LeoHu

          公司待遇太差。

  9. lxc

    老哥,你这边能支持多少路的视频

  10. LeoHu

    我这边能够用c++解析出来1078的实时视频数据,并写入文件,但是写入文件的内容再用vlc播放时会隔段时间产生卡顿效果,分析了下应该不是丢包的问题。看了你们上面的讨论,请问博主,是需要对H264内部进行重新解析吗?然后把1078协议中的数据类型、时间间隔关联起来进行处理?可如果是这样,处理逻辑又是怎么样的呢?博主在这方面有经验吗?

    1. LeoHu
      @LeoHu

      另外,写入的H264裸流文件,用vlc播放时会有一种快进的感觉,这个又是什么原因,博主有这方面经验或者方向吗?

      1. LeoHu
        @LeoHu

        谢谢博主,目前这块在播放低分辨率的时候没有问题。仔细分析了设备上传的数据,在高分辨率,尤其是网络环境不好时,会出现3s的丢帧,这个目前也没好办法解决。但设备丢帧3s,vlc播放器播放时丢了10多秒,可能还需要继续深入分析分析。

        1. admin
          @LeoHu

          播放裸流快进问题已经解决了吗?如果没有你可以参考下H264推流的方法:
          协议上RTP负载包中只说了视频数据类型字段有I帧、P帧、B帧,实际上我们可以根据NALU来判断,在收到I帧的时候我们是需要先发送一次PPS和SPS的,PPS和SPS应该只在建立连接的时候设备会发送一次(具体会不会多次发送没去分析),PPS和SPS帧也相当于一个独立的RTP负载包(这一点协议上没有说明,协议上各种坑大家慢慢踩吧~),所以协议中那个数据类型只用来判断是音频和视频帧就可以了,判断I帧、P帧、B帧、PPS、SPS还是通过NALU来判断吧,判断方法:
          int naluType = frame[4] & 0x1f;
          naluType 取值:7(SPS)、8(PPS)、5(I帧)、1(B帧或者P帧)
          可以参考这篇文章:https://blog.csdn.net/dittychen/article/details/55509718
          前面说了收到I帧的时候需要先发送一次PPS和SPS(注意是每次都要发送,不管SPS和PPS会不会多次收到,所以SPS和PPS需要缓存起来),收到B帧和P帧直接推流即可(前提是前面至少发送过一次SPS和PPS,包的顺序可以这么认为:...PPS->SPS->I帧->B帧或者P帧->PPS->SPS->I帧->B帧或者P帧...),这样就可以了。

          1. LeoHu
            @admin

            @博主,目前播放裸流文件快进的问题我还没有跟进。实时播放采用的live555的框架,分辨率低的情况下没有问题。对于裸流问题的播放问题,暂时还没有深入研究。目前在考虑音频接入的问题,后续有时间再深入研究下裸流播放快进问题

      2. admin
        @LeoHu

        H264裸流播放是这个样子的。

      3. lxc
        @LeoHu

        可能丢包了

        1. LeoHu
          @lxc

          lxc,是的,设备有丢帧,因为不是做设备的,也没啥办法。

    2. LeoHu
      @LeoHu

      感觉这种卡顿像一种快进效果,目前还不知道原因是什么。

  11. 王者讨饭

    推流到rtmp不成功 又没报错

  12. 王者讨饭

    能够解出来播放不了 问下加上SIM卡跟通道号 URL地址是那种形式的 rtmp://127.0.0.1:1935/live/room/sim_1 是这样的吗

    1. admin
      @王者讨饭

      rtmp://127.0.0.1:1935/live/room/sim_1 没有 room 具体是什么抓下nginx日志就知道了,日志中也可以看到是否启动推流了。

      1. 王者讨饭
        @admin

        是用的ffmpeg 是否需要在本地安装这个插件调用它执行 然后推流

      2. 王者讨饭
        @admin

        推流不到rtmp里面, recorder = new FFmpegFrameRecorder("rtmp://172.16.0.252:1935/live/"+name, 352,288,0); 不成功 没报错

  13. 天堂

    我的接收到设备数据后,视频界面一直卡着不动,是为什么的?

    1. admin
      @天堂

      应该不是一直卡着不动,视频会出现卡顿,我这边测试也是一样的,netty 接收设备数据的网速我发现一直是 100 kb 以内(去掉RTP包的解析,就只接收数据都是这个网速),所以导致了卡顿,但是用 C# 写的视频转发服务器接收设备数据的网速可以达到 150kb 左右,可能是 netty 对数据接收速率有什么限制,具体原因我也不清楚,不知道 netty 该怎么优化参数。另外如果网速正常的话用 javacv 直接推流视频播放的时候回像快进一样,所以不能直接将 H264 裸流用 javacv 推流。

      1. 天堂
        @admin

        我发现不是卡顿,就不动了。小半天才动一下。还有FFmpegFrameRecorder里面的参数是不是不一样,应该配置成设备传过来的。但是设备传过来的没有这些参数说明

  14. lxc

    grabber = new FFmpegFrameGrabber(pis);
    grabber.start();
    之前都可以正常推流,隔了一段时间不用又在start()这卡住了,我断点看里面也有数据,有什么解决办法吗

    1. 花钱少
      @lxc

      有完整的代码参考吗

    2. admin
      @lxc

      不要用 javacv 推流了,解析完 RTP 中的 H264 裸流不能直接这样子推过去的,音频也需要进行处理然后推流,总之很麻烦,现在我也在对讲那块卡住了,涉及到的音视频编码知识太多了,java 不好处理,这种东西还是用C或者C++来做吧。

      1. benjamin
        @admin

        请问博主的对讲现在是怎么实现的?

      2. 王者讨饭
        @admin

        grabber = new FFmpegFrameGrabber(pis); 不能这样推流吗

  15. 郭小坏

    1078负载包数据体里的是什么东西?怎么进行组包呢?求大佬指教

    1. admin
      @郭小坏

      数据包可以是音频、视频或者透传数据,具体参见前面的PT字段值(指定了数据体的编码格式)以及数据类型字段。负载包的解析不难,关键在于对解析数据体的编解码问题。

  16. 杨杰

    测试类的源码能发一下吗,我这边也是想把流发送大rtmp流媒体服务器。谢谢大佬

  17. xxgycf

    博主你好,我最近也开发1078视频协议,我主要想了解一下自定义的rtp服务器,是不是我需要开发一个能够支持部标的rtp服务器,然后客户端通过我自定义开发的服务器推流 实现实时播放呢? 主要想理清一下架构,希望博主指导一下

    1. admin
      @xxgycf

      设备->RTP视频服务器(需要自己写,主要就是解析实时音视频负载包)->推流到流媒体服务器->播放

      1. xxgycf
        @admin

        感谢楼主回复!这一步已经理清楚了,还想问一下1. 视频协议是i b p分帧上传,我自己如何合成?2.开源流媒体服务器是直接把流推过去,配好播放地址 客户端(web Android iso)根据配置的地址播放吗?

        1. admin
          @xxgycf

          H264 推流至 RTMP 服务器自行百度搜索吧,推流地址与播放地址一致,URL中唯一用 sim 加 channel 通道号可以确定,使用第三方流媒体服务器也是差不多,比如腾讯云只是推流地址可能与播放地址是单独分开的。最后就是不建议用 Java 来写,上面代码测试效果不是很好,是用的 JavaCV 来推流的(没做 I 帧 P帧 B 帧的处理,结果就是播放速度偏快[可能卡顿也是这个原因导致的]),现公司已买了第三方代码,具体做法是将收到的 H264 帧放进队列,然后取出的时候判断是 sps、pps、I帧还是P帧,取出是sps、pps帧的时候先缓存起来,待到取出 I 帧时先将前面缓存的 sps、pps发送出去,然后再将I帧发送出去,P 帧直接发送,具体代码公司保密不方便发出。

          1. xxgycf
            @admin

            1078协议中 只有i b p没有sps和pps啊!还是说我们要自己根据h264协议去判断呢

          2. xxgycf
            @admin

            我就是做做前期研究,公司可能也要去买,这一块感觉需要的知识量太大了,而且我们是c#,开源的例子更少,还是要谢谢楼主 !帮我梳理架构 ,后期有问题继续交流。

            1. admin
              @xxgycf

              嗯,需要自己根据每个H264包的前几个字节去判断,我们买的源码就是C#的,思路和我的一样,但这种中转推流的办法性能是个问题,每个直播都开一个线程去推流至RTMP服务器。

              1. xxgycf
                @admin

                思路基本上理顺了,楼主有没有考虑用hls协议,我开始打算用rtsp但是web端不好弄,现在设备回来了 准备开始弄了。楼主可以给一个你们买的源码的联系吗?或者给一个楼主的即时联系方式?

        2. xxgycf
          @xxgycf

          楼主的源码就是第二部分吗?

  18. lxc

    老哥有没有试过在linux下运行这个项目呢

    1. admin
      @lxc

      没有,linux下应该要将ffmpeg的jar包换成linux的。

      1. lxc
        @admin

        linux运行报java.lang.ClassNotFoundException: io.netty.channel.EventLoopGroup这个错

        1. lxc
          @lxc

          这个问题解决了,原来我导出jar文件没有把jar包都到出去

  19. lxc

    老哥,我这里还有个问题咨询一下,我这边项目测试同一台设备的两个摄像头的时候先发送第一个摄像头的数据,再发送第二个摄像头的数据,我在网页放了两个窗口,只有一个窗口在播放第二个摄像头的画面,时不时卡到第一个摄像头的画面去.过一会项目就报错了,对于多个设备数据的分别老哥有什么好的建议吗

    A fatal error has been detected by the Java Runtime Environment:EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc=0x000007fed7e72904, pid=11568, tid=0x0000000000002be8JRE version: Java(TM) SE Runtime Environment (8.0_172-b11) (build 1.8.0_172-b11)Java VM: Java HotSpot(TM) 64-Bit Server VM (25.172-b11 mixed mode windows-amd64 compressed oops)Problematic frame:C [swscale-4.dll+0x62904]RtpMessage{M=0, PT=98, seq=4546, simNum='014440314563', logicChnnel=1, dataType=0, flag=3, timestamp=25000, LIFI=0, LFI=40, length=950}Failed to write core dump. Minidumps are not enabled by default on client versions of WindowsAn error report file with more information is saved as:C:UsersAdministratorDesktopdiyireed5rtpserverhs_err_pid11568.log

    RtpMessage{M=0, PT=98, seq=4547, simNum='014440314563', logicChnnel=1, dataType=0, flag=3, timestamp=25000, LIFI=0, LFI=40, length=950}
    RtpMessage{M=1, PT=6, seq=211, simNum='014440314563', logicChnnel=2, dataType=3, flag=0, timestamp=1157523, LIFI=0, LFI=0, length=160}
    RtpMessage{M=0, PT=98, seq=4548, simNum='014440314563', logicChnnel=1, dataType=0, flag=3, timestamp=25000, LIFI=0, LFI=40, length=950}
    574e80] concealing 1551 DC, 1551 AC, 1551 MV errors in P frame
    [h264 @ 000000001e574e80] mb_type 30 in P slice too large at 37 0
    [h264 @ 000000001e574e80] error while decoding MB 37 0
    [h264 @ 000000001e574e80] concealing 1584 DC, 1584 AC, 1584 MV errors in P frame
    [h264 @ 000000001e574e80] P sub_mb_type 14 out of range at 21 3
    [h264 @ 000000001e574e80] error while decoding MB 21 3
    [h264 @ 000000001e574e80] concealing 1480 DC, 1480 AC, 1480 MV errors in P frame
    [h264 @ 000000001e574e80] P sub_mb_type 13 out of range at 43 0
    [h264 @ 000000001e574e80] error while decoding MB 43 0
    [h264 @ 000000001e574e80] concealing 1584 DC, 1584 AC, 1584 MV errors in P frame
    [h264 @ 000000001e574e80] mb_type 31 in P slice too large at 1 2
    [h264 @ 000000001e574e80] error while decoding MB 1 2
    [h264 @ 000000001e574e80] concealing 1544 DC, 1544 AC, 1544 MV errors in P frame
    [h264 @ 000000001e574e80] P sub_mb_type 5 out of range at 32 2
    [h264 @ 000000001e574e80] error while decoding MB 32 2
    [h264 @ 000000001e574e80] concealing 1513 DC, 1513 AC, 1513 MV errors in P frame
    [h264 @ 000000001e574e80] P sub_mb_type 28 out of range at 17 4
    [h264 @ 000000001e574e80] error while decoding MB 17 4
    [h264 @ 000000001e574e80] concealing 1440 DC, 1440 AC, 1440 MV errors in P frame
    [h264 @ 000000001e574e80] mb_type 30 in P slice too large at 39 2
    [h264 @ 000000001e574e80] error while decoding MB 39 2
    [h264 @ 000000001e574e80] concealing 1506 DC, 1506 AC, 1506 MV errors in P frame
    [h264 @ 000000001e574e80] out of range intra chroma pred mode
    [h264 @ 000000001e574e80] error while decoding MB 9 2
    [h264 @ 000000001e574e80] concealing 1536 DC, 1536 AC, 1536 MV errors in P frame
    [h264 @ 000000001e574e80] top block unavailable for requested intra mode
    [h264 @ 000000001e574e80] error while decoding MB 43 0
    [h264 @ 000000001e574e80] concealing 1584 DC, 1584 AC, 1584 MV errors in P frame
    [h264 @ 000000001e574e80] Not enough data for an intra PCM block.
    [h264 @ 000000001e574e80] error while decoding MB 6 1
    [h264 @ 000000001e574e80] concealing 396 DC, 396 AC, 396 MV errors in I frame
    [h264 @ 000000001e574e80] mb_type 41 in P slice too large at 13 7
    [h264 @ 000000001e574e80] error while decoding MB 13 7
    [h264 @ 000000001e574e80] concealing 278 DC, 278 AC, 278 MV errors in P frame
    [h264 @ 000000001e574e80] negative number of zero coeffs at 8 9
    [h264 @ 000000001e574e80] error while decoding MB 8 9
    [h264 @ 000000001e574e80] concealing 239 DC, 239 AC, 239 MV errors in P frame
    [h264 @ 000000001e574e80] P sub_mb_type 6 out of range at 7 3
    [h264 @ 000000001e574e80] error while decoding MB 7 3
    [h264 @ 000000001e574e80] concealing 372 DC, 372 AC, 372 MV errors in P frame
    [h264 @ 000000001e574e80] out of range intra chroma pred mode
    [h264 @ 000000001e574e80] error while decoding MB 10 5
    [h264 @ 000000001e574e80] concealing 325 DC, 325 AC, 325 MV errors in P frame
    [h264 @ 000000001e574e80] P sub_mb_type 7 out of range at 11 6
    [h264 @ 000000001e574e80] error while decoding MB 11 6
    [h264 @ 000000001e574e80] concealing 302 DC, 302 AC, 302 MV errors in P frame
    [h264 @ 000000001e574e80] out of range intra chroma pred mode
    [h264 @ 000000001e574e80] error while decoding MB 32 3
    [h264 @ 000000001e574e80] concealing 1469 DC, 1469 AC, 1469 MV errors in I frame
    RtpMessage{M=0, PT=98, seq=212, simNum='014440314563', logicChnnel=2, dataType=1, flag=1, timestamp=1320, LIFI=320, LFI=40, length=950}
    RtpMessage{M=1, PT=98, seq=213, simNum='014440314563', logicChnnel=2, dataType=1, flag=2, timestamp=1320, LIFI=320, LFI=40, length=482}
    RtpMessage{M=1, PT=6, seq=214, simNum='014440314563', logicChnnel=2, dataType=3, flag=0, timestamp=1157543, LIFI=0, LFI=0, length=160}

    If you would like to submit a bug report, please visit:http://bugreport.java.com/bugreport/crash.jspThe crash happened outside the Java Virtual Machine in native code.See problematic frame for where to report the bug.
    1. 邓工
      @lxc

      哥们,人才啊。有没有 兴趣搞车载管理平台。一起干一番事业。联系微信13510671870 我们开源平台网站WWW.car-eye.cn 产品地址www.liveoss.com www.liveoss:8088

    2. admin
      @lxc

      RtpPacketHandler.java 52行开始改成下面的(用卡号和视频通道号来确定唯一的直播推流任务):
      if (rtpMessage.getDataType() != 3 && rtpMessage.getDataType() != 4) {

              String taskName = rtpMessage.getSimNum() + "_" + rtpMessage.getLogicChnnel();
              PushTask task = PushManager.get(taskName);
              if (task == null) {
                  task = PushManager.newPublishTask(taskName, taskName);
                  task.start();
              }
              task.write(rtpMessage.getBody());
              if (rtpMessage.getFlag() == 0 || rtpMessage.getFlag() == 2) {
                  task.flush();
              }
          }
      1. lxc
        @admin

        channelUnregistered()方法里面的
        PushTask task = PushManager.get(ctx.name());

            if (task != null) {
                try {
                    task.shutdown();
                } catch (Exception e) {
                }
                PushManager.remove(ctx.name());
            }

        没用的吗

        1. admin
          @lxc

          RtpPacketHandler.java 修改如下:

          @Override
          public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
              super.channelUnregistered(ctx);
              PushTask task = PushManager.get(ctx);
              if (task != null) {
                  try {
                      task.shutdown();
                  } catch (Exception e) {
                  }
                  PushManager.remove(ctx);
              }
          
          }
          
          @Override
          protected void channelRead0(ChannelHandlerContext ctx, RtpMessage rtpMessage) throws Exception {
              //logger.debug(rtpMessage.toString());
              System.out.println(rtpMessage);
          
              if (rtpMessage.getDataType() != 3 && rtpMessage.getDataType() != 4) {
                  String taskName = rtpMessage.getSimNum() + "_" + rtpMessage.getLogicChnnel();
                  PushTask task = PushManager.get(ctx);
                  if (task == null) {
                      task = PushManager.newPublishTask(ctx, taskName);
                      task.start();
                  }
                  task.write(rtpMessage.getBody());
                  if (rtpMessage.getFlag() == 0 || rtpMessage.getFlag() == 2) {
                      task.flush();
                  }
              }
          }

          PushManager.java 做如下修改:

          public class PushManager {
          
              public static Map<ChannelHandlerContext, PushTask> tasks = new HashMap<>();
          
              public static PushTask newPublishTask(ChannelHandlerContext ctx, String name) {
                  try {
                      PushTask task = new PushTask(name);
                      tasks.put(ctx, task);
                      return task;
                  } catch (Exception e) {
                      e.printStackTrace();
                  }
                  return null;
              }
          
              public static PushTask get(ChannelHandlerContext ctx) {
                  return tasks.get(ctx);
              }
          
              public static void remove(ChannelHandlerContext ctx) {
                  tasks.remove(ctx);
              }
          }
    3. lxc
      @lxc

      我刚看了一下,我不同的摄像头进来存放pushtask的map集合的key值都是一样的,这个要怎么解决

      1. lxc
        @lxc

        就是ctx.name()都是一个值

  20. lxc

    您好,我将pushtask和pushmanager移植到我的项目当中,pushtask中的run方法运行到grabber.start();就停住了是为什么

    1. 花钱少
      @lxc

      大佬你好,请问grabber.start()这地方卡住不执行了,怎么解决的呢

    2. admin
      @lxc

      grabber.start();需要管道流里面有数据才会继续执行。注意调用pos.flush();来刷新写入数据。

      1. lxc
        @admin

        因为线程开始的时候task.flush();还没有执行,pos里面还没有数据,代码就会在start的时候卡住,所以我把代码放在一个方法里面,在task.flush()后调用这个方法,这时候pos的buffer里面有数据,但是还是在start的地方卡住了,执行不到下一步.(把grabber.start();那一块的代码都写进一个方法里面)

        1. tom
          @lxc

          最后 怎么解决的?

    3. lxc
      @lxc

      我走进start()里面是在pkt2.size(0);的地方卡住的

  21. lxc

    我自己写了netty的项目可以解析出数据体,保存为.264文件可以用vlc播放,我是否可以将您代码里的pusktask和pushManage移植到我的项目中发送数据到rtmp服务器

    1. bridge
      @lxc

      你好!请教一下1078读视频的功能,方便加下QQ吗?我是1781168046 没接触过,希望能够得到你指点指点。

    2. duan
      @lxc

      我现在要通过1078协议取音视频的地址回来,进行播放。以前没做过,只有809的代码,netty的。不知道是不是还得自己写关于1078的代码呢?

    3. duan
      @lxc

      你好

  22. lxc

    303163648162002a014440314563010300000000000000000000000003b6082d45a0fa2fbebae5a483502fd01029fc1ea2dbc528fff9783677650998bbdf7fe7958d68393ef3265f97a7ffd573b0cf81d8c46d55bc55b4bde022e88c112d50a4537fdba65dee9e64fdacd66f93e3b408998fc6673dffb4e5901261de9fef43183bd4e418a7cfa04c219fe74013c127785504517f600278a60f9194cbf1815c11217afcfa56cce0ffa4d7094bb39e55c4f697a00b8978d043fd76cda4509152e7ab1ac4d51aa23659c65fef4f8fdb1471f1ef96d832c5cebd36fba22ebfbe041f942724e5fd0f4793be314f7ef6cbc9fe17fe7848a5f390d8339063ac5a4ce41efd7fc61088c0f02d6ae7cace7ff760f391d833909c83419c23ac3f0508573f723d333e6e41e0406d36ae19465ab288a43a607a68e37aa3cecbfddcfdf9edf43f6fd3faffea1a18dd50d24f23f380a7372d83025625477eae002c447906fecd405d78d50df43a9a7f7e4db632cbe3dc8291557857aff0670d2b11996653983b91465a1ef22b6022687f8dc642009a70c8a9fb8f1340fb5d97bcd13405e6157439250965e998141ce6b6265edd3d2a76ebaaff36769406fb50cda60a276d828eae46fd019a779ef4e7d849e3bfd4cad4d8c74cd48a61de2ff6e4603c175a9d9c69c8fed0346856515912be323ff1efb83e6e335a0f50960b8eb15fce66dbd55d843f275f085143414798c76ce67b0984c2cc62b5f16b05ad6e8663cb01b0b882512ac5853b8315afa4c0388890e253bc86e20fe7126dc4e2db253fb6cedbcbf2882690d6f5ed8737237f0317b2e83779f96d83e72147615c08a9e277617616638c2eb6cc03e58184e279c835e5c635ee0713ae3b04d66f7a5615084cc5e99aa658bb98c66f8570ad07615bb0bfe2cf0fa8e3d7b077da67afefc3c3a69ce46b39058cfb069bc91c8abf583a0ce43dbe9f9a56396bbd7573906f9b7f1ff8234e41f390958a0dc4148ca3ed4761e412abfb61fdb1917235966c469258e7a68b65534c2d87e21ba1f816b74735f129c21b1421295a53128acbc32ded74c0704ecc04041bbe8718ac0a08682243c37ef49fb57ba0a0e6b84d2a221bb84decd4bf6fcfda3c301ac6d396318fba2a8a8187031c4d41842dc7424ffd677f9be5e2170ed8ec41df5e720bf39059fef073ac353107b07cc4160c3ec941bc83ab8fb1381fcae20183e36b0ee5937e3cb9af8ebffad6b54fdae8a3c3c139326cee5c76f2fb6ff875306c1e7ebf5afafd5a1854199515d8b6c4db766c504c69ead3fc834c649c3ac7240f4a429c15ff80e0d469ecfb6bae7455a9d6acf194f5323e46ec9dbbcbbe3df46bb7cbc100b186402e2910398054e717d985这个是我前天抓包的

    1. admin
      @lxc

      相应的 private final int MIN_HEADER_LENGTH = 30; 改为 private final int MIN_HEADER_LENGTH = 35;

    2. admin
      @lxc

      你们的设备比较标准,完全符合1078协议里面的负载包格式,我这边设备RTP负载包开头没有帧头标识、V、P、X、CC,所以你如果用我的代码的话需要将 RtpMessageDecoder.java 里面 28 行改为 in.skipBytes(6);

      1. lxc
        @admin

        我按照你说的改了一下,还是不行,同样的报错信息

        1. admin
          @lxc

          说错了,是skipBytes(5), MIN_HEADER_LENGTH = 34

          1. lxc
            @admin

            可以正确解析了,我把rtmp服务器开启,报了这个错,您这个项目对jdk版本有要求吗

            A fatal error has been detected by the Java Runtime Environment:EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc=0x000007fedbf64712, pid=13084, tid=0x0000000000002c88JRE version: Java(TM) SE Runtime Environment (8.0_172-b11) (build 1.8.0_172-b11)Java VM: Java HotSpot(TM) 64-Bit Server VM (25.172-b11 mixed mode windows-amd64 compressed oops)Problematic frame:C [swscale-4.dll+0x4712]Failed to write core dump. Minidumps are not enabled by default on client versions of WindowsAn error report file with more information is saved as:C:UsersAdministratorDesktopdiyireed5rtpserverhs_err_pid13084.logIf you would like to submit a bug report, please visit:http://bugreport.java.com/bugreport/crash.jspThe crash happened outside the Java Virtual Machine in native code.See problematic frame for where to report the bug.

            Input #0, h264, from 'java.io.BufferedInputStream@4c87dc08':
            Duration: N/A, bitrate: N/A

            Stream #0:0: Video: h264 (Baseline), yuv420p(progressive), 704x576, 25 fps, 25 tbr, 1200k tbn, 50 tbc

            [libx264 @ 000000005e20e020] using cpu capabilities: MMX2 SSE2Fast SSSE3 SSE4.2 AVX FMA3 BMI2 AVX2
            [libx264 @ 000000005e20e020] profile High, level 1.3
            [libx264 @ 000000005e20e020] 264 - core 152 - H.264/MPEG-4 AVC codec - Copyleft 2003-2017 - http://www.videolan.org/x264.html - options: cabac=1 ref=3 deblock=1:0:0 analyse=0x3:0x113 me=hex subme=7 psy=1 psy_rd=1.00:0.00 mixed_ref=1 me_range=16 chroma_me=1 trellis=1 8x8dct=1 cqm=0 deadzone=21,11 fast_pskip=1 chroma_qp_offset=-2 threads=6 lookahead_threads=1 sliced_threads=0 nr=0 decimate=1 interlaced=0 bluray_compat=0 constrained_intra=0 bframes=3 b_pyramid=2 b_adapt=1 b_bias=0 direct=1 weightb=1 open_gop=0 weightp=2 keyint=250 keyint_min=25 scenecut=40 intra_refresh=0 rc_lookahead=40 rc=abr mbtree=1 bitrate=400 ratetol=1.0 qcomp=0.60 qpmin=0 qpmax=69 qpstep=4 ip_ratio=1.40 aq=1:1.00
            Output #0, flv, to 'rtmp://192.168.3.185/live/014440314563_1':
            Metadata:

            encoder         : Lavf57.83.100
            Stream #0:0: Video: h264 (Constrained Baseline) ([7][0][0][0] / 0x0007), yuv420p, 352x288, q=2-31, 400 kb/s, 1k tbn
            1. admin
              @lxc

              删掉pom.xml中的FFmpeg-windows-x86依赖clean项目重试一下。

              1. lxc
                @admin

                好了,谢谢,我这边已经可以用vlc播放了,万分感谢

                1. duan
                  @lxc

                  我要做的跟你这功能一样,就是在1078获取视频进行播放。不知道咋弄。可以指点指点吗 qq:1781168046

  23. lxc

    8加上)就变成得意的表情了

  24. lxc

    我这边用你的系统解析数据报错了An exceptionCaught() event was fired, and it reached at the tail of the pipeline. It usually means the last handler in the pipeline did not handle the exception.
    io.netty.handler.codec.DecoderException: java.lang.NegativeArraySizeException

    at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:459)
    at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:265)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:34icon_cool.gif
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340)
    at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1434)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:34icon_cool.gif
    at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:965)
    at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:163)
    at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:646)
    at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:581)
    at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:49icon_cool.gif
    at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:460)
    at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:884)
    at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
    at java.lang.Thread.run(Thread.java:74icon_cool.gif

    Caused by: java.lang.NegativeArraySizeException

    at net.che_mi.message.RtpMessageDecoder.decode(RtpMessageDecoder.java:6icon_cool.gif
    at io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:489)
    at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:42icon_cool.gif
    ... 16 more
    1. admin
      @lxc

      或者你把数据包的头部信息16进制发给我看下。

    2. admin
      @lxc

      RTP 数据包解析出错了,你抓下设备传输过来的数据包,然后对比下 1078 协议里负载包格式看哪里出错了。

  25. lxc

    测试类的源码能发一下吗,我这边也是想把流发送大rtmp流媒体服务器

  26. roylu

    您好,代码下载下来,里面是乱吗来的。

    1. admin
      @roylu

      代码重新上传了,.txt的。

    2. admin
      @roylu

      公司装了加密软件的 - -!

      1. roylu
        @admin

        谢谢分享

  27. chenzhao

    最近也在搞这个,请问下你这边推流到rtmp服务器是如何实现的,谢谢!

    1. admin
      @chenzhao

      源码已上传在底部。

  28. na

    大佬,代码能共享学习下吗?

    1. admin
      @na

      源码已上传在底部。

      1. zker
        @admin

        源码全是乱码呢

  29. levince

    大佬,你的这个task.write()是写往哪的?是把数据重新封包成rtp协议的视频流播放吗?

    1. admin
      @levince

      PublishTask 里面封装了一个管道流,task.write() 只是做了一个委托,最终是往 PipedOutputStream 里面写数据。

  30. 范明明

    虽然看不懂,但感觉很高端!

Leave a Comment