【十五】【vlc-android】vlc-sout流媒体输出端源码实现分析【Part 3】【02】

此章节分析承接上一章分析:
【十五】【vlc-android】vlc-sout流媒体输出端源码实现分析【Part 3】【01】
1.1.2.1、net_ListenSingle实现分析:【本地IP地址和端口号】

// [vlc/src/network/udp.c]
static int net_ListenSingle (vlc_object_t *obj, const char *host, int port,
                             int protocol)
{
    // 网络地址信息
    struct addrinfo hints = {
        // sock类型即数据报套接字类型
        .ai_socktype = SOCK_DGRAM,
        // 协议类型【如UDP】
        .ai_protocol = protocol,
        // 输入标识
        .ai_flags = AI_PASSIVE | AI_NUMERICSERV | AI_IDN,
    }, *res;

    if (host && !*host)
        // 指针不为空但值为空则将指针置为空指针
        host = NULL;

    msg_Dbg (obj, "net: opening %s datagram port %d",
             host ? host : "any", port);

    // 内部主要使用getaddrinfo函数解析网址和IP地址【127.0.0.1】等【获取可用地址列表】
    // 内部会有vlc的特殊处理:接收空串host主机地址请求等
    int val = vlc_getaddrinfo (host, port, &hints, &res);
    if (val)
    {
        msg_Err (obj, "Cannot resolve %s port %d : %s", host, port,
                 gai_strerror (val));
        return -1;
    }

    val = -1;

    // socket打开处理【可能需要从可用网络信息中选择可用的】
    for (const struct addrinfo *ptr = res; ptr != NULL; ptr = ptr->ai_next)
    {
        // 打开socket并返回套接字描述符
        // 见下面的分析
        int fd = net_Socket (obj, ptr->ai_family, ptr->ai_socktype,
                             ptr->ai_protocol);
        if (fd == -1)
        {
            msg_Dbg (obj, "socket error: %s", vlc_strerror_c(net_errno));
            continue;
        }

#ifdef IPV6_V6ONLY
        /* Try dual-mode IPv6 if available. */
        if (ptr->ai_family == AF_INET6)
            setsockopt (fd, SOL_IPV6, IPV6_V6ONLY, &(int){ 0 }, sizeof (int));
#endif
        // 初始化数据包套接字
        // 见下面的分析
        fd = net_SetupDgramSocket( obj, fd, ptr );
        if( fd == -1 )
            continue;

        // 检查socket地址是否为多播地址,若为多播地址则执行加入多播组,
        // 若加入失败则重新下一个地址
        if (net_SockAddrIsMulticast (ptr->ai_addr, ptr->ai_addrlen)
         && net_Subscribe (obj, fd, ptr->ai_addr, ptr->ai_addrlen))
        {
            net_Close (fd);
            continue;
        }

        val = fd;
        break;
    }

    freeaddrinfo (res);
    return val;
}

// [vlc/src/network/io.c]
int net_Socket (vlc_object_t *p_this, int family, int socktype,
                int protocol)
{
    // 内部调用socket函数创建socket套接字连接
    int fd = vlc_socket (family, socktype, protocol, true);
    if (fd == -1)
    {
        if (net_errno != EAFNOSUPPORT)
            msg_Err (p_this, "cannot create socket: %s",
                     vlc_strerror_c(net_errno));
        return -1;
    }

    // 设置套接字的选项:若等待时间,缓冲区大小等
    setsockopt (fd, SOL_SOCKET, SO_REUSEADDR, &(int){ 1 }, sizeof (int));

// 以下为其他连接方式的处理,暂不分析

#ifdef IPV6_V6ONLY
    /*
     * Accepts only IPv6 connections on IPv6 sockets.
     * If possible, we should open two sockets, but it is not always possible.
     */
    if (family == AF_INET6)
        setsockopt (fd, IPPROTO_IPV6, IPV6_V6ONLY, &(int){ 1 }, sizeof (int));
#endif

#if defined (_WIN32)
# ifndef IPV6_PROTECTION_LEVEL
#  warning Please update your C library headers.
#  define IPV6_PROTECTION_LEVEL 23
#  define PROTECTION_LEVEL_UNRESTRICTED 10
# endif
    if (family == AF_INET6)
        setsockopt (fd, IPPROTO_IPV6, IPV6_PROTECTION_LEVEL,
                    &(int){ PROTECTION_LEVEL_UNRESTRICTED }, sizeof (int));
#endif

#ifdef DCCP_SOCKOPT_SERVICE
    if (socktype == SOL_DCCP)
    {
        char *dccps = var_InheritString (p_this, "dccp-service");
        if (dccps != NULL)
        {
            setsockopt (fd, SOL_DCCP, DCCP_SOCKOPT_SERVICE, dccps,
                        (strlen (dccps) + 3) & ~3);
            free (dccps);
        }
    }
#endif

    return fd;
}

// [vlc/src/network/udp.c]
static int net_SetupDgramSocket (vlc_object_t *p_obj, int fd,
                                 const struct addrinfo *ptr)
{
#if defined (SO_REUSEPORT) && !defined (__linux__)
// 设置套接字的选项
    setsockopt (fd, SOL_SOCKET, SO_REUSEPORT, &(int){ 1 }, sizeof (int));
#endif

#if defined (_WIN32)
// ... 省略中间Windows平台处理
#endif
    // 调用bind将IP地址信息【地址和端口号、缓冲区的长度】和socket绑定
    if (bind (fd, ptr->ai_addr, ptr->ai_addrlen))
    {
        msg_Err( p_obj, "socket bind error: %s", vlc_strerror_c(net_errno) );
        net_Close (fd);
        return -1;
    }
    return fd;
}

1.2、rtp_get_fmt实现分析:【vlc/modules/stream_out/ftpfmt.c】

// 注解意思是:可以考虑更简单的方式实现下面大串switch匹配方式代码实现
/* TODO: make this into something more clever than a big switch? */
int rtp_get_fmt( vlc_object_t *obj, const es_format_t *p_fmt, const char *mux,
                 rtp_format_t *rtp_fmt )
{
    assert( p_fmt != NULL || mux != NULL );

    // 动态的负载类型。 放入每个ES流到它自己的RTP会话轨道数据中,因此没有冲突风险
    /* Dynamic payload type. Payload types are scoped to the RTP
     * session, and we put each ES in its own session, so no risk of
     * conflict. */
    rtp_fmt->payload_type = 96;
    // SDP数据格式负载的数据类型:音频流、视频流、字幕流、数据流。 默认处理为视频流
    rtp_fmt->cat = mux != NULL ? VIDEO_ES : p_fmt->i_cat;
    if( rtp_fmt->cat == AUDIO_ES )
    {
        // 采样率和声音通道格式
        rtp_fmt->clock_rate = p_fmt->audio.i_rate;
        rtp_fmt->channels = p_fmt->audio.i_channels;
    }
    else
        // 不是音频流时【最常见情况就是视频】,则默认设置时钟速率为90KHz
        rtp_fmt->clock_rate = 90000; /* most common case for video */
    // 媒体流码率计算,若p_fmt对象为空则为0,【单位为kbps】
    /* Stream bitrate in kbps */
    rtp_fmt->bitrate = p_fmt != NULL ? p_fmt->i_bitrate/1000 : 0;
    rtp_fmt->fmtp = NULL;

    // RTP流复用格式【封装格式】,若指定了封装格式则直接返回
    if( mux != NULL )
    {// 不为空,若为TS则使用MEPG-TS流传输封装格式,否则默认为MEPG-PS封装格式
        if( strncmp( mux, "ts", 2 ) == 0 )
        {
            rtp_fmt->payload_type = 33;
            rtp_fmt->ptname = "MP2T";
        }
        else
            rtp_fmt->ptname = "MP2P";
        return VLC_SUCCESS;
    }

    // 未指定封装格式则根据当前编解码类型
    switch( p_fmt->i_codec )
    {
        case VLC_CODEC_MULAW:
            if( p_fmt->audio.i_channels == 1 && p_fmt->audio.i_rate == 8000 )
                rtp_fmt->payload_type = 0;
            rtp_fmt->ptname = "PCMU";
            rtp_fmt->pf_packetize = rtp_packetize_pcm;
            break;
            
            // ... 省略部分代码
            
        case VLC_CODEC_H263:
            rtp_fmt->ptname = "H263-1998";
            // 分组分包功能方法指针赋值,在第五章中分析调用的
            rtp_fmt->pf_packetize = rtp_packetize_h263;
            break;
        case VLC_CODEC_H264:
            rtp_fmt->ptname = "H264";
            // 分组分包功能方法指针赋值,在第五章中分析调用的
            // 该实现分析见后续章节 TODO
            rtp_fmt->pf_packetize = rtp_packetize_h264;
            rtp_fmt->fmtp = NULL;

            // 【i_extra】解复用后es格式数据中的额外数据指针字节长度
            if( p_fmt->i_extra > 0 )
            {
                // SPS 和 PPS
                char    *p_64_sps = NULL;
                char    *p_64_pps = NULL;
                char    hexa[6+1];

                // H264数据迭代器
                hxxx_iterator_ctx_t it;
                hxxx_iterator_init( &it, p_fmt->p_extra, p_fmt->i_extra, 0 );

                // NALU格式分为2类,VCL和non-VCL,总共有19种不同的NALU格式
                // 通过下面的分析可知该指针指向AnnexB格式数据流开始位置【已去掉起始码】
                const uint8_t *p_nal;
                // 通过下面的分析可知该值表示【p_nal】指针负载的数据大小
                size_t i_nal;
                // 循环获取【Annex B流格式】数据即解析不同NALU数据
                // Annex B格式通常用于实时的流格式,比如说传输流,通过无线传输的广播、DVD等。
                // 在这些格式中通常会周期性的重复SPS和PPS包,经常是在每一个关键帧之前, 
                // 因此据此建立解码器可以一个随机访问的点,这样就可以加入一个正在进行的流,及播放一个已经在传输的流。
                // 见1.2.1小节分析
                while( hxxx_annexb_iterate_next( &it, &p_nal, &i_nal ) )
                {// 根据分析可知while条件处理功能为:获取到AnnexB数据流
                // 并去掉任何的AnnexB数据流格式中的起始码【三字节和四字节的起始码】
                    if( i_nal < 2 )
                    {// NAL数据大小小于2
                        msg_Dbg( obj, "No-info found in nal ");
                        continue;
                    }

                    // 根据H264码流架构可知,此处第一个字节数据即为NAL Header
                    // 因此此处和【0001 1111】第4到8位进行与运算来获取这几位的值,即为NALU类型
                    const int i_nal_type = p_nal[0]&0x1f;

                    msg_Dbg( obj, "we found a startcode for NAL with TYPE:%d", i_nal_type );

                    if( i_nal_type == 7 && i_nal >= 4 )
                    {// SPS类型数据处理,并且至少数据大小为4个字节
                        free( p_64_sps );
                        // 使用Base64算法来编码二进制数据得到SPS数据,便于网络传输
                        // 【Base64是网络上最常见的用于传输8Bit字节代码的编码方式之一
                        // ,Base64编码可用于在HTTP环境下传递较长的标识信息】
                        p_64_sps = vlc_b64_encode_binary( p_nal, i_nal );
                        // 读取NAL数据中的第2到4个字节数据【根据下面分析可知表示profile-level-id】
                        // 格式化转换二进制数据为十六进制数据表示
                        sprintf_hexa( hexa, &p_nal[1], 3 );
                    }
                    else if( i_nal_type == 8 )
                    {// PPS类型数据处理
                        free( p_64_pps );
                        // 同上SPS处理
                        p_64_pps = vlc_b64_encode_binary( p_nal, i_nal );
                    }
                }

                // 参数集PS和档次等级ID值格式化并保存
                /* */
                if( p_64_sps && p_64_pps &&
                    ( asprintf( &rtp_fmt->fmtp,
                                "packetization-mode=1;profile-level-id=%s;"
                                "sprop-parameter-sets=%s,%s;", hexa, p_64_sps,
                                p_64_pps ) == -1 ) )
                    rtp_fmt->fmtp = NULL;
                free( p_64_sps );
                free( p_64_pps );
            }
            if( rtp_fmt->fmtp == NULL )
                // 若没有额外数据信息,则默认初始化如下格式设置
                rtp_fmt->fmtp = strdup( "packetization-mode=1" );
            break;
        
        // TODO 下面这几个编码格式目前暂时不分析,只重点研究H264码流处理过程,后续有时间再分析
        
        case VLC_CODEC_HEVC:
        {
            rtp_fmt->ptname = "H265";
            rtp_fmt->pf_packetize = rtp_packetize_h265;
            rtp_fmt->fmtp = NULL;

            int i_profile = p_fmt->i_profile;
            int i_level = p_fmt->i_level;
            int i_tiers = -1;
            int i_space = -1;

            struct nalset_e
            {
                const uint8_t i_nal;
                const uint8_t i_extend;
                const char *psz_name;
                char *psz_64;
            } nalsets[4] = {
                { 32, 0, "vps", NULL },
                { 33, 0, "sps", NULL },
                { 34, 0, "pps", NULL },
                { 39, 1, "sei", NULL },
            };

            if( p_fmt->i_extra > 0 )
            {
                hxxx_iterator_ctx_t it;
                for(int i=0; i<4; i++)
                {
                    struct nalset_e *set = &nalsets[i];

                    hxxx_iterator_init( &it, p_fmt->p_extra, p_fmt->i_extra, 0 );
                    const uint8_t *p_nal;
                    size_t i_nal;
                    while( hxxx_annexb_iterate_next( &it, &p_nal, &i_nal ) )
                    {
                        const uint8_t i_nal_type = (p_nal[0] & 0x7E) >> 1;
                        if( i_nal_type < set->i_nal ||
                            i_nal_type > set->i_nal + set->i_extend )
                            continue;
                        msg_Dbg( obj, "we found a startcode for NAL with TYPE:%" PRIu8, i_nal_type );

                        char *psz_temp = vlc_b64_encode_binary( p_nal, i_nal );
                        if( psz_temp )
                        {
                            if( set->psz_64 == NULL )
                            {
                                set->psz_64 = psz_temp;
                            }
                            else
                            {
                                char *psz_merged;
                                if( asprintf( &psz_merged, "%s,%s", set->psz_64, psz_temp ) != -1 )
                                {
                                    free( set->psz_64 );
                                    set->psz_64 = psz_merged;
                                }
                                free( psz_temp );
                            }
                        }

                        if( i_nal_type == 33 && i_nal > 12 )
                        {
                            if( i_profile < 0 )
                                i_profile = p_nal[1] & 0x1F;
                            if( i_space < 0 )
                                i_space = p_nal[1] >> 6;
                            if( i_tiers < 0 )
                                i_tiers = !!(p_nal[1] & 0x20);
                            if( i_level < 0 )
                                i_level = p_nal[12];
                        }
                    }
                }
            }

            rtp_fmt->fmtp = strdup( "tx-mode=SRST;" );
            if( rtp_fmt->fmtp )
            {
                char *psz_fmtp;
                if( i_profile >= 0 &&
                    asprintf( &psz_fmtp, "%sprofile-id=%d;",
                                         rtp_fmt->fmtp, i_profile ) != -1 )
                {
                    free( rtp_fmt->fmtp );
                    rtp_fmt->fmtp = psz_fmtp;
                }
                if( i_level >= 0 &&
                    asprintf( &psz_fmtp, "%slevel-id=%d;",
                                         rtp_fmt->fmtp, i_level ) != -1 )
                {
                    free( rtp_fmt->fmtp );
                    rtp_fmt->fmtp = psz_fmtp;
                }
                if( i_tiers >= 0 &&
                    asprintf( &psz_fmtp, "%stier-flag=%d;",
                                         rtp_fmt->fmtp, i_tiers ) != -1 )
                {
                    free( rtp_fmt->fmtp );
                    rtp_fmt->fmtp = psz_fmtp;
                }
                if( i_space >= 0 &&
                    asprintf( &psz_fmtp, "%sprofile-space=%d;",
                                         rtp_fmt->fmtp, i_space ) != -1 )
                {
                    free( rtp_fmt->fmtp );
                    rtp_fmt->fmtp = psz_fmtp;
                }

                for(int i=0; i<4; i++)
                {
                    struct nalset_e *set = &nalsets[i];
                    if( set->psz_64 &&
                        asprintf( &psz_fmtp, "%ssprop-%s=%s;",
                                             rtp_fmt->fmtp,
                                             set->psz_name,
                                             set->psz_64 ) != -1 )
                    {
                        free( rtp_fmt->fmtp );
                        rtp_fmt->fmtp = psz_fmtp;
                    }
                }
            }

            for(int i=0; i<4; i++)
                free( nalsets[i].psz_64 );

            break;
        }
        
        // ... 省略部分代码

        case VLC_CODEC_R420:
            rtp_fmt->ptname = "RAW";
            rtp_fmt->pf_packetize = rtp_packetize_r420;
            if( asprintf( &rtp_fmt->fmtp,
                    "sampling=YCbCr-4:2:0; width=%d; height=%d; "
                    "depth=8; colorimetry=BT%s",
                    p_fmt->video.i_visible_width, p_fmt->video.i_visible_height,
                    p_fmt->video.i_visible_height > 576 ? "709-2" : "601-5") == -1 )
            {
                rtp_fmt->fmtp = NULL;
                return VLC_ENOMEM;
            }
            break;
        case VLC_CODEC_RGB24:
            rtp_fmt->ptname = "RAW";
            rtp_fmt->pf_packetize = rtp_packetize_rgb24;
            if( asprintf( &rtp_fmt->fmtp,
                    "sampling=RGB; width=%d; height=%d; "
                    "depth=8; colorimetry=SMPTE240M",
                    p_fmt->video.i_visible_width,
                    p_fmt->video.i_visible_height ) == -1 )
            {
                rtp_fmt->fmtp = NULL;
                return VLC_ENOMEM;
            }
            break;
        case VLC_CODEC_MJPG:
        case VLC_CODEC_JPEG:
            rtp_fmt->ptname = "JPEG";
            rtp_fmt->payload_type = 26;
            rtp_fmt->pf_packetize = rtp_packetize_jpeg;
            break;

        default:
            msg_Err( obj, "cannot add this stream (unsupported "
                     "codec: %4.4s)", (char*)&p_fmt->i_codec );
            return VLC_EGENERIC;
    }

    return VLC_SUCCESS;
}

1.2.1、hxxx_annexb_iterate_next实现分析:

//【vlc/modules/packetizer/hxxx_nal.h】
static inline bool hxxx_annexb_iterate_next( hxxx_iterator_ctx_t *p_ctx, const uint8_t **pp_start, size_t *pi_size )
{
    if( !p_ctx->p_head )
        return false;

    // 起始码处理寻找AnnexB流格式数据开始位置
    p_ctx->p_head = startcode_FindAnnexB( p_ctx->p_head, p_ctx->p_tail );
    if( !p_ctx->p_head )
        return false;

    // 起始码处理寻找AnnexB流格式数据结尾位置
    // 加3表示跳过AnnexB数据起始码【0x00 0x00 0x01】3字节大小
    const uint8_t *p_end = startcode_FindAnnexB( p_ctx->p_head + 3, p_ctx->p_tail );
    if( !p_end )
        // 若为空则将传入的值附上
        p_end = p_ctx->p_tail;

    // 注译:修正3到4字节大小的起始码偏移量和删除任何尾随零
    // 其实作用就是:去掉原始编码数据后面的结尾标记添加的字节对齐0
    /* fix 3 to 4 startcode offset and strip any trailing zeros */
    while( p_end > p_ctx->p_head && p_end[-1] == 0 )
        p_end--;

    // 当前AnnexB数据开始位置赋值【注意包括了起始码】
    *pp_start = p_ctx->p_head;
    // 当前AnnexB数据的大小
    *pi_size = p_end - p_ctx->p_head;
    // 指针指向AnnexB结尾数据位置,起始就是将其数据指针往后移,指向后面的数据
    p_ctx->p_head = p_end;

    // 去掉任何的AnnexB数据流格式中的起始码【三字节和四字节的起始码】
    // 见下面的分析
    return hxxx_strip_AnnexB_startcode( pp_start, pi_size );
}

//【vlc/modules/packetizer/startcode_helper.h】
/* That code is adapted from libav's ff_avc_find_startcode_internal
 * and i believe the trick originated from
 * https://graphics.stanford.edu/~seander/bithacks.html#ZeroInWord
 */
static inline const uint8_t * startcode_FindAnnexB( const uint8_t *p, const uint8_t *end )
{
#if defined(CAN_COMPILE_SSE2) || defined(HAVE_SSE2_INTRINSICS)
// 英特尔设计的CPU SSE2整数指令集,拥有更快的【图像】处理速度
    if (vlc_CPU_SSE2())
    // 此处不展开分析,因为内部实现设计一些该寄存器指令算术的实现
        return startcode_FindAnnexB_SSE2(p, end);
#endif
    const uint8_t *a = p + 4 - ((intptr_t)p & 3);

    // 循环查找起始码值【3个byte,其他类型数据起始码为4个】
    // 每次指针指向地址往后移动1个字节大小,
    // 若前三个起始码等于001字节序列值即【0000 0000 0001】起始码值,
    // 则表示找到Annex B流格式,并将其数据流开始地址指针返回
    for (end -= 3; p < a && p <= end; p++) {
        if (p[0] == 0 && p[1] == 0 && p[2] == 1)
            return p;
    }

    // 若上面的匹配查找失败,则进行4字节大小往后移动指针来匹配
    for (end -= 3; p < end; p += 4) {
        uint32_t x = *(const uint32_t*)p;
        // ~x表示x的二进制位都取反包括符号位
        if ((x - 0x01010101) & (~x) & 0x80808080)
        {// 通过使用比单字节查找快4倍的技巧有效地查找AnnexB数据流格式起始码0x00 0x00 0x01。
            // 该匹配算法不是很快
            /* matching DW isn't faster */
            // 见下面的宏定义
            TRY_MATCH(p, 0);
        }
    }

    // 若上面两次匹配查找都失败,则执行更慢的查找匹配算法即从头开始每个字节移动指针匹配
    for (end += 3; p <= end; p++) {
        if (p[0] == 0 && p[1] == 0 && p[2] == 1)
            return p;
    }

    return NULL;
}

//【vlc/modules/packetizer/startcode_helper.h】
/* Looks up efficiently for an AnnexB startcode 0x00 0x00 0x01
 * by using a 4 times faster trick than single byte lookup. */
#define TRY_MATCH(p,a) {\
     if (p[a+1] == 0) {\
            if (p[a+0] == 0 && p[a+2] == 1)\
                return a+p;\
            if (p[a+2] == 0 && p[a+3] == 1)\
                return a+p+1;\
        }\
        if (p[a+3] == 0) {\
            if (p[a+2] == 0 && p[a+4] == 1)\
                return a+p+2;\
            if (p[a+4] == 0 && p[a+5] == 1)\
                return a+p+3;\
        }\
    }

//【vlc/modules/packetizer/hxxx_nal.h】
// 注译:去掉任何AnnexB数据流中的起始码【三字节和四字节的起始码】
/* strips any AnnexB startcode [0] 0 0 1 */
static inline bool hxxx_strip_AnnexB_startcode( const uint8_t **pp_data, size_t *pi_data )
{
    // 定义的【比特流】记录
    unsigned bitflow = 0;
    const uint8_t *p_data = *pp_data;
    size_t i_data = *pi_data;

    // 循环查找来判断是否已跳过【指针往后移动】起始码,成功则放回true并修改参数变量值
    while( i_data && p_data[0] <= 1 )
    {// 注意此处的【bitflow】作用是:标记循环执行次数有效性【见下分析】
        bitflow = (bitflow << 1) | (!p_data[0]);
        p_data++;
        i_data--;
        if( !(bitflow & 0x01) )
        {// 当第一次【p_data[0]】为1时才会进来,因此根据起始码【[0] 0 0 1】,前面最少有两个0参与循环了
            // 因此此处bitflow的值后八位二进制为:0000 0111,因此与运算后变为0000 0110
            if( (bitflow & 0x06) == 0x06 ) /* there was at least 2 leading zeros */
            {// 若是理论上的循环次数则表示正确,然后将去掉起始码的数据保存返回
                *pi_data = i_data;
                *pp_data = p_data;
                return true;
            }
            return false;
        }
    }
    return false;
}

1.3、rtp_listen_thread实现分析:

// [vlc/modules/stream_out/rtp.c]
// 注译:RTP服务器端socket监听线程将传入的链接【DCCP流】出队处理
/* This thread dequeues incoming connections (DCCP streaming) */
static void *rtp_listen_thread( void *data )
{
    sout_stream_id_sys_t *id = data;

    assert( id->listen.fd != NULL );

    // 开启线程循环
    for( ;; )
    {
        // RTP服务器端socket accept接收客户端请求
        // 见下面的分析
        int fd = net_Accept( id->p_stream, id->listen.fd );
        if( fd == -1 )
            continue;
        int canc = vlc_savecancel( );
        // rtp流添加到数据输出端发送数据给客户端套接字fd,【true表示RTCP复用同一socket应答】
        // 见1.1.2小节分析
        rtp_add_sink( id, fd, true, NULL );
        vlc_restorecancel( canc );
    }

    vlc_assert_unreachable();
}

// 该方法会wait等待新连接进入
// [vlc/modules/stream_out/tcp.c]
/**
 * Accepts an new connection on a set of listening sockets.
 * If there are no pending connections, this function will wait.
 * @note If the thread needs to handle events other than incoming connections,
 * you need to use poll() and net_AcceptSingle() instead.
 *
 * @param p_this VLC object for logging and object kill signal
 * @param pi_fd listening socket set
 * @return -1 on error (may be transient error due to network issues),
 * a new socket descriptor on success.
 */
int net_Accept (vlc_object_t *p_this, int *pi_fd)
{
    assert (pi_fd != NULL);

    // 此处处理为:从前面的net_Listen方法实现中可知,
    // 返回的可能是多个套接字接口【即打开多个socket进行连接】,最后结尾赋值了为-1。
    // 因此此处是计算pi_fd即套接字描述符的个数
    unsigned n = 0;
    while (pi_fd[n] != -1)
        n++;

    // 初始化套接字描述符集合[pollfd是个网络相关结构体]
    struct pollfd ufd[n];
    /* Initialize file descriptor set */
    for (unsigned i = 0; i < n; i++)
    {
        ufd[i].fd = pi_fd[i];
        ufd[i].events = POLLIN;
    }

    // 开启循环
    for (;;)
    {
        // poll是Linux中的字符设备驱动中的一个函数。此处超时时间设为了-1
        // 返回值:
        // >0:数组fds中准备好读、写或出错状态的那些socket描述符的总数量;
        // ==0:数组fds中没有任何socket描述符准备好读、写,或出错;
        // 此时poll超时,超时时间是timeout毫秒;
        // 换句话说,如果所检测的socket描述符上没有任何事件发生的话,
        // 那么poll()函数会阻塞timeout所指定的毫秒时间长度之后返回,
        // 如果timeout==0,那么poll() 函数立即返回而不阻塞,
        // 如果timeout==INFTIM,那么poll() 函数会一直阻塞下去,
        // 直到所检测的socket描述符上的感兴趣的事件发生是才返回,
        // 如果感兴趣的事件永远不发生,那么poll()就会永远阻塞下去;
        // -1: poll函数调用失败,同时会自动设置全局变量errno;
        while (poll (ufd, n, -1) == -1)
        {// 若失败原因不是【EINTR】则重新poll
            if (net_errno != EINTR)
            {
                msg_Err (p_this, "poll error: %s", vlc_strerror_c(net_errno));
                return -1;
            }
        }

        // 若上面检测到有事件发生则处理接收到的事件,
        // 循环处理多个打开的socket套接字连接。
        for (unsigned i = 0; i < n; i++)
        {
            // 若该socket套接字没有接收到事件,则下一个处理
            if (ufd[i].revents == 0)
                continue;

            // 接收到事件
            int sfd = ufd[i].fd;
            int fd = net_AcceptSingle (p_this, sfd);
            if (fd == -1)
                continue;

            // 注译:将正在监听的套接字移到末尾,让其他套接口在下次机会进行处理。
            // 即循环处理每个打开的套接字
            /*
             * Move listening socket to the end to let the others in the
             * set a chance next time.
             */
            memmove (pi_fd + i, pi_fd + i + 1, n - (i + 1));
            pi_fd[n - 1] = sfd;
            return fd;
        }
    }
    return -1;
}

// [vlc/modules/stream_out/tcp.c]
int net_AcceptSingle (vlc_object_t *obj, int lfd)
{
    // 内部会调用accept网络接口函数
    // 【会阻塞,直到有客户端连接上来为止】
    int fd = vlc_accept (lfd, NULL, NULL, true);
    if (fd == -1)
    {// 接收客户端连接失败
        if (net_errno != EAGAIN)
#if (EAGAIN != EWOULDBLOCK)
          if (net_errno != EWOULDBLOCK)
#endif
            msg_Err (obj, "accept failed (from socket %d): %s", lfd,
                     vlc_strerror_c(net_errno));
        return -1;
    }

    // 从本机套接口接收了客户端套接接口的请求
    msg_Dbg (obj, "accepted socket %d (from socket %d)", fd, lfd);
    // 设置套接字的选项
    setsockopt (fd, SOL_SOCKET, SO_REUSEADDR, &(int){ 1 }, sizeof(int));
    return fd;
}

1.4、net_ConnectDgram实现分析:

// 【vlc/src/network/udp.c】
/*****************************************************************************
 * net_ConnectDgram:
 *****************************************************************************
 * Open a datagram socket to send data to a defined destination, with an
 * optional hop limit.
 *****************************************************************************/
int net_ConnectDgram( vlc_object_t *p_this, const char *psz_host, int i_port,
                      int i_hlim, int proto )
{
    // 地址信息:类型为数据报类套接口
    struct addrinfo hints = {
        .ai_socktype = SOCK_DGRAM,
        .ai_protocol = proto,
        .ai_flags = AI_NUMERICSERV | AI_IDN,
    }, *res;
    int       i_handle = -1;
    bool      b_unreach = false;

    // TTL生存时间值【大致是经过的交换机个数】,未设置时此处获取此前计算出的TTL
    if( i_hlim < 0 )
        i_hlim = var_InheritInteger( p_this, "ttl" );

    // 连接推流地址处理
    
    msg_Dbg( p_this, "net: connecting to [%s]:%d", psz_host, i_port );

    // 内部调用getaddrinfo网络接口函数:
    // getaddrinfo函数能够处理名字到地址以及服务到端口这两种转换,
    // 返回的是一个sockaddr结构的链表而不是一个地址清单。
    // 这些sockaddr结构随后可由套接口函数直接使用
    int val = vlc_getaddrinfo (psz_host, i_port, &hints, &res);
    if (val)
    {
        msg_Err (p_this, "cannot resolve [%s]:%d : %s", psz_host, i_port,
                 gai_strerror (val));
        return -1;
    }

    // 与前面分析过的地址信息处理类似,打开socket连接
    for (struct addrinfo *ptr = res; ptr != NULL; ptr = ptr->ai_next)
    {
        char *str;
        // 打开socket连接
        int fd = net_Socket (p_this, ptr->ai_family, ptr->ai_socktype,
                             ptr->ai_protocol);
        if (fd == -1)
            continue;

        // 让内核来检测当前地址上是否可以进行广播信息发送
        /* Allow broadcast sending */
        setsockopt (fd, SOL_SOCKET, SO_BROADCAST, &(int){ 1 }, sizeof (int));

        if( i_hlim >= 0 )
        // 设置当前多播地址跳数限制【TTL值】,内部通过setsockopt设置
            net_SetMcastHopLimit( p_this, fd, ptr->ai_family, i_hlim );

        // 得到此前保存的多播地址接口
        str = var_InheritString (p_this, "miface");
        if (str != NULL)
        {// 设置多播输出接口选项【调用setsockopt】
            net_SetMcastOut (p_this, fd, ptr->ai_family, str);
            free (str);
        }

        // DSCP差分服务代码点(Differentiated Services Code Point),
        // IETF于1998年12月发布了Diff-Serv(Differentiated Service)的QoS分类标准。
        // 它在每个数据包IP头部的服务类别TOS标识字节中,利用已使用的6比特和未使用的2比特,
        // 通过编码值来区分优先级。
        // 保证通信的QoS。 内部通过【setsockopt】设置DSCP值
        net_SetDSCP (fd, var_InheritInteger (p_this, "dscp"));

        // 【connect】方法为socket连接方法:创建与指定外部端口的连接,
        // 将参数fd 的socket 连至参数serv_addr 指定的网络地址 
        if( connect( fd, ptr->ai_addr, ptr->ai_addrlen ) == 0 )
        {// 连接成功则退出
            /* success */
            i_handle = fd;
            break;
        }

#if defined( _WIN32 )
        if( WSAGetLastError () == WSAENETUNREACH )
#else
        if( errno == ENETUNREACH )
#endif
            b_unreach = true;
        else
            msg_Warn( p_this, "%s port %d : %s", psz_host, i_port,
                      vlc_strerror_c(errno) );
        net_Close( fd );
    }

    freeaddrinfo( res );

    if( i_handle == -1 )
    {// 若失败则表示不能打开当前设置的主机地址和端口号
        if( b_unreach )
            msg_Err( p_this, "Host %s port %d is unreachable", psz_host,
                     i_port );
        return -1;
    }

    return i_handle;
}

1.5、RtspAddId实现分析:

// [vlc/modules/stream_out/rtsp.c]
rtsp_stream_id_t *RtspAddId( rtsp_stream_t *rtsp, sout_stream_id_sys_t *sid,
                             uint32_t ssrc, unsigned clock_rate,
                             int mcast_fd)
{
    // 通过前面分析可知ssrc值为一个随机生成值,此处已转化为网络字节顺序值便于网上传输
    
    // 检查rtsp数据流的track id,若大于999则失败
    if (rtsp->track_id > 999)
    {
        msg_Err(rtsp->owner, "RTSP: too many IDs!");
        return NULL;
    }

    char *urlbuf;
    rtsp_stream_id_t *id = malloc( sizeof( *id ) );
    httpd_url_t *url;

    if( id == NULL )
        return NULL;

    id->stream = rtsp;
    id->sout_id = sid;
    id->track_id = rtsp->track_id;
    id->ssrc = ssrc;
    id->clock_rate = clock_rate;
    // 打开链接的套接字描述符
    id->mcast_fd = mcast_fd;

    // 解析rtsp推流地址
    // 见下面的分析
    urlbuf = RtspAppendTrackPath( id, rtsp->psz_path );
    if( urlbuf == NULL )
    {
        free( id );
        return NULL;
    }

    msg_Dbg( rtsp->owner, "RTSP: adding %s", urlbuf );

    // 获取设置的RTSP链接的用户名和密码
    char *user = var_InheritString(rtsp->owner, "sout-rtsp-user");
    char *pwd = var_InheritString(rtsp->owner, "sout-rtsp-pwd");

    // 注册一个新的URL
    // 见下面的分析
    url = id->url = httpd_UrlNew( rtsp->host, urlbuf, user, pwd );
    free( user );
    free( pwd );
    free( urlbuf );

    if( url == NULL )
    {
        free( id );
        return NULL;
    }

    // 注册RTSP服务请求URL上能接受处理的消息类型的回调方法
    // 目前VLC中实现RTSP服务端接收请求方法:DESCRIBE SETUP PLAY PAUSE GETPARAMETER TEARDOWN
    // [httpd_UrlCatch]见下面的分析
    // [RtspCallbackId]后续章节发送数据处理时分析 TODO
    httpd_UrlCatch( url, HTTPD_MSG_DESCRIBE, RtspCallbackId, (void *)id );
    httpd_UrlCatch( url, HTTPD_MSG_SETUP,    RtspCallbackId, (void *)id );
    httpd_UrlCatch( url, HTTPD_MSG_PLAY,     RtspCallbackId, (void *)id );
    httpd_UrlCatch( url, HTTPD_MSG_PAUSE,    RtspCallbackId, (void *)id );
    httpd_UrlCatch( url, HTTPD_MSG_GETPARAMETER, RtspCallbackId, (void *)id );
    httpd_UrlCatch( url, HTTPD_MSG_TEARDOWN, RtspCallbackId, (void *)id );

    // RTSP数据流的track id加1【即为下一个新ID】
    rtsp->track_id++;

    return id;
}

// [vlc/modules/stream_out/rtsp.c]
char *RtspAppendTrackPath( rtsp_stream_id_t *id, const char *base )
{
    // 若推流路径不为空则在路径最后检查是否需要添加'/'反斜杠
    const char *sep = strlen( base ) > 0 && base[strlen( base ) - 1] == '/' ?
                      "" : "/";
    char *url;

    // 然后将当前地址和数据拼接起来,作为RTSP推流URL放回
    if( asprintf( &url, "%s%strackID=%u", base, sep, id->track_id ) == -1 )
        url = NULL;
    return url;
}

// [vlc/src/network/httpd.c]
/* register a new url */
httpd_url_t *httpd_UrlNew(httpd_host_t *host, const char *psz_url,
                           const char *psz_user, const char *psz_password)
{
    httpd_url_t *url;

    assert(psz_url);

    vlc_mutex_lock(&host->lock);
    for (int i = 0; i < host->i_url; i++)
        // 判断是否当前RTSP推流URL已注册到host对象中了,若已注册则失败处理
        if (!strcmp(psz_url, host->url[i]->psz_url)) {
            msg_Warn(host, "cannot add '%s' (url already defined)", psz_url);
            vlc_mutex_unlock(&host->lock);
            return NULL;
        }

    // xmalloc效率低代价高,而标准库高效的的malloc和free
    url = xmalloc(sizeof(httpd_url_t));
    url->host = host;

    vlc_mutex_init(&url->lock);
    // 复制对应参数值
    url->psz_url = xstrdup(psz_url);
    url->psz_user = xstrdup(psz_user ? psz_user : "");
    url->psz_password = xstrdup(psz_password ? psz_password : "");
    // RTSP请求服务类型的消息回调
    for (int i = 0; i < HTTPD_MSG_MAX; i++) {
        url->catch[i].cb = NULL;
        url->catch[i].p_sys = NULL;
    }

    // 添加当前新的RTSP track URL到URL缓存列表中 
    TAB_APPEND(host->i_url, host->url, url);
    // 通知host的wait事件端发送请求数据等
    // 见后续章节该部分分析 TODO
    vlc_cond_signal(&host->wait);
    vlc_mutex_unlock(&host->lock);

    return url;
}

/* register callback on a url */
int httpd_UrlCatch(httpd_url_t *url, int i_msg, httpd_callback_t cb,
                    httpd_callback_sys_t *p_sys)
{// 赋值即可。 注意此处的[httpd_callback_sys_t]为未实现的结构体名,
// 只是为了缓存当前sys对象指针地址
    vlc_mutex_lock(&url->lock);
    url->catch[i_msg].cb   = cb;
    url->catch[i_msg].p_sys= p_sys;
    vlc_mutex_unlock(&url->lock);

    return VLC_SUCCESS;
}

TODO:此章节系列流媒体处理未分析完整,待后续更新,敬请关注,Thanks♪(・ω・)ノ
【由于本人目前工作内容转为了android framework多媒体框架层的维护、优化等日常工作,因此后续会先更新android framework多媒体框架层实现分析,而vlc-sout流媒体处理部分会延缓更新】

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值