天天看點

【十五】【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流媒體處理部分會延緩更新】

繼續閱讀