当前位置: 首页 > wzjs >正文

网站建设系统规划猫咪99永久找到回家的路

网站建设系统规划,猫咪99永久找到回家的路,网站建立费用,一个公司如何做多个网站备案一、引言 由《音视频入门基础:RTP专题(2)——使用FFmpeg命令生成RTP流》可以知道,推流端通过下面FFmpeg命令可以将一个媒体文件转推RTP,生成RTP流: ffmpeg -re -stream_loop -1 -i input.mp4 -vcodec cop…

一、引言

由《音视频入门基础:RTP专题(2)——使用FFmpeg命令生成RTP流》可以知道,推流端通过下面FFmpeg命令可以将一个媒体文件转推RTP,生成RTP流:

ffmpeg -re -stream_loop -1 -i input.mp4 -vcodec copy -an -f rtp rtp://192.168.0.103:6005 -acodec copy -vn -sdp_file XXX.sdp -f rtp rtp://192.168.0.103:7005

接收端通过命令:ffmpeg -protocol_whitelist "file,rtp,udp" -i XXX.sdp 可以查看生成的RTP流的信息:

由《音视频入门基础:RTP专题(8)——使用Wireshark分析RTP》可以知道,上述推流端的本质是创建了一个UDP客户端,将媒体文件input.mp4的视频数据发送到IP为192.168.0.103的UDP服务器的6005端口,音频数据发送到该UDP服务器的7005端口。与之对应,接收端的本质是创建了一个UDP服务器来接收推流端发送的基于UDP的RTP数据。下面讲述接收端的FFmpeg,其源码中接收RTP流的内部实现。

二、FFmpeg接收RTP流的原理

(一)给addrinfo结构赋值

当音视频流为RTP流时,FFmpeg的avformat_open_input函数内部会调用rtp_open函数,而rtp_open函数的底层又会调用udp_open函数,udp_open函数内部又调用了udp_socket_create函数:

/* put it in UDP context */
/* return non zero if error */
static int udp_open(URLContext *h, const char *uri, int flags)
{
//...udp_fd = udp_socket_create(h, &my_addr, &len, s->localaddr);
//...
}

udp_socket_create函数内部会通过语句:

res0 = ff_ip_resolve_host(h, (localaddr && localaddr[0]) ? localaddr : NULL,s->local_port,SOCK_DGRAM, family, AI_PASSIVE) 根据给定的主机名和需要创建的UDP服务器的端口,返回一个struct addrinfo结构,从而给addrinfo结构赋值。其中localaddr包含主机名,s->local_port为UDP服务器的端口,该端口号来源于推流端的FFmpeg命令生成的SDP文件中的端口信息:

static int udp_socket_create(URLContext *h, struct sockaddr_storage *addr,socklen_t *addr_len, const char *localaddr)
{UDPContext *s = h->priv_data;int udp_fd = -1;struct addrinfo *res0, *res;int family = AF_UNSPEC;if (((struct sockaddr *) &s->dest_addr)->sa_family)family = ((struct sockaddr *) &s->dest_addr)->sa_family;res0 = ff_ip_resolve_host(h, (localaddr && localaddr[0]) ? localaddr : NULL,s->local_port,SOCK_DGRAM, family, AI_PASSIVE);if (!res0)goto fail;for (res = res0; res; res=res->ai_next) {if (s->udplite_coverage)udp_fd = ff_socket(res->ai_family, SOCK_DGRAM, IPPROTO_UDPLITE, h);elseudp_fd = ff_socket(res->ai_family, SOCK_DGRAM, 0, h);if (udp_fd != -1) break;ff_log_net_error(h, AV_LOG_ERROR, "socket");}if (udp_fd < 0)goto fail;memcpy(addr, res->ai_addr, res->ai_addrlen);*addr_len = res->ai_addrlen;freeaddrinfo(res0);return udp_fd;fail:if (udp_fd >= 0)closesocket(udp_fd);if(res0)freeaddrinfo(res0);return -1;
}

ff_ip_resolve_host函数定义在libavformat/ip.c中,可以看到其内部通过getaddrinfo函数根据给定的主机名和服务名,返回一个struct addrinfo结构。关于getaddrinfo函数的用法可以参考:《百度百科——getaddrinfo》:

struct addrinfo *ff_ip_resolve_host(void *log_ctx,const char *hostname, int port,int type, int family, int flags)
{struct addrinfo hints = { 0 }, *res = 0;int error;char sport[16];const char *node = 0, *service = "0";if (port > 0) {snprintf(sport, sizeof(sport), "%d", port);service = sport;}if ((hostname) && (hostname[0] != '\0') && (hostname[0] != '?')) {node = hostname;}hints.ai_socktype = type;hints.ai_family   = family;hints.ai_flags    = flags;if ((error = getaddrinfo(node, service, &hints, &res))) {res = NULL;av_log(log_ctx, AV_LOG_ERROR, "getaddrinfo(%s, %s): %s\n",node ? node : "unknown",service,gai_strerror(error));}return res;
}

执行完上述操作后,udp_open函数中的变量my_addr会得到主机名和需要创建的UDP服务器的端口信息:

/* put it in UDP context */
/* return non zero if error */
static int udp_open(URLContext *h, const char *uri, int flags)
{
//...udp_fd = udp_socket_create(h, &my_addr, &len, s->localaddr);
//...
}

(二)创建套接字

给addrinfo结构赋值后,udp_socket_create函数内部会执行语句:udp_fd = ff_socket(res->ai_family, SOCK_DGRAM, 0, h) 来创建套接字,SOCK_DGRAM表示是基于UDP:

static int udp_socket_create(URLContext *h, struct sockaddr_storage *addr,socklen_t *addr_len, const char *localaddr)
{
//...res0 = ff_ip_resolve_host(h, (localaddr && localaddr[0]) ? localaddr : NULL,s->local_port,SOCK_DGRAM, family, AI_PASSIVE);
//...for (res = res0; res; res=res->ai_next) {if (s->udplite_coverage)udp_fd = ff_socket(res->ai_family, SOCK_DGRAM, IPPROTO_UDPLITE, h);elseudp_fd = ff_socket(res->ai_family, SOCK_DGRAM, 0, h);if (udp_fd != -1) break;ff_log_net_error(h, AV_LOG_ERROR, "socket");}
//...
}

 ff_socket函数定义在libavformat/network.c中,可以看到该函数内部通过socket函数创建了套接字。音视频流为RTP的情况下,形参type的值为SOCK_DGRAM,所以此时创建的是UDP套接字:


int ff_socket(int af, int type, int proto, void *logctx)
{int fd;#ifdef SOCK_CLOEXECfd = socket(af, type | SOCK_CLOEXEC, proto);if (fd == -1 && errno == EINVAL)
#endif{fd = socket(af, type, proto);
#if HAVE_FCNTLif (fd != -1) {if (fcntl(fd, F_SETFD, FD_CLOEXEC) == -1)av_log(logctx, AV_LOG_DEBUG, "Failed to set close on exec\n");}
#endif}
#ifdef SO_NOSIGPIPEif (fd != -1) {if (setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &(int){1}, sizeof(int))) {av_log(logctx, AV_LOG_WARNING, "setsockopt(SO_NOSIGPIPE) failed\n");}}
#endifreturn fd;
}

(三)绑定套接字

创建完UDP套接字后,udp_open函数中会通过bind函数绑定上述创建的UDP套接字到my_addr上。从上面我们已经可以知道,my_addr包含需要创建的UDP服务器的端口信息:

​/* put it in UDP context */
/* return non zero if error */
static int udp_open(URLContext *h, const char *uri, int flags)
{
//...udp_fd = udp_socket_create(h, &my_addr, &len, s->localaddr);
//.../* bind to the local address if not multicast or if the multicast* bind failed *//* the bind is needed to give a port to the socket now */if (bind_ret < 0 && bind(udp_fd,(struct sockaddr *)&my_addr, len) < 0) {ff_log_net_error(h, AV_LOG_ERROR, "bind failed");ret = ff_neterrno();goto fail;}
//...
}​

(四)监视文件描述符是否可读并接收数据

绑定完套接字后,FFmpeg的avformat_find_stream_info函数底层会调用rtp_read函数,而rtp_read函数内部会通过poll函数监视文件描述符(上述创建的UDP套接字)是否可读。如果可读,那就通过recvfrom函数接收UDP数据:

static int rtp_read(URLContext *h, uint8_t *buf, int size)
{RTPContext *s = h->priv_data;int len, n, i;struct pollfd p[2] = {{s->rtp_fd, POLLIN, 0}, {s->rtcp_fd, POLLIN, 0}};int poll_delay = h->flags & AVIO_FLAG_NONBLOCK ? 0 : POLLING_TIME;struct sockaddr_storage *addrs[2] = { &s->last_rtp_source, &s->last_rtcp_source };socklen_t *addr_lens[2] = { &s->last_rtp_source_len, &s->last_rtcp_source_len };int runs = h->rw_timeout / 1000 / POLLING_TIME;for(;;) {if (ff_check_interrupt(&h->interrupt_callback))return AVERROR_EXIT;n = poll(p, 2, poll_delay);if (n > 0) {/* first try RTCP, then RTP */for (i = 1; i >= 0; i--) {if (!(p[i].revents & POLLIN))continue;*addr_lens[i] = sizeof(*addrs[i]);len = recvfrom(p[i].fd, buf, size, 0,(struct sockaddr *)addrs[i], addr_lens[i]);if (len < 0) {if (ff_neterrno() == AVERROR(EAGAIN) ||ff_neterrno() == AVERROR(EINTR))continue;return AVERROR(EIO);}if (ff_ip_check_source_lists(addrs[i], &s->filters))continue;return len;}} else if (n == 0 && h->rw_timeout > 0 && --runs <= 0) {return AVERROR(ETIMEDOUT);} else if (n < 0) {if (ff_neterrno() == AVERROR(EINTR))continue;return AVERROR(EIO);}if (h->flags & AVIO_FLAG_NONBLOCK)return AVERROR(EAGAIN);}
}

三、总结

1.从上面的代码分析可以看出来,接收端的FFmpeg接收RTP数据,其原理就是创建了一个UDP服务器来接收推流端发送的基于UDP的RTP数据,本质就是接收UDP数据。该UDP服务器的实现原理跟《Linux下使用poll函数编写UDP客户端、服务器程序》中展示的UDP服务器是一样的,都是调用了socket、bind、poll、recvfrom这几个函数。

2.跟普通的UDP服务器相比,FFmpeg接收RTP流时创建的UDP服务器,其端口来源于推流端的FFmpeg命令生成的SDP文件中的端口信息。也就是说此时的流程是UDP客户端先被创建,然后服务器才创建。所以可以实现推流端先推流(UDP客户端先发送音视频数据,不管服务器有没有接收到),接收端再接收RTP音视频数据。

http://www.dtcms.com/wzjs/553500.html

相关文章:

  • 在百度做推广送网站好吗怎么把别人网站的tag写上自己的
  • asp.net微信网站wordpress4.9 多站点
  • 肥料网站建设网站开发如何引用函数
  • 网站备案备注信息阿里的6家外包公司名单
  • 高端网站建设 案例wordpress 封装 app
  • 博客网站模板有哪些长沙百度关键词推广
  • 服务区里可以做多少个网站甘肃项目信息网
  • 福州mip网站建设东营做网站哪家好
  • 创建网站需要什么技术电子商务网站建设与管理 李建忠
  • 专业群建设专题网站长沙百度贴吧
  • 网站搜索哪里有做网站的教程
  • 智能模板网站建设做编程的 网站有哪些方面
  • 单位的网站建设费如何核算海南行指海口网站开发
  • 网站建设需求文件做网站开发有前途么
  • 湛江市seo网站设计联系方式高境网站建设
  • 做室内装修设计的网站环球军事头条
  • 政法门户网站建设情况沈阳网站关键词优化哪家好
  • 做网站开发学什么软件自己建设一个网站需要多少钱
  • 怎样做科技小制作视频网站什么叫电商运营
  • 顺义建站好的公司中学生做的网站
  • 网站开发工程师心得总结wordpress安装为什么是英文版
  • 网站收录方法cms开发教程
  • 广州企业建站系统模板怎样查后缀vc域名网站
  • 世代网络网站建设设计浙江省住房城乡建设厅网站首页
  • 支付宝手机网站网站建设衤金手指谷哥十四
  • 网站开发毕业设计文档对网站策划的看法
  • 住房建设部官方网站办事大厅网站为什么需要空间
  • 网站开发 青岛自建淘宝客网站模板
  • 网站建设与推广的实训报告wordpress import
  • 美食 网站模板开发者app