live555Server读取文件修改为socket接收数据遇到的问题

来源:互联网 发布:天猫魔盒必装软件 编辑:程序博客网 时间:2024/06/05 02:18
最近在修改 live555Server端的代码,基本需求是这样的,live555Server有从文件读取音视频数据作为 Server的 Demo。我们需要改为 从网络中接收音视频数据作为 Server的数据输入。最终是一个程序从网络中接收音视频数据,然后建立 Unix socket套接字服务,等待有客户端连接 live555Server的时候,live555Server首先创建套接字连接我们程序刚才创建的服务,然后我们的程序向 Unix socket套接字上发送音视频数据,live555Server接收数据作为输入,向 rtsp的客户端发送数据,rtsp客户端进行音视频播放。
在修改过程中遇到了几个问题,在这里记录一下:

1.在做的过程中出现了一点小问题。就是使用 Unix socket套接字发送数据开始的时候由于 live555Server读取速度等原因,导致很容易将 socket缓冲区写满,导致写入失败,我们就直接把这一帧数据丢弃了。但是 live555Server向外发送 rtp包的时间戳是经过 视频帧率、音频采样率与系统时间计算出来的,这就会出现一个问题。
当视频经常丢弃时,但是音频没有出现缓冲区满的问题(音频数据量很小),rtp的时间戳计算就会出现问题,导致音视频的时间戳相差很大,导致解码端无法解码显示。
对于这个问题,我们的修改方法是 使用信源的 pts作为 rtp的时间戳向外发送,由我们的程序向 live555Server发送音视频数据及对应的 pts值,在 live555Server端解析出音视频数据及 pts,并将 pts作为 rtp的时间戳向外发送。这样就处理了音视频的 rtp包时间戳相差大无法解码的问题。
其实 live555Server的 rtcp包也是使用的系统时间计算的,所以我们还需要将 rtcp的时间戳也转换为我们的 pts附近的值,我得处理方法是 使用上一次发送的 pts值。

2.第二个问题。在测试过程中,发现使用我们的 rtsp客户端拉流,一般情况下 2个小时左右会出现断流情况。原因是在 MPEGVideoStreamFramer.cpp文件的 continueReadProcessing()函数中,会调用 parse()解析音视频函数,因为 parse()函数在缓冲区中没有数据的情况下,会驱动数据的读取,然后抛出异常,然后返回0,所以这个函数的返回值会决定着下一步的走向。
当这个函数内部抛出异常时,会返回0,然后就什么都不做。返回非0值时,会继续驱动afterGetting(),然后打包,发送 rtp,继续下一次定时器的安装。
所以,可能是由于音视频数据的原因,导致 parse函数解析正常数据后,没有解析到有效数据,导致 parse()函数返回了0,注意:这不是抛出异常导致的。。。所以没有进行下一次 定时器的安装。所以,没有驱动了。。。就会造成服务器不解析也不发送音视频数据了。。。
我的修改方法是:在 parse()函数里面,抛出异常导致的函数结束,返回-1(修改 parse()函数返回值类型),其他情况下继续正常返回。continueReadProcessing()函数中在对 parse()的返回值判断的地方,大于等于0走的是一个逻辑(继续数据驱动)。
这样修改过后即使因为某次数据的解析错误导致 parse()函数返回了0,也可以正常去驱动下一次的数据读取解析发送流程。

3.做测试过程中,还出现了另外一个问题,因为我们需要传入 pts值,所以将音视频的 pts全部放在了 音视频头部的后面,数据的前面的位置。在 live555Server解析过程中会随机的解析错误一次。这个问题排查了很久。最终,发现原因是在 H264or5VideoStreamFramer.cpp的 parse函数中,如果找到了下一帧的起始头(00 00 00 01或者 00 00 01),就会跳过头部字节并跳出循环(skipBytes()),继续下面的处理,在函数退出之前会保存当前的解析状态(setParseState()),但是在保存解析状态之前,会有一个测试下一帧数据的操作,来判断 rtp数据包打不打 Mark标志。testBytes()这个函数在测试之后的数据的时候,如果没有数据会向 数据输入Source要数据,然后抛出异常,退出 parse函数。这就导致了上面 skipBytes()的数据,没有保存解析状态就跳出函数了,导致下一次我解析 pts值得时候,直接解析错误了。。。
修改方法是在 找到下一帧的起始头之后,skipBytes()之前调用一次 setParseState()函数,保存一下当前解析状态。

4.最后一个修改的地方是数据驱动的地方。之前代码的逻辑是 定时器驱动数据读取,然后在 ByteStreamFileSource.cpp里面的 doGetNextFrame()函数里面会添加 epoll读感兴趣事件,添加完成之后就返回,所以并没有真正的读取数据(也可以直接读取数据,但是我们的 socket是非阻塞的,所以需要等到有数据时在读取)。
然后等到 epoll读事件触发时,继续上面未完成的任务,读数据,然后调用 afterGetting()函数,一步步调用(真的绕)之后来到 MultiFramedRTPSink.cpp进行数据的发送,sendPacketIfNecessary()函数发送数据之后会继续加一个定时器去驱动下一次的数据读取。循环往复。
但是这里会出现一个性能的问题,因为这是读文件的例子,所以它按照视频的帧率来计算下一次的读取时间,然后根据时间添加定时器。然而我们修改的是从网络中获取数据,肯定数据来的有快有慢,所以不能完全依靠定时器去驱动。如果仍然按照以前的逻辑,就会出现这个问题:
1.定时器到时间,进行数据读取,加入 epoll读事件,返回;
2.epoll读事件驱动,数据读取解析,发送 rtp数据包,继续加下一个定时器(20ms);
3.在下一次定时器触发之前,20ms以内,epoll读事件触发也没有用,因为数据接收的缓冲区需要定时器这边驱动传入 ByteStreamFileSource类,所以这 20ms以内数据来了也没有用,只能等待,所以很容易造成 socket缓冲区满了,导致数据发送失败。
我的解决方法是,去掉定时器驱动,完全依靠 epoll的读事件驱动。在 sendPacketIfNecessary()函数中,不使用
nextTask() = envir().taskScheduler().scheduleDelayedTask(uSecondsToGo, (TaskFunc*)sendNext, this);
添加定时器,而是直接调用 sendNext函数中的有效代码:
buildAndSendPacket(False);
直接将一切接收数据之前的操作准备好,这个时候,无论 epoll读事件什么时候触发,都可以直接读取数据,不用进行定时器的等待,这样修改 epoll事件驱动之后,性能上有一定的提升。


阅读全文
0 0
原创粉丝点击