流媒体转发的遇到的一些问题

Posted 风翼科技

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了流媒体转发的遇到的一些问题相关的知识,希望对你有一定的参考价值。

在windows下开发流媒体转发,遇到了不少问题,汇总一把

 

1.第一个是有时候会莫名其妙的卡死,检查了不少代码,也没发现问题,

不过发现一个现象,printf()打印的终端,没显示最近的日志,按enter,会出来很多日志,在网上找了好久

http://www.cnblogs.com/jwk000/p/6194525.html

发现这兄弟和我遇见的情况一致,可能是打印缓冲区满了导致的,也有可能其他原因(大神知道的可以告诉我),最后在接收到心跳的时候加了system(“cls”),就没出现过,可能windows真的不适合服务器开发

 

2.使用close关闭boost的socket的时候奔溃

boost::asio::ip::tcp::socket socket(io_service); 
boost::asio::socket_base::linger option(true, 0);
socket.set_option(option);
....
socket.close();

这个是多线程问题,这边关闭,另一边却不知道,还在操作,所以崩溃

socket.shutdown(tcp::socket::shutdown_both);    

shutdown就不会,官方注释也是这样推荐的

 

3.udp接收到的数据不能正常正常解析

不停调试发现是udp socket的缓存区的大小设置太小,要找的适合自己程序的值

    int nRecvBuf = 1024 * 1920 * 1090;
    //int nRecvBuf = 1024*1024*100;
    int iResult = setsockopt(NodeRecSocket, SOL_SOCKET, SO_RCVBUF, (const char*)&nRecvBuf, sizeof(int));
    if (SOCKET_ERROR == iResult)
    {
        LOG_ERROR << "setsockopt error " << WSAGetLastError();
        WSACleanup();
        return;
    }

 

4.sleep(1)的实际休眠时间,在传递数据的时候,为了保证数据的有序,使用了sleep(1),循环一次不明显,循环多次视频明显延迟很多,所以查了一下sleep(1)

不查不知道,一查吓一跳,sleep(1)的实际时间取决于 系统时间精度,cpu核个数,CPU分配的时间片,以及线程调度的时候上下文切换,等等一系列因素,有的电脑测出来休眠了10毫秒,简直无法忍受,有的测出来0.6-0.8ms

目前没有好的办法,我只用sleep(0),让渡出去,让系统决定时间

 

5.以前的流媒体转发设计是,使用了一个线程,重复做如下工作

  a.读取视频流udp数据

  b.解析udp数据

  c.向所有请求的远端转发udp数据

  d.重复a-c;

这样的设计出现一个很大的问题就是

延时的积累,因为b,c都是耗时操作,而且要耗时操作执行完才能读取下一次的数据,这个时候可能udp的缓存区已经积累的大量的数据,而且udp每次只能读一个包

有时候延时很严重,延时一分钟两分钟,直至最后卡死

 

所以我改写了流程

a.新建一个数组存放udp数据,数组的每个元素存放一个udp包,数组的长度依据期望的延时来设置,

视频大小 分辨率 建议码率
480P 720X480 1800Kbps
720P 1280X720 3500Kbps
1080P 1920X1080 8500Kbps

我最大的延时忍受值是5秒,传输的视频是1920*1080   也就是5*8500Kbps=41KB    每个元素存放1024byte数据,所以就是40个元素,(考虑到特殊情况I帧比较多的时候)我放大了一点60个元素

b.新建两个线程,一个专门读取udp传输过来数据,放入数组,记录读取下标。另一个转发数据,记录转发下标(加锁,防止读写冲突)

如果读取udp传输过来数据的位置马上追上了转发数据的位置,那说明已经延时5秒,直接丢弃前五秒的数据,把转发数据的位置移到读取传输数据的前一个位置

 

6.程序请求不了视频流,具体描述是,在同一网段(局域网)的视频能正常播放,公网的视频拉不下来,研究了很久没找到原因,抓包发现,在别的程序能正常请求视频的,在rtsp的setup步骤后面会根据对方的地址和协商的端口发送几个穿网包,这个穿网包打通了网络,视频流才能从公网传输的局域网

穿网包代码如下

void App::SendNetPackage(unsigned long in_channelId, uint16_t remote_port, uint16_t local_port)
{
    LOG_INFO << "App::SendNetPackage  " << in_channelId;
    boost::lock_guard<boost::mutex> locker(m_stream_mutex);
    std::map<unsigned long, boost::shared_ptr<StreamInfo> >::iterator it;
    it = m_streams.find(in_channelId);

    if (it != m_streams.end()){
        SOCKET NodeSendSocket;
            WSADATA wsaData;
            WORD sockVersion = MAKEWORD(2, 2);
            if (WSAStartup(sockVersion, &wsaData) != 0){
                LOG_ERROR << "WSAStartup error !";
                return;
            }

            NodeSendSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_HOPOPTS);
            if (NodeSendSocket == INVALID_SOCKET){
                LOG_ERROR << "socket error !";
                return;
            }
        sockaddr_in serAddr;
        serAddr.sin_family = AF_INET;
        serAddr.sin_port = htons(remote_port);
        serAddr.sin_addr.S_un.S_addr = inet_addr(it->second->camera_ip.c_str());


        sockaddr_in localAddr;
        localAddr.sin_family = AF_INET;
        localAddr.sin_port = htons(local_port);
        localAddr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);

        LOG_INFO << "SendNetPackage bind ok !port " << local_port;
        if (bind(NodeSendSocket, (sockaddr *)&localAddr, sizeof(localAddr)) == SOCKET_ERROR){
            LOG_ERROR << "bind error !";
            closesocket(NodeSendSocket);
            return;
        }
        char* mBuff = "rtp package send!";
        int nAddrLen = sizeof(serAddr);
        memset(mBuff, 1, strlen(mBuff));
        int ret=sendto(NodeSendSocket, mBuff, 10, 0, (sockaddr *)&serAddr, nAddrLen);
        LOG_INFO << "SendNetPackage " << ret;
        closesocket(NodeSendSocket);
        WSACleanup();
    }
    else {
        LOG_ERROR << "No this channel : " << in_channelId;
    }


}

 

7.局域网到局域网(p2p)视频传输,不需要服务器接通的临时解决方案

前置条件:

视频输出主机的554(rtsp)端口,映射到固定的公网端口;

能控制视频输出 公网IP的端口的情况下;

 

   一般p2p都是需要公网服务器在中间,检测视频从那个地址那个端口出来,然后提前打通通道,保证一般情况能打通

 如果没有服务器呢

 播放端,通过地址,554端口,请求rtsp,然后协商传输,

 可是协商的都是局域网里面的主机的端口,你不知道,这个端口,路由器会给你映射到公网的哪个端口

 对于发送端,没法知道目的端口,不知道发送到哪里

 对于接收端,没法知道发送端端口,连提前告诉发送端自己位置的机会都没有

 

 仔细查看发现,

  播放端一般会发一个穿网包,

  如果发送端能接到这个穿网包,播放端的 播放端公网地址:播放端公网分配端口 都可以被发送端记录下来,

  发送端直接朝这个 播放端公网地址:播放端公网分配端口 发送视频流就可以正常实现播放,因为播放端发送穿网包打通了网络,允许数据进入。

     可惜rtsp协商的端口都是局域网里面的端口,所以发送端一般情况下是接收不到这个包的

  如果在发送端的主机上设置一个rtp端口范围,发送端主机只能在这个范围分配端口,然后这个范围的端口一一映射到公网ip的同样的端口,这样发送端协商的端口,直接就是公网端口,播放端发送的穿网包就可以直接进来,发送端记录下来播放端的ip和端口,直接就  可以朝这个播放端的ip和端口发送数据流,从而实现无服务器p2p。

 

配置udp端口范围

 

  <remote_min_rtp_port>10000</remote_min_rtp_port>
  <remote_max_rtp_port>10020</remote_max_rtp_port>

 

端口范围内分配

#pragma once
#include <boost/asio.hpp>
#include <boost/interprocess/sync/scoped_lock.hpp>
#include <boost/interprocess/sync/named_mutex.hpp>
//#include <boost/interprocess/creation_tags.hpp>
class L_GetFreePort
{
public:

    L_GetFreePort(unsigned short min_port, unsigned short max_port);
    int operator()();
private:
    boost::asio::io_service io_service;
    unsigned short maxport;
    unsigned short minport;
    unsigned short curPort;
    //boost::mutex mutex;
    //boost::interprocess::named_mutex namedMutex;
};
extern L_GetFreePort getFreePort;


/**********/


m_GetFreePort(min_port, max_port)     
/*********/
port.port1 = m_GetFreePort(); port.port2 = port.port1 + 1;

 

端口一一映射到公网需要在路由器上面配置

 

 

监听554端口报错

tcp监听554端口报错:
一个封锁操作被对 WSACancelBlockingCall 的调用中断。

同时udp等待穿网包报错:
995  由于线程退出或应用程序请求,已中止 I/O 操作

一个封锁操作被对 WSACancelBlockingCall 的调用中断。

这个错误网上有如下几种情况导致

1.在断开UPDClient的时候,正在读取数据,导致该异常,使用Available 属性用于确定在网络缓冲区中排队等待读取的数据的量,解决

2.多线程中,关闭一个线程中的socket,错误的关闭了在监听的socket,导致这个错误,调用了closesocket后,一定要把里面的socket即时赋值成INVALID_SOCKET,解决

3.监听tcp socket的时候被关闭

4.关闭socket时,socket.Receive(buffer); 仍出于读取状态。

5. http://blog.csdn.net/zz962/article/details/8746800  这篇写的不错,不过只说了是WSACleanup引起的,没给出解决方法

参考微软的网页 https://msdn.microsoft.com/en-us/library/windows/desktop/ms741549(v=vs.85).aspx 有些不错的建议

 An application or DLL is required to perform a successful WSAStartup call before it can use Windows Sockets services. When it has completed the use of Windows Sockets, the application or DLL must call WSACleanup to deregister itself from a Windows Sockets implementation and allow the implementation to free any resources allocated on behalf of the application or DLL.

使用socket前最好WSAStartup

使用结束WSACleanup

 

When WSACleanup is called, any pending blocking or asynchronous Windows Sockets calls issued by any thread in this process are canceled without posting any notification messages or without signaling any event objects.

一旦调用WSACleanup,任何阻塞异步的socket都会被取消,但是不会有任何消息信号通知

 

Sockets that were open when WSACleanup was called are reset and automatically deallocated as if closesocket were called. Sockets that have been closed with closesocket but that still have pending data to be sent can be affected when WSACleanup is called. In this case, the pending data can be lost if the WS2_32.DLL is unloaded from memory as the application exits. To ensure that all pending data is sent, an application should use shutdown to close the connection, then wait until the close completes before calling closesocket and WSACleanup. All resources and internal state, such as queued unposted or posted messages, must be deallocated so as to be available to the next user.

调用WSACleanup会自动调用closesocket,为了保证正在发送的数据不会丢失,最好调用shutdown

In Windows Sockets 1.1, attempting to call WSACleanup from within a blocking hook and then failing to check the return code was a common programming error. If a Winsock 1.1 application needs to quit while a blocking call is outstanding, the application has to first cancel the blocking call with WSACancelBlockingCall then issue the WSACleanup call once control has been returned to the application. In Windows Sockets 2, this issue does not exist and the WSACancelBlockingCall function has been removed.

Windows Sockets 1.1里面,调用WSACleanup ,如果有阻塞的监听的话,首先会调用 WSACancelBlockingCallIn Windows Sockets 2中不存在这些问题

 

WSAStartupWSACleanup必须是一一对应的,这个在代码里面最好好好检查,WSAStartup调用一次,就会增加一次记录,WSACleanup调用一次,减少一次调用记录,如果在某些地方多调用了WSACleanup,就会导致无缘无故的释放socket,socket变为无效

 

参考资料

https://www.2cto.com/net/201311/254835.html

以上是关于流媒体转发的遇到的一些问题的主要内容,如果未能解决你的问题,请参考以下文章

片段中的媒体控制器

创建自己的代码片段(CodeSnippet)

用Darwin开发RTSP级联server(拉模式转发)(附源代码)

如何对媒体片段的任何部分进行范围请求?

如何在Sublime Text中添加代码片段

在不存在的片段上调用片段生命周期和 onCreate 的问题