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

标签: 无

精彩评论
  1. 虽然看不懂,但感觉很高端!

  2. levince levince

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

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

  3. na na

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

    1. 源码已上传在底部。

  4. chenzhao chenzhao

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

    1. 源码已上传在底部。

  5. roylu roylu

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

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

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

  6. lxc lxc

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

  7. lxc 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. RTP 数据包解析出错了,你抓下设备传输过来的数据包,然后对比下 1078 协议里负载包格式看哪里出错了。

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

  8. lxc lxc

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

  9. lxc lxc

    303163648162002a014440314563010300000000000000000000000003b6082d45a0fa2fbebae5a483502fd01029fc1ea2dbc528fff9783677650998bbdf7fe7958d68393ef3265f97a7ffd573b0cf81d8c46d55bc55b4bde022e88c112d50a4537fdba65dee9e64fdacd66f93e3b408998fc6673dffb4e5901261de9fef43183bd4e418a7cfa04c219fe74013c127785504517f600278a60f9194cbf1815c11217afcfa56cce0ffa4d7094bb39e55c4f697a00b8978d043fd76cda4509152e7ab1ac4d51aa23659c65fef4f8fdb1471f1ef96d832c5cebd36fba22ebfbe041f942724e5fd0f4793be314f7ef6cbc9fe17fe7848a5f390d8339063ac5a4ce41efd7fc61088c0f02d6ae7cace7ff760f391d833909c83419c23ac3f0508573f723d333e6e41e0406d36ae19465ab288a43a607a68e37aa3cecbfddcfdf9edf43f6fd3faffea1a18dd50d24f23f380a7372d83025625477eae002c447906fecd405d78d50df43a9a7f7e4db632cbe3dc8291557857aff0670d2b11996653983b91465a1ef22b6022687f8dc642009a70c8a9fb8f1340fb5d97bcd13405e6157439250965e998141ce6b6265edd3d2a76ebaaff36769406fb50cda60a276d828eae46fd019a779ef4e7d849e3bfd4cad4d8c74cd48a61de2ff6e4603c175a9d9c69c8fed0346856515912be323ff1efb83e6e335a0f50960b8eb15fce66dbd55d843f275f085143414798c76ce67b0984c2cc62b5f16b05ad6e8663cb01b0b882512ac5853b8315afa4c0388890e253bc86e20fe7126dc4e2db253fb6cedbcbf2882690d6f5ed8737237f0317b2e83779f96d83e72147615c08a9e277617616638c2eb6cc03e58184e279c835e5c635ee0713ae3b04d66f7a5615084cc5e99aa658bb98c66f8570ad07615bb0bfe2cf0fa8e3d7b077da67afefc3c3a69ce46b39058cfb069bc91c8abf583a0ce43dbe9f9a56396bbd7573906f9b7f1ff8234e41f390958a0dc4148ca3ed4761e412abfb61fdb1917235966c469258e7a68b65534c2d87e21ba1f816b74735f129c21b1421295a53128acbc32ded74c0704ecc04041bbe8718ac0a08682243c37ef49fb57ba0a0e6b84d2a221bb84decd4bf6fcfda3c301ac6d396318fba2a8a8187031c4d41842dc7424ffd677f9be5e2170ed8ec41df5e720bf39059fef073ac353107b07cc4160c3ec941bc83ab8fb1381fcae20183e36b0ee5937e3cb9af8ebffad6b54fdae8a3c3c139326cee5c76f2fb6ff875306c1e7ebf5afafd5a1854199515d8b6c4db766c504c69ead3fc834c649c3ac7240f4a429c15ff80e0d469ecfb6bae7455a9d6acf194f5323e46ec9dbbcbbe3df46bb7cbc100b186402e2910398054e717d985这个是我前天抓包的

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

      1. lxc lxc

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

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

          1. lxc lxc

            可以正确解析了,我把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
          2. 删掉pom.xml中的FFmpeg-windows-x86依赖clean项目重试一下。

          3. lxc lxc

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

          4. duan duan

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

          5. Clown Clown

            你现在实现这个推流了吗,可否分享一下代码,我这边也使用这个例子调试,一直不成功,我的qq:376267941

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

  10. lxc lxc

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

    1. duan duan

      你好

    2. duan duan

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

    3. bridge bridge

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

  11. lxc lxc

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

    1. lxc lxc

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

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

      1. lxc lxc

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

        1. tom tom

          最后 怎么解决的?

    3. 花钱少 花钱少

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

  12. lxc 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 lxc

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

      1. lxc lxc

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

    2. 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 lxc

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

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

        没用的吗

        1. 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. 邓工 邓工

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

  13. lxc lxc

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

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

      1. lxc lxc

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

        1. lxc lxc

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

  14. xxgycf xxgycf

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

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

      1. xxgycf xxgycf

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

        1. xxgycf xxgycf

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

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

          1. xxgycf xxgycf

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

          2. xxgycf xxgycf

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

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

          4. xxgycf xxgycf

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

  15. 杨杰 杨杰

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

  16. 郭小坏 郭小坏

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

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

  17. lxc lxc

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

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

      1. 王者讨饭 王者讨饭

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

      2. benjamin benjamin

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

    2. 花钱少 花钱少

      有完整的代码参考吗

  18. 天堂 天堂

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

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

      1. 天堂 天堂

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

  19. 王者讨饭 王者讨饭

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

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

      1. 王者讨饭 王者讨饭

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

      2. 王者讨饭 王者讨饭

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

  20. 王者讨饭 王者讨饭

    推流到rtmp不成功 又没报错

  21. LeoHu LeoHu

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

    1. LeoHu LeoHu

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

    2. LeoHu LeoHu

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

      1. lxc lxc

        可能丢包了

        1. LeoHu LeoHu

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

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

      3. LeoHu LeoHu

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

        1. 播放裸流快进问题已经解决了吗?如果没有你可以参考下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 LeoHu

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

    3. 周书维 周书维

      请问,音频问题解决没,能不能留个wx交流下。

  22. lxc lxc

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

  23. 追梦少年 追梦少年

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

    1. 准备离职不搞了 ::twemoji:smilecry::

      1. LeoHu LeoHu

        ::twemoji:sweat:: 这么快就放弃了

  24. 王者讨饭 王者讨饭

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

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

      1. 花钱少 花钱少

        博主,代码有更新吗,可以发一套新的来吗 [email protected]

      2. 花钱少 花钱少

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

  25. 仁哥 仁哥

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

    1. tom tom

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

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

        1. 仁哥 仁哥

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

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

            int tick = 0;
            int tickInterval = 40;             //时间戳增量,实际上从设备上传的 RTP 负载包里面的 时间戳 可以知道每两个完整的 RTP 之间的时间间隔是 40ms (我这边音频流对应是两帧[因为根据音频流参数知道每帧 20ms])
            
            //tickInterval 也可以自己动态计算,也就是 RTP 最后一个包的时间戳 - 上一个 RTP 最后一个包的时间戳
            
            //每次发送前叠加增量
            tick += tickInterval;
            sendAAC2Rtmp(aacData, tick);
          2. 实际上不仅音频流如此,视频流也是一样,不然就会出现我一开始直接推流那种情况:视频播放速度过快导致卡顿。

          3. 仁哥 仁哥

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

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

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

          6. 仁哥 仁哥

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

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

          8. 仁哥 仁哥

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

        2. tom tom

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

        3. tom tom

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

      2. tom tom

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

  26. 花钱少 花钱少

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

  27. 统一回复下音频流的问题吧。之前解析 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 tom

      没成功,有成功的吗?

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

          1. tom tom

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

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

          3. tom tom

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

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

          5. tom tom

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

          6. fox fox

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

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

          8. rickhuan rickhuan

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

          9. tom tom

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

          10. 微信:xwlcnsy

          11. joxhome joxhome

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

          12. 洛奇 洛奇

            你好,请问能风向下你的音频代码么

          13. zx6247021 zx6247021

            请问您是怎么从711a转码到acc的,我在网上没有找到音频流的转化库

          14. Zim Zim

            求G711A转AAC的。搞了好久都不行

  28. edison edison

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

  29. H H

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

  30. 小戴 小戴

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

    1. L L

      老哥,问下音视频那边实现了吗?能给下方向吗?

  31. 疯子也是猖狂 疯子也是猖狂

    大佬,我也咋写这个,我想知道从接收RTP码流到实时播放是什么个流程

    1. 解析 RTP 音视频流 -> 将音视频流转码推流到 RTMP 流媒体服务器 -> 客户端播放

  32. lxc lxc

    pcm转aac的java代码有吗,求 ::twemoji:prayer::

    1. lxc lxc

      不要安卓的代码

    2. bao bao

      pcm 转aac java 的问题解决了么,我这也遇见这个问题了

  33. mmrls mmrls

    @tom 求G711A转AAC的。在网上找了好久

  34. qiuzhizhe qiuzhizhe

    org.bytedeco.javacv.FrameGrabber$Exception: Could not grab: No AVFormatContext. (Has start() been called?)

    at org.bytedeco.javacv.FFmpegFrameGrabber.grabFrame(FFmpegFrameGrabber.java:106icon_cool.gif
    at org.bytedeco.javacv.FFmpegFrameGrabber.grab(FFmpegFrameGrabber.java:1055)
    at net.che_mi.push.PushTask.run(PushTask.java:52)

    博主,项目一运行就报错了,求解,万分感谢

发表评论:

icon_mrgreen.gificon_neutral.gificon_twisted.gificon_arrow.gificon_eek.gificon_smile.gificon_confused.gificon_cool.gificon_evil.gificon_biggrin.gificon_idea.gificon_redface.gificon_razz.gificon_rolleyes.gificon_wink.gificon_cry.gificon_surprised.gificon_lol.gificon_mad.gificon_sad.gificon_exclaim.gificon_question.gif