接着第十五章节【Part 1】小节分析:本章分析【不需要加载流复用器模块功能】时的媒体流处理
承接上一章节:
【十五】【vlc-android】vlc-sout流媒体输出端源码实现分析【Part 1】 1、Add实现分析:

// 【vlc/modules/stream_output/rtp.c】
// 注译:添加一个ES流数据作为一个新的RTP流数据
/** Add an ES as a new RTP stream */
static sout_stream_id_sys_t *Add( sout_stream_t *p_stream,
                                  const es_format_t *p_fmt )
{
    // 注译:当p_fmt为null时表示使用非RTP复用格式【流封装格式】
    /* NOTE: As a special case, if we use a non-RTP
     * mux (TS/PS), then p_fmt is NULL. */
    sout_stream_sys_t *p_sys = p_stream->p_sys;
    char              *psz_sdp;

    // 创建输出流ID
    sout_stream_id_sys_t *id = malloc( sizeof( *id ) );
    if( unlikely(id == NULL) )
        return NULL;
    id->p_stream   = p_stream;

    // 分组打包器特有字段
    // 最大传输单元(Maximum Transmission Unit,MTU)用来通知对方所能接受数据服务单元的最大尺寸,
    // 说明发送方能够接受的有效载荷大小。是包或帧的最大长度,一般以字节记
    id->i_mtu = var_InheritInteger( p_stream, "mtu" );
    // 若设置太小【<=28】则默认设置MTU大小为548字节数
    if( id->i_mtu <= 12 + 16 )
        id->i_mtu = 576 - 20 - 8; /* pessimistic */
    // 最大的RTP包大小【字节单位】    
    msg_Dbg( p_stream, "maximum RTP packet size: %d bytes", id->i_mtu );

#ifdef HAVE_SRTP
// 若设置了SRTP (Secure RTP) 安全RTP
    id->srtp = NULL;
#endif
    vlc_mutex_init( &id->lock_sink );
    id->sinkc = 0;
    id->sinkv = NULL;
    id->rtsp_id = NULL;
    id->p_fifo = NULL;
    id->listen.fd = NULL;

    // 初始化标记为第一个RTP包情况
    id->b_first_packet = true;
    // 根据设置配置缓冲时间【此处时间转化为了US微秒单位】
    id->i_caching =
        (int64_t)1000 * var_GetInteger( p_stream, SOUT_CFG_PREFIX "caching");

    // 随机初始化数据包的序列号  RTP包字段
    vlc_rand_bytes (&id->i_sequence, sizeof (id->i_sequence));
    // 随机初始化值
    vlc_rand_bytes (id->ssrc, sizeof (id->ssrc));

    bool format = false;

    if (p_sys->p_vod_media != NULL)
    {
        // RTP包数据格式信息
        id->rtp_fmt.ptname = NULL;
        // 一个主机字节顺序的无符号长整形数【从网络字节顺序转换为主机字节顺序】[netlong hostlong]
        uint32_t ssrc;
        // 匹配一个RTP id到一个VoD媒体ES和RTSP追踪并用已经设置好的数据初始化它
        // 【i_seq_sent_next为RTSP包序列号字段值】
        // 见1.1小节分析 【内部处理含有RTCP socket的链接处理】
        int val = vod_init_id(p_sys->p_vod_media, p_sys->psz_vod_session,
                              p_fmt ? p_fmt->i_id : 0, id, &id->rtp_fmt,
                              &ssrc, &id->i_seq_sent_next);
        if (val == VLC_SUCCESS)
        {
            memcpy(id->ssrc, &ssrc, sizeof(id->ssrc));
            /* This is ugly, but id->i_seq_sent_next needs to be
             * initialized inside vod_init_id() to avoid race
             * conditions. */
             // 将RTSP包序列号赋值给RTP包序列号
            id->i_sequence = id->i_seq_sent_next;
        }
        /* vod_init_id() may fail either because the ES wasn't found in
         * the VoD media, or because the RTSP session is gone. In the
         * former case, id->rtp_fmt was left untouched. */
        format = (id->rtp_fmt.ptname != NULL);
    }

    if (!format)
    {// id->rtp_fmt.ptname为空时
        id->rtp_fmt.fmtp = NULL; /* don't free() garbage on error */
        char *psz = var_GetNonEmptyString( p_stream, SOUT_CFG_PREFIX "mux" );
        if (p_fmt == NULL && psz == NULL)
            goto error;
        // 获取RTP SDP格式信息
        // 见1.2小节分析
        int val = rtp_get_fmt(VLC_OBJECT(p_stream), p_fmt, psz, &id->rtp_fmt);
        free( psz );
        if (val != VLC_SUCCESS)
            goto error;
    }

#ifdef HAVE_SRTP
// 若开启SRTP即安全RTP传输,该实现也不展开分析,后续有时间再继续 TODO
// 在使用实时传输协议或实时传输控制协议时,
// 使不使用安全实时传输协议或安全实时传输控制协议是可选的,
// 但即使使用了安全实时传输协议或安全实时传输控制协议,所有它们提供的特性
//(如加密和认证)也都是可选的,这些特性可以被独立地使用或禁用
// 唯一的例外是在使用安全实时传输控制协议时,必须要用到其消息认证特性
    char *key = var_GetNonEmptyString (p_stream, SOUT_CFG_PREFIX"key");
    if (key)
    {
        vlc_gcrypt_init ();
        id->srtp = srtp_create (SRTP_ENCR_AES_CM, SRTP_AUTH_HMAC_SHA1, 10,
                                   SRTP_PRF_AES_CM, SRTP_RCC_MODE1);
        if (id->srtp == NULL)
        {
            free (key);
            goto error;
        }

        char *salt = var_GetNonEmptyString (p_stream, SOUT_CFG_PREFIX"salt");
        int val = srtp_setkeystring (id->srtp, key, salt ? salt : "");
        free (salt);
        free (key);
        if (val)
        {
            msg_Err (p_stream, "bad SRTP key/salt combination (%s)",
                     vlc_strerror_c(val));
            goto error;
        }
        id->i_sequence = 0; /* FIXME: awful hack for libvlc_srtp */
    }
#endif

    id->i_seq_sent_next = id->i_sequence;

    int mcast_fd = -1;
    if( p_sys->psz_destination != NULL )
    {// 此处地址指的是推流地址,不为空时
        // 选择端口号
        /* Choose the port */
        uint16_t i_port = 0;
        if( p_fmt == NULL )
            ;
        else
        if( p_fmt->i_cat == AUDIO_ES && p_sys->i_port_audio > 0 )
            // 当前es数据流为audio,且设置了端口号
            i_port = p_sys->i_port_audio;
        else
        if( p_fmt->i_cat == VIDEO_ES && p_sys->i_port_video > 0 )
            // 当前es数据流为video,且设置了端口号
            i_port = p_sys->i_port_video;

        /* We do not need the ES lock (p_sys->lock_es) here, because
         * this is the only one thread that can *modify* the ES table.
         * The ES lock protects the other threads from our modifications
         * (TAB_APPEND, TAB_REMOVE). */
        for (int i = 0; i_port && (i < p_sys->i_es); i++)
             // 检查当前设置端口是否和es中使用的端口号重复
             if (i_port == p_sys->es[i]->i_port)
                 i_port = 0; /* Port already in use! */
        // 若上面选择的i_port端口值为0则进行for循环,
        // 在设置的【p_sys->i_port】值上每次加2来选择一个可用端口号
        for (uint16_t p = p_sys->i_port; i_port == 0; p += 2)
        {
            if (p == 0)
            {// 注意:若设置的【p_sys->i_port】为0则失败
            // 太多RTP基本流
                msg_Err (p_stream, "too many RTP elementary streams");
                goto error;
            }
            i_port = p;
            // 再次判断是否和SDP使用的端口号重复
            for (int i = 0; i_port && (i < p_sys->i_es); i++)
                 if (p == p_sys->es[i]->i_port)
                     i_port = 0;
        }

        id->i_port = i_port;

        // 流类套接口
        // 【对于流类套接口(SOCK_STREAM类型),利用名字来与一个远程主机建立连接,
        // 一旦套接口调用成功返回,它就能收发数据了。对于数据报类套接口(SOCK_DGRAM类型),
        // 则设置成一个缺省的目的地址,并用它来进行后续的send()与recv()调用】
        int type = SOCK_STREAM;

        switch( p_sys->proto )
        {
#ifdef SOCK_DCCP
            case IPPROTO_DCCP:
            {
                const char *code;
                switch (id->rtp_fmt.cat)
                {
                    case VIDEO_ES: code = "RTPV";     break;
                    case AUDIO_ES: code = "RTPARTPV"; break;
                    case SPU_ES:   code = "RTPTRTPV"; break;
                    default:       code = "RTPORTPV"; break;
                }
                var_SetString (p_stream, "dccp-service", code);
                type = SOCK_DCCP;
            }
#endif
            // 失败?? TODO 
            /* fall through */
            case IPPROTO_TCP:
                // 内部通过调用socket、bind、listen三个网络编程接口来创建对应host和端口号的被动socket
                // 注意若内部bind失败则会尝试调用另外的网络接口:send和recvmsg函数。
                // 注:recvmsg和sendmsg函数:这两个函数是最通用的I/O函数。
                // 实际上我们可以把所有read、readv、recv和recvfrom调用替换成recvmsg调用。
                // 类似地,各种输出函数调用也可以替换成sendmsg调用。
                
                // 在大多数网络程序中,服务端会作为被动socket被动接受连接,
                // 而客户端会作为主动socket主动发起连接。
                // 被动socket是一个通过调用listen函数监听要发起连接的socket,
                // 当被动socket接受一个连接通常称为被动打开。
                // 服务端通过socket函数创建的socket是主动socket,
                // 而listen函数就是把这个还未接受连接的主动socket转换为被动socket,
                // 因为服务端只需要被动接受客户端的连接请求。
                id->listen.fd = net_Listen( VLC_OBJECT(p_stream),
                                            p_sys->psz_destination, i_port,
                                            type, p_sys->proto );
                if( id->listen.fd == NULL )
                {// 创建被动socket失败
                    msg_Err( p_stream, "passive COMEDIA RTP socket failed" );
                    goto error;
                }
                // 创建RTP服务器端socket链接成功,则开启被动监听请求线程【优先级底】
                // 该线程实现分析见1.3小节
                if( vlc_clone( &id->listen.thread, rtp_listen_thread, id,
                               VLC_THREAD_PRIORITY_LOW ) )
                {
                    net_ListenClose( id->listen.fd );
                    id->listen.fd = NULL;
                    goto error;
                }
                break;

            default:
            {// 否则默认使用数据报类套接口进行socket连接
                // 打开数据报类套接口,将数据发送到已定义的目的【推流】地址(带有可选的跳数限制即TTL生存时间值【大致是经过的交换机个数】)。
                // 见1.4小节分析
                int fd = net_ConnectDgram( p_stream, p_sys->psz_destination,
                                           i_port, -1, p_sys->proto );
                if( fd == -1 )
                {
                    msg_Err( p_stream, "cannot create RTP socket" );
                    goto error;
                }
                // 注意:此处英文意思大致是vlc推流会忽略不期望的传入连接如RTCP-RR数据包,
                // 原因是rtcp-mux设置即rtcp_mux若为true表示RTCP复用同一socket应答
                /* Ignore any unexpected incoming packet (including RTCP-RR
                 * packets in case of rtcp-mux) */
                setsockopt (fd, SOL_SOCKET, SO_RCVBUF, &(int){ 0 },
                            sizeof (int));
                // rtp流添加到数据输出端发送数据给客户端套接字fd,【rtcp_mux若为true表示RTCP复用同一socket应答】
                // 见1.1.2小节分析
                rtp_add_sink( id, fd, p_sys->rtcp_mux, NULL );
                /* FIXME: test if this is multicast  */
                // 是否为多播接口套接字描述符
                mcast_fd = fd;
            }
        }
    }

    // p_fmt参数不为空时
    // [rtp_set_ptime]该方法将MTU缩小到一个固定的分组时间(用于音频)。
    if( p_fmt != NULL )
    switch( p_fmt->i_codec )
    {
        case VLC_CODEC_MULAW:
        case VLC_CODEC_ALAW:
        case VLC_CODEC_U8:
            // 参数20表示20毫秒,最后参数1表示位深
            rtp_set_ptime (id, 20, 1);
            break;
        case VLC_CODEC_S16B:
        case VLC_CODEC_S16L:
            rtp_set_ptime (id, 20, 2);
            break;
        case VLC_CODEC_S24B:
            rtp_set_ptime (id, 20, 3);
            break;
        default:
            break;
    }

#if 0 /* No payload formats sets this at the moment */
    int cscov = -1;
    if( cscov != -1 )
        cscov += 8 /* UDP */ + 12 /* RTP */;
    if( id->sinkc > 0 )
        net_SetCSCov( id->sinkv[0].rtp_fd, cscov, -1 );
#endif

    vlc_mutex_lock( &p_sys->lock_ts );
    // NPT时间是否已初始化
    id->b_ts_init = ( p_sys->i_npt_zero != VLC_TS_INVALID );
    vlc_mutex_unlock( &p_sys->lock_ts );
    if( id->b_ts_init )
        // 已初始化则计算TS时间偏移量
        // 见下面的分析
        id->i_ts_offset = rtp_compute_ts( id->rtp_fmt.clock_rate,
                                          p_sys->i_pts_offset );

    if( p_sys->rtsp != NULL )
        // RTSP流对象不为空时,GetDWBE作用为将主机字节顺序【ssrc】转为32位表示的网络字节顺序
        // 见1.5小节分析
        id->rtsp_id = RtspAddId( p_sys->rtsp, id, GetDWBE( id->ssrc ),
                                 id->rtp_fmt.clock_rate, mcast_fd );

    id->p_fifo = block_FifoNew();
    if( unlikely(id->p_fifo == NULL) )
        goto error;
    if( vlc_clone( &id->thread, ThreadSend, id, VLC_THREAD_PRIORITY_HIGHEST ) )
    {
        block_FifoRelease( id->p_fifo );
        id->p_fifo = NULL;
        goto error;
    }

    /* Update p_sys context */
    vlc_mutex_lock( &p_sys->lock_es );
    TAB_APPEND( p_sys->i_es, p_sys->es, id );
    vlc_mutex_unlock( &p_sys->lock_es );

    psz_sdp = SDPGenerate( p_stream, NULL );

    vlc_mutex_lock( &p_sys->lock_sdp );
    free( p_sys->psz_sdp );
    p_sys->psz_sdp = psz_sdp;
    vlc_mutex_unlock( &p_sys->lock_sdp );

    msg_Dbg( p_stream, "sdp=\n%s", p_sys->psz_sdp );

    /* Update SDP (sap/file) */
    if( p_sys->b_export_sap ) SapSetup( p_stream );
    if( p_sys->psz_sdp_file != NULL ) FileSetup( p_stream );

    return id;

error:
    Del( p_stream, id );
    return NULL;
}

// 【vlc/modules/stream_output/rtp.c】
uint32_t rtp_compute_ts( unsigned i_clock_rate, int64_t i_pts )
{
    /* This is an overflow-proof way of doing:
     * return i_pts * (int64_t)i_clock_rate / CLOCK_FREQ;
     *
     * NOTE: this plays nice with offsets because the (equivalent)
     * calculations are linear. */
     // NOTE: 使用PTS偏移量比较好的播放处理,因为(等效)计算是线性的。
     // lldiv: 同时计算商(表达式x / y的结果)和余数(表达式x%y的结果)。
    lldiv_t q = lldiv(i_pts, CLOCK_FREQ);
    return q.quot * (int64_t)i_clock_rate
          + q.rem * (int64_t)i_clock_rate / CLOCK_FREQ;
}

1.1、vod_init_id实现分析:

// 【vlc/modules/stream_output/vod.c】
/* Match an RTP id to a VoD media ES and RTSP track to initialize it
 * with the data that was already set up */
int vod_init_id(vod_media_t *p_media, const char *psz_session, int es_id,
                sout_stream_id_sys_t *sout_id, rtp_format_t *rtp_fmt,
                uint32_t *ssrc, uint16_t *seq_init)
{
    // es媒体数据
    media_es_t *p_es;

    if (p_media->psz_mux != NULL)
    {// 有ES复用封装格式该值时,只有一个es媒体数据
        assert(p_media->i_es == 1);
        p_es = p_media->es[0];
    }
    else
    {
        // 不加锁,匹配当前es_id获取当前对应的es out数据即音频、视频或字幕流
        p_es = NULL;
        /* No locking needed, the ES table can't be modified now */
        for (int i = 0; i < p_media->i_es; i++)
        {
            if (p_media->es[i]->es_id == es_id)
            {
                p_es = p_media->es[i];
                break;
            }
        }
        if (p_es == NULL)
            return VLC_EGENERIC;
    }

    // 复制给参数字段
    memcpy(rtp_fmt, &p_es->rtp_fmt, sizeof(*rtp_fmt));
    if (p_es->rtp_fmt.fmtp != NULL)
        rtp_fmt->fmtp = strdup(p_es->rtp_fmt.fmtp);

    // RTSP追踪关联RTP流数据
    return RtspTrackAttach(p_media->rtsp, psz_session, p_es->rtsp_id,
                           sout_id, ssrc, seq_init);
}

// 【vlc/modules/stream_output/rtsp.c】
// 注译:将一个启动VoD RTP id附加到它的RTSP轨道上,并让它使用【SETUP】请求的参数进行初始化
/* Attach a starting VoD RTP id to its RTSP track, and let it
 * initialize with the parameters of the SETUP request */
int RtspTrackAttach( rtsp_stream_t *rtsp, const char *name,
                     rtsp_stream_id_t *id, sout_stream_id_sys_t *sout_id,
                     uint32_t *ssrc, uint16_t *seq_init )
{
    int val = VLC_EGENERIC;
    // RTSP unicast单播流session会话信息
    rtsp_session_t *session;

    vlc_mutex_lock(&rtsp->lock);
    // 获取rtsp客户端session会话信息
    // 见1.1.1小节分析
    session = RtspClientGet(rtsp, name);

    if (session == NULL)
        goto out;

    rtsp_strack_t *tr = NULL;
    // trackc表示输出访问ID
    for (int i = 0; i < session->trackc; i++)
    {
        // RTSP单播会话轨道track信息中id和rtsp流ID对象中的id相同时赋值
        if (session->trackv[i].id == id)
        {
            tr = session->trackv + i;
            break;
        }
    }

    if (tr != NULL)
    {
        tr->sout_id = sout_id;
        // 【rtp_fd】为播放时被RTP输出端使用的socket,
        // 【setup_fd】为【SETUP】请求时创建的socket
        // 此处处理为:复制socket,获取套接字描述符
        // 注:内部调用fcntl【系统调用】(根据文件描述符来操作文件的特性)
        // 针对(文件)描述符提供控制,fcntl函数来加锁文件
        tr->rtp_fd = dup_socket(tr->setup_fd);
    }
    else
    {// RTSP会话轨道没有【SETUP】请求,则新建一个
        /* The track was not SETUP. We still create one because we'll
         * need the sout_id if we set it up later. */
        rtsp_strack_t track = { .id = id, .sout_id = sout_id,
                                .setup_fd = -1, .rtp_fd = -1 };
        vlc_rand_bytes (&track.seq_init, sizeof (track.seq_init));
        vlc_rand_bytes (&track.ssrc, sizeof (track.ssrc));

        // 将当前RTSP会话轨道track对象加入【RTSP unicast单播流session会话信息】的列表字段中
        TAB_APPEND(session->trackc, session->trackv, track);
        // 将该数据列表中新入的最后一个track赋值
        tr = session->trackv + session->trackc - 1;
    }

    // ntohl函数:将一个无符号长整形数从网络字节顺序转换为主机字节顺序。[netlong hostlong]
    *ssrc = ntohl(tr->ssrc);
    // RTSP包序列号【随机生成】
    *seq_init = tr->seq_init;

    if (tr->rtp_fd != -1)
    {
        // rtp流数据包序列号
        uint16_t seq;
        // rtp流添加到数据输出端
        // 见1.1.2小节分析
        rtp_add_sink(tr->sout_id, tr->rtp_fd, false, &seq);
        /* To avoid race conditions, sout_id->i_seq_sent_next must
         * be set here and now. Make sure the caller did its job
         * properly when passing seq_init. */
        assert(tr->seq_init == seq);
    }

    val = VLC_SUCCESS;
out:
    vlc_mutex_unlock(&rtsp->lock);
    return val;
}

1.1.1、RtspClientGet实现分析:

// 【vlc/modules/stream_output/rtsp.c】
/** rtsp must be locked */
static
rtsp_session_t *RtspClientGet( rtsp_stream_t *rtsp, const char *name )
{
    char *end;
    uint64_t id;
    int i;

    // 有上面分析可知,name表示session字符串值
    if( name == NULL )
        return NULL;

    errno = 0;
    // 转换成 unsigted long long类型的数值
    id = strtoull( name, &end, 0x10 );
    if( errno || *end )
        return NULL;

    // 找到当前rtsp中同一个session id的对应对象返回
    /* FIXME: use a hash/dictionary */
    for( i = 0; i < rtsp->sessionc; i++ )
    {
        if( rtsp->sessionv[i]->id == id )
            return rtsp->sessionv[i];
    }
    return NULL;
}

1.1.2、rtp_add_sink实现分析:

// 【vlc/modules/stream_output/rtp.c】
int rtp_add_sink( sout_stream_id_sys_t *id, int fd, bool rtcp_mux, uint16_t *seq )
{
    // rtp流输出【发送】信息
    rtp_sink_t sink = { fd, NULL };
    // 创建RTCP控制发送端对象并打开,使用UDP传输层协议
    // 见下面的分析
    sink.rtcp = OpenRTCP( VLC_OBJECT( id->p_stream ), fd, IPPROTO_UDP,
                          rtcp_mux );
    if( sink.rtcp == NULL )
        msg_Err( id->p_stream, "RTCP failed!" );

    vlc_mutex_lock( &id->lock_sink );
    // 保存rtp流数据包输出信息对象到对应列表中
    TAB_APPEND(id->sinkc, id->sinkv, sink);
    if( seq != NULL )
        // 将RTSP数据包的序列号赋值给变量返回
        *seq = id->i_seq_sent_next;
    vlc_mutex_unlock( &id->lock_sink );
    return VLC_SUCCESS;
}

// 【vlc/modules/stream_output/rtcp.c】
rtcp_sender_t *OpenRTCP (vlc_object_t *obj, int rtp_fd, int proto,
                         bool mux)
{
    rtcp_sender_t *rtcp;
    uint8_t *ptr;
    // 表示套接字描述符
    int fd;
    // 从后续分析可知是保存的IP地址字符串: 如"127.0.0.1:6563'
    // 网络接口最大数字主机【max numeric host】
    char src[NI_MAXNUMERICHOST];
    // RTCP发送端sock端口号
    int sport;

    // 获取本机协议地址网络IP地址和端口
    // 见下面的分析
    if (net_GetSockAddress (rtp_fd, src, &sport))
        return NULL;

    // RTP/RTCP mux复用功能作用:根据其功能处理可知,
    // 该参数是用于是否RTP和RTCP使用同一个IP端口,true则使用同一个端口号
    if (mux)
    {// 若需要RTCP进行复用封装处理,则复制socket,
    // 即在同一个端口号上进行传输数据【如同一个客户端网络请求进行应答时】
        /* RTP/RTCP mux: duplicate the socket */
#ifndef _WIN32
        // 此处处理为:复制socket,获取套接字描述符
        // 注:内部调用fcntl【系统调用】(根据文件描述符来操作文件的特性)
        // 针对(文件)描述符提供控制,fcntl函数来加锁文件
        fd = vlc_dup (rtp_fd);
#else
        WSAPROTOCOL_INFO info;
        WSADuplicateSocket (rtp_fd, GetCurrentProcessId (), &info);
        fd = WSASocket (info.iAddressFamily, info.iSocketType, info.iProtocol,
                        &info, 0, 0);
#endif
    }
    else
    {// RTCP运行在不同的端口号上
        /* RTCP on a separate port */
        // 客户端网络IP地址
        char dst[NI_MAXNUMERICHOST];
        // 客户端IP端口号
        int dport;

        // 获取客户端协议地址网络IP地址和端口
        if (net_GetPeerAddress (rtp_fd, dst, &dport))
            return NULL;

        // 将本地端口和客户端端口均增加1
        sport++;
        dport++;

        // 使用当前本地和客户端的IP地址、端口号和传输层协议【如UDP】,来打开UDP数据报网络链接
        // 并返回套接字描述符
        // 见1.1.2.1小节分析
        fd = net_OpenDgram (obj, src, sport, dst, dport, proto);
        if (fd != -1)
        {
            // TTL表示Time to live即网络生存时间值
            // 【该字段指定IP包被路由器丢弃之前允许通过的最大网段数量。TTL是IPv4报头的一个8 bit字段。
            // 注意:TTL与DNS TTL有区别。二者都是生存时间,
            // 前者指ICMP包的转发次数(跳数),后者指域名解析信息在DNS中的存在时间】
            /* Copy the multicast IPv4 TTL value (useless for IPv6) */
            int ttl;
            socklen_t len = sizeof (ttl);

            // 【rtp_fd】为播放时被RTP输出端使用的socket描述符
            // getsockopt()函数用于获取任意类型、任意状态套接口的选项当前值,并把结果存入optval。
            if (!getsockopt (rtp_fd, SOL_IP, IP_MULTICAST_TTL, &ttl, &len))
                // 获取成功,则根据播放时RTP输出端socket的TTL值设置给当前不同端口的RTCP套接字相同使用即可
                setsockopt (fd, SOL_IP, IP_MULTICAST_TTL, &ttl, len);

            // 此处设置RTCP端口套接字网络上忽略所有传入的RTCP-RR数据包
            // RR--Receiver Reports 接收者报告
            /* Ignore all incoming RTCP-RR packets */
            setsockopt (fd, SOL_SOCKET, SO_RCVBUF, &(int){ 0 }, sizeof (int));
        }
    }

    if (fd == -1)
        return NULL;

    rtcp = malloc (sizeof (*rtcp));
    if (rtcp == NULL)
    {// 关闭套接字连接
        net_Close (fd);
        return NULL;
    }

    // RTCP socket处理者即保存的是套接字描述符,此处为IP地址转换后的长整形无符号数值
    rtcp->handle = fd;
    // 初始化已发送RTP数据包个数、已发送RTP数据字节大小,和从最后一个RTCP包发送的RTP包计数值【counter】
    rtcp->bytes = rtcp->packets = rtcp->counter = 0;

    // 获取%字符之后的字符串【包括%号】
    ptr = (uint8_t *)strchr (src, '%');
    if (ptr != NULL)
        *ptr = '\0'; /* remove scope ID frop IPv6 addresses */

    // RTCP负载数据信息
    ptr = rtcp->payload;

    // 【SR】数据报文信息
    /* Sender report */
    ptr[0] = 2 << 6; /* V = 2, P = RC = 0 */
    ptr[1] = 200; /* payload type: Sender Report */
    // 将值的主机字节顺序转换为网络字节顺序
    SetWBE (ptr + 2, 6); /* length = 6 (7 double words) */
    // SSRC值未知,因此该4个字符内存初始化为0
    memset (ptr + 4, 0, 4); /* SSRC unknown yet */
    // 【RTSP中NPT时间(Normal Play Time) 正常播放时间】
    // 将该时间值转换为网络字节顺序值
    SetQWBE (ptr + 8, NTPtime64 ());
    memset (ptr + 16, 0, 12); /* timestamp and counters */
    // 指针加整数,此处为指针指向向后移动28个字节【ptr指针元素类型大小为8位】
    ptr += 28;

    // 【SDES】数据报文信息
    /* Source description */
    uint8_t *sdes = ptr;
    ptr[0] = (2 << 6) | 1; /* V = 2, P = 0, SC = 1 */
    ptr[1] = 202; /* payload type: Source Description */
    uint8_t *lenptr = ptr + 2;
    memset (ptr + 4, 0, 4); /* SSRC unknown yet */
    ptr += 8;

    ptr[0] = 1; /* CNAME - mandatory */
    assert (NI_MAXNUMERICHOST <= 256);
    // 本机IP地址字符长度
    ptr[1] = strlen (src);
    // 然后保存IP地址字符到ptr中
    memcpy (ptr + 2, src, ptr[1]);
    // 跳过地址长度字节数后之后再跳过2字节
    ptr += ptr[1] + 2;

    // 定义的数据包全名和版本,如:
    // #define PACKAGE_STRING "vlc 3.0.11.1"
    static const char tool[] = PACKAGE_STRING;
    ptr[0] = 6; /* TOOL */
    ptr[1] = (sizeof (tool) > 256) ? 255 : (sizeof (tool) - 1);
    memcpy (ptr + 2, tool, ptr[1]);
    ptr += ptr[1] + 2;

    // 将数据包数据位数对齐为32位,因此若前两位二进制值
    while ((ptr - sdes) & 3) /* 32-bits padding */
        *ptr++ = 0;
    // 注意:此处有个指针向右位移2运算,其实就是除以4之后的商值
    SetWBE (lenptr, (ptr - sdes - 1) >> 2);

    // 保存RTCP数据包总长度
    rtcp->length = ptr - rtcp->payload;
    return rtcp;
}

// [vlc/include/vlc-network.h]
static inline int net_GetSockAddress( int fd, char *address, int *port )
{// 从SOCKADDR_STORAGE结构中获取客户端IP地址和端口
    struct sockaddr_storage addr;
    socklen_t addrlen = sizeof( addr );

    // fd参数表示:套接字描述符
    
    // 通过套接字描述符来获取自己的IP地址和连接对端的IP地址,
    // 如在未调用bind函数的TCP客户端程序上,
    // 可以通过调用getsockname()函数获取由内核赋予该连接的本地IP地址和本地端口号,
    // 还可以在TCP的服务器端accept成功后,通过getpeername()函数来获取当前连接的客户端的IP地址和端口号。

    // [getpeername]获取与套接口相连的端地址:从端口s中获取与它捆绑的端口名,
    // 并把它存放在sockaddr类型的name结构中。它适用于数据报或流类套接口。
    // 返回与某个套接字关联的本地协议地址(getsockname),
    // 返回与某个套接字关联的外地协议地址即得到对方的地址(getpeername)
    // getnameinfo函数:以一个套接口地址为参数,返回一个描述主机的字符串和一个描述服务的字符串
    return getsockname( fd, (struct sockaddr *)&addr, &addrlen )
        || vlc_getnameinfo( (struct sockaddr *)&addr, addrlen, address,
                            NI_MAXNUMERICHOST, port, NI_NUMERICHOST )
        ? VLC_EGENERIC : 0;
}

// [vlc/include/vlc-network.h]
static inline int net_GetPeerAddress( int fd, char *address, int *port )
{// 从SOCKADDR_STORAGE结构中获取客户端IP地址和端口
    struct sockaddr_storage addr;
    socklen_t addrlen = sizeof( addr );
    // 返回与某个套接字关联的外地协议地址即得到对方的地址(getpeername)
    return getpeername( fd, (struct sockaddr *)&addr, &addrlen )
        || vlc_getnameinfo( (struct sockaddr *)&addr, addrlen, address,
                            NI_MAXNUMERICHOST, port, NI_NUMERICHOST )
        ? VLC_EGENERIC : 0;
}

1.1.2.1、net_OpenDgram实现分析:

// [vlc/src/network/udp.c]
int net_OpenDgram( vlc_object_t *obj, const char *psz_bind, int i_bind,
                   const char *psz_server, int i_server, int protocol )
{
    if ((psz_server == NULL) || (psz_server[0] == '\0'))
        // 若客户端IP地址不存在则只打开服务器进行单独监听【客户端请求】
        return net_ListenSingle (obj, psz_bind, i_bind, protocol);

    // 本地服务器连接客户端处理
    
    msg_Dbg (obj, "net: connecting to [%s]:%d from [%s]:%d",
             psz_server, i_server, psz_bind, i_bind);

    // 地址信息【local/remote】
    struct addrinfo hints = {
        .ai_socktype = SOCK_DGRAM,
        .ai_protocol = protocol,
        .ai_flags = AI_NUMERICSERV | AI_IDN,
    }, *loc, *rem;

    // 解析客户端网络地址获取IP地址信息等
    int val = vlc_getaddrinfo (psz_server, i_server, &hints, &rem);
    if (val)
    {
        msg_Err (obj, "cannot resolve %s port %d : %s", psz_server, i_server,
                 gai_strerror (val));
        return -1;
    }

    hints.ai_flags |= AI_PASSIVE;
    // 解析本地网络地址获取IP地址相关信息
    val = vlc_getaddrinfo (psz_bind, i_bind, &hints, &loc);
    if (val)
    {
        msg_Err (obj, "cannot resolve %s port %d : %s", psz_bind, i_bind,
                 gai_strerror (val));
        freeaddrinfo (rem);
        return -1;
    }

    val = -1;
    // 先循环处理本地可用网络地址信息并尝试打开套接字连接
    for (struct addrinfo *ptr = loc; ptr != NULL; ptr = ptr->ai_next)
    {
        // 前面已分析过:打开socket连接
        int fd = net_Socket (obj, ptr->ai_family, ptr->ai_socktype,
                             ptr->ai_protocol);
        if (fd == -1)
            continue; // usually, address family not supported

        // 前面已分析过:设置socket连接选项并调用bind方法进行socket和地址信息绑定
        fd = net_SetupDgramSocket( obj, fd, ptr );
        if( fd == -1 )
            continue;

        // 然后进行客户端可用地址的处理
        for (struct addrinfo *ptr2 = rem; ptr2 != NULL; ptr2 = ptr2->ai_next)
        {
            // 重点:必须客户端的协议族、socket类型、协议类型均相同才能进行互联
            if ((ptr2->ai_family != ptr->ai_family)
             || (ptr2->ai_socktype != ptr->ai_socktype)
             || (ptr2->ai_protocol != ptr->ai_protocol))
                continue;

            // 若当前打开的本地socket地址为多播地址,则尝试将当前本地地址加入到多播组,
            // 若失败【客户端地址不能加入本地服务端多播组网络】则重新下一个客户端地址链接;
            // 若不为多播地址,则执行connect链接处理
            // 【connect】方法为socket连接方法:创建与指定外部端口的连接,
            // 将参数sockfd 的socket 连至参数serv_addr 指定的网络地址
            if (net_SockAddrIsMulticast (ptr->ai_addr, ptr->ai_addrlen)
              ? net_SourceSubscribe (obj, fd,
                                     ptr2->ai_addr, ptr2->ai_addrlen,
                                     ptr->ai_addr, ptr->ai_addrlen)
              : connect (fd, ptr2->ai_addr, ptr2->ai_addrlen))
            {
                msg_Err (obj, "cannot connect to %s port %d: %s",
                         psz_server, i_server, vlc_strerror_c(net_errno));
                continue;
            }
            val = fd;
            break;
        }

        if (val != -1)
        // 服务器端和客户端连接成功则退出
            break;

        net_Close (fd);
    }

    freeaddrinfo (rem);
    freeaddrinfo (loc);
    return val;
}

1.1.2.1、net_ListenSingle实现分析:【本地IP地址和端口号】
由于章节篇幅长度问题,见下一小章节分析
【十五】【vlc-android】vlc-sout流媒体输出端源码实现分析【Part 3】【02】