srs源码解析2【http-flv播放】

目录

前言:

一、SRS中的http处理套路

二、http-flv播放详情

三、总结


前言:

http-flv和hls都是基于http协议的。在SRS中,http的处理借鉴了golang的http处理架构,但是也有一些冗余的信息。本节,主要解析http-flv播放并触发回源的流程,以及与flv协议相关的内容。

一、SRS中的http处理套路

1、ISrsHttpServeMux

       这是一个虚类,实际上就是路由器接口。和golang一样,每个http-server都有一个路由器,收到请求后,会根据请求url去ISrsHttpServeMux去找到对应的handler(即处理器),然后由hander去处理请求。

// http请求的路由
class ISrsHttpServeMux
{
public:
    ISrsHttpServeMux();
    virtual ~ISrsHttpServeMux();
public:
    virtual int serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessage* r) = 0;
};

     SRS中处理http-flv播放请求的路由器是SrsHttpServeMux ,其包括两个重要的集合成员:

     1、entries是一个map映射,key是请求url,value是包括处理器和流信息的对象,这里可以简单理解为一个处理器

     2、hijackers是劫持器集合。请求来了,首先去entries找对应的处理器,如果找不到处理器,则会一一调用hijackers中的劫持器去生成一个处理器,并且只有第一个生成处理器会用来处理本次请求。前面说到,每个http-server都有一个路由器,在SRS中,一般路由器中的hijackers实际上包括http-server,也就是说http-server具备为新请求生成处理器的能力。

      然后介绍一下SrsHttpServeMux中的成员方法:

      1、bool can_serve(ISrsHttpMessage* r)

      先在entries找对应的处理器,如果找不到就会尝试调用hijackers去生成一个处理器(其实在hijackers中不仅是能生成处理器,而且会初始化流相关的信息,比如SrsSource)。如果配置中对应域名下有和请求匹配的flv模版,则会生成对应的处理器。处理器生成成功,就会把r和处理器注册到entries中,然后返回true。

       2、int serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessage* r)

      一般调用can_serve返回true后,就会调用serve_http来处理请求,参数w用于写数据给客户端,r代表请求。can_serve会首先从entries中获取处理器,然后处理请求。

class SrsHttpServeMux : public ISrsHttpServeMux
{
private:
    std::map entries;// 已经处理过的具体请求url在这里有处理器
    std::map vhosts;// 并没有什么卵用
    std::vector hijackers;// 请求劫持器
public:
    SrsHttpServeMux();
    virtual ~SrsHttpServeMux();
public:
    virtual int initialize();
    virtual void hijack(ISrsHttpMatchHijacker* h);// 注册劫持器
    virtual void unhijack(ISrsHttpMatchHijacker* h);// 卸载劫持器
public:
    virtual int handle(std::string pattern, ISrsHttpHandler* handler);// 挂载处理器
    virtual bool can_serve(ISrsHttpMessage* r);// 判断是否可以处理该请求
public:
    virtual int serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessage* r);// 处理请求
private:
    virtual int find_handler(ISrsHttpMessage* r, ISrsHttpHandler** ph);// 查找匹配的handler
    virtual int match(ISrsHttpMessage* r, ISrsHttpHandler** ph);
    virtual bool path_match(std::string pattern, std::string path);
};

2、流处理器的架构

srs源码解析2【http-flv播放】_第1张图片

注:1)SrsHttpServer也是一个路由处理器类型

        2)图中SrsHttpMuxEntry是在SrsHttpServeMux中由map管理,key是请求url,value是SrsHttpMuxEntry

        3)SrsHttpStreamServer和SrsHttpStaticServer分别是SrsHttpServer中的两个处理器,前者处理http动态请求,后者处理http静态请求。http-flv请求由SrsHttpStreamServer处理

        4)请求到SrsHttpStreamServer后,会首先在其包含的SrsHttpServeMux中的map对象entries中查找请求url对应的SrsHttpMuxEntry,如果找到,就会使用SrsHttpMuxEntry中的处理器,也就是SrsLiveStream的serve_http方法去处理请求

        5)如果在SrsHttpServeMux的map对象entries中没有找到请求url对应的SrsHttpMuxEntry,则会在SrsHttpStreamServer的hijack方法中出生成一个处理器,并且生成对应的SrsSource和回源对象,触发回源。原生srs回源模式只支持rtmp协议的回源。

二、http-flv播放详情

        从上一节中,我们对整个flv请求的流程有了初步的了解。其中,生成处理器、触发回源是在SrsHttpStreamServer的方法hijack中处理的。如果已经有对应的处理器,则会在SrsLiveStream的方法serve_http中处理请求。所以,下面将重点解析一下这两个方法。

1、SrsHttpStreamServer::hijack

int SrsHttpStreamServer::hijack(ISrsHttpMessage* request, ISrsHttpHandler** ph)
{
// 能到这里来,说明request在sflvs中没有找到对应的处理器,一般情况就要期望激发回源或转封装
    int ret = ERROR_SUCCESS;
    
    // when handler not the root, we think the handler is ok.
    ISrsHttpHandler* h = *ph? *ph : NULL;
    if (h && h->entry && h->entry->pattern != "/") {
        return ret;
    }
    
    // only hijack for http streaming, http-flv/ts/mp3/aac.
    // 查看请求的后缀
    std::string ext = request->ext();
    if (ext.empty()) {
        return ret;
    }
    
    // find the actually request vhost.
    SrsConfDirective* vhost = _srs_config->get_vhost(request->host());
    if (!vhost || !_srs_config->get_vhost_enabled(vhost)) {
        return ret;
    }
    
    // find the entry template for the stream.
    SrsLiveEntry* entry = NULL;
    if (true) {
        // no http streaming on vhost, ignore.
        std::map::iterator it = tflvs.find(vhost->arg0());
        if (it == tflvs.end()) {
            return ret;
        }
        
        // hstrs not enabled, ignore.
        // for origin, the http stream will be mount already when publish,
        //      so it must never enter this line for stream already mounted.
        // for edge, the http stream is trigger by hstrs and mount by it,
        //      so we only hijack when only edge and hstrs is on.
        // hstrs表示有请求时是否触发rtmp回源
        entry = it->second;
        if (!entry->hstrs) {
            return ret;
        }

        // check entry and request extension.
        if (entry->is_flv()) {
            if (ext != ".flv") {
                return ret;
            }
        } else if (entry->is_ts()) {
            if (ext != ".ts") {
                return ret;
            }
        } else if (entry->is_mp3()) {
            if (ext != ".mp3") {
                return ret;
            }
        } else if (entry->is_aac()) {
            if (ext != ".aac") {
                return ret;
            }
        } else {
            return ret;
        }
    }
    
    // convert to concreate class.
    SrsHttpMessage* hreq = dynamic_cast(request);
    srs_assert(hreq);
    
    // hijack for entry.
    // 把客户的请求转化为对应的rtmp回源请求(这样用这个生成的请求r就可以去第三方回源了(但是这里的r中的tcurl是错的))
    SrsRequest* r = hreq->to_request(vhost->arg0());
    SrsAutoFree(SrsRequest, r);

	// 这里的sid就是rtmp请求了
    std::string sid = r->get_stream_url();
    // check whether the http remux is enabled,
    // for example, user disable the http flv then reload.
    if (sflvs.find(sid) != sflvs.end()) {
        SrsLiveEntry* s_entry = sflvs[sid];
        if (!s_entry->stream->entry->enabled) {
            // only when the http entry is disabled, check the config whether http flv disable,
            // for the http flv edge use hijack to trigger the edge ingester, we always mount it
            // eventhough the origin does not exists the specified stream.
            if (!_srs_config->get_vhost_http_remux_enabled(r->vhost)) {
                srs_error("stream is disabled, hijack failed. ret=%d", ret);
                return ret;
            }
        }
    }

	// 激发新的回源,能到这里说明请求对应的SrsSource不存在,而且配置的该请求可以触发回源
    SrsSource* s = NULL;
    if ((ret = SrsSource::fetch_or_create(r, server, &s)) != ERROR_SUCCESS) {
        return ret;
    }
    srs_assert(s != NULL);

    // create http streaming handler.
    if ((ret = http_mount(s, r)) != ERROR_SUCCESS) {
        return ret;
    }

    // use the handler if exists.
    if (ph) {
        if (sflvs.find(sid) != sflvs.end()) {
            entry = sflvs[sid];
            *ph = entry->stream;
        }
    }
    
    // trigger edge to fetch from origin.
    bool vhost_is_edge = _srs_config->get_vhost_is_edge(r->vhost);
    srs_trace("hstrs: source url=%s, is_edge=%d, source_id=%d[%d]",
        r->get_stream_url().c_str(), vhost_is_edge, s->source_id(), s->source_id());
    
    return ret;
}

      如果在SrsHttpStreamServer的SrsHttpServeMux中的路由映射表entries中,没有找到请求url(将播放域名替换成vhost)对应的处理器,就会进入SrsHttpStreamServer::hijack,创建处理器,并促发回源。下面分析hijack做了什么:

      (1)判断*ph是否为空,这里是确认调用hijack前是否已经找到了对应的处理器,如果找到了,就直接退出了

      (2)查看请求url中的协议后缀是不是配置匹配,实际上是找对应域名下的http_remux{mount}配置中的后缀是否和请求的后缀一致。实际上这里是可以扩展的,比如可以让一个域名支持多钟http协议的播放(比如 flv和hls,例如使用hlsflv)。

      (3)获取SrsSource,如果SrsSource的回源器没有回源,则触发回源

2、SrsLiveStream::serve_http

int SrsLiveStream::serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessage* r)
{
    int ret = ERROR_SUCCESS;
    
    ISrsStreamEncoder* enc = NULL;
    
    srs_assert(entry);
    if (srs_string_ends_with(entry->pattern, ".flv")) {
        w->header()->set_content_type("video/x-flv");
#ifdef SRS_PERF_FAST_FLV_ENCODER
        enc = new SrsFastFlvStreamEncoder();
#else
        enc = new SrsFlvStreamEncoder();
#endif
    } else if (srs_string_ends_with(entry->pattern, ".aac")) {
        w->header()->set_content_type("audio/x-aac");
        enc = new SrsAacStreamEncoder();
    } else if (srs_string_ends_with(entry->pattern, ".mp3")) {
        w->header()->set_content_type("audio/mpeg");
        enc = new SrsMp3StreamEncoder();
    } else if (srs_string_ends_with(entry->pattern, ".ts")) {
        w->header()->set_content_type("video/MP2T");
        enc = new SrsTsStreamEncoder();
    } else {
        ret = ERROR_HTTP_LIVE_STREAM_EXT;
        srs_error("http: unsupported pattern %s", entry->pattern.c_str());
        return ret;
    }
    SrsAutoFree(ISrsStreamEncoder, enc);
    
    // create consumer of souce, ignore gop cache, use the audio gop cache.
    SrsConsumer* consumer = NULL;
	// create_consumer的第一个参数是表示和播放器的连接,这里不传连接是因为,我们这里的逻辑只需要source将msg下发到
	// 这个consumer的msg队列中,剩下转封装和发送,由我们的serve_http来做
    if ((ret = source->create_consumer(NULL, consumer, true, true, !enc->has_cache())) != ERROR_SUCCESS) {
        srs_error("http: create consumer failed. ret=%d", ret);
        return ret;
    }
    SrsAutoFree(SrsConsumer, consumer);
    srs_verbose("http: consumer created success.");

    SrsPithyPrint* pprint = SrsPithyPrint::create_http_stream();
    SrsAutoFree(SrsPithyPrint, pprint);
    
    SrsMessageArray msgs(SRS_PERF_MW_MSGS);
    
    // the memory writer.
    SrsStreamWriter writer(w);// 对SrsHttpResponseWriter的简单封装,SrsHttpResponseWriter是对网络连接描述符返回http响应
    if ((ret = enc->initialize(&writer, cache)) != ERROR_SUCCESS) {
        srs_error("http: initialize stream encoder failed. ret=%d", ret);
        return ret;
    }
    
    // if gop cache enabled for encoder, dump to consumer.
    // flv忽略这个
    if (enc->has_cache()) {// flv这里始终是false
        if ((ret = enc->dump_cache(consumer, source->jitter())) != ERROR_SUCCESS) {
            srs_error("http: dump cache to consumer failed. ret=%d", ret);
            return ret;
        }
    }
    
#ifdef SRS_PERF_FAST_FLV_ENCODER
    SrsFastFlvStreamEncoder* ffe = dynamic_cast(enc);
#endif
    
    // Use receive thread to accept the close event to avoid FD leak.
    // @see https://github.com/ossrs/srs/issues/636#issuecomment-298208427
    SrsHttpMessage* hr = dynamic_cast(r);
    SrsResponseOnlyHttpConn* hc = dynamic_cast(hr->connection());
    
    SrsHttpRecvThread* trd = new SrsHttpRecvThread(hc);
    SrsAutoFree(SrsHttpRecvThread, trd);
    
    if ((ret = trd->start()) != ERROR_SUCCESS) {
        srs_error("http: start notify thread failed, ret=%d", ret);
        return ret;
    }

    // TODO: free and erase the disabled entry after all related connections is closed.
    while (entry->enabled) {
        pprint->elapse();
        
        // Whether client closed the FD.
        if ((ret = trd->error_code()) != ERROR_SUCCESS) {
            return ret;
        }

        // get messages from consumer.
        // each msg in msgs.msgs must be free, for the SrsMessageArray never free them.
        int count = 0;
        if ((ret = consumer->dump_packets(&msgs, count)) != ERROR_SUCCESS) {
            srs_error("http: get messages from consumer failed. ret=%d", ret);
            return ret;
        }
        
        if (count <= 0) {
            srs_info("http: sleep %dms for no msg", SRS_CONSTS_RTMP_PULSE_TIMEOUT_US);
            // directly use sleep, donot use consumer wait.
            st_usleep(SRS_CONSTS_RTMP_PULSE_TIMEOUT_US);
            
            // ignore when nothing got.
            continue;
        }

        if (pprint->can_print()) {
            srs_info("-> "SRS_CONSTS_LOG_HTTP_STREAM" http: got %d msgs, age=%d, min=%d, mw=%d", 
                count, pprint->age(), SRS_PERF_MW_MIN_MSGS, SRS_CONSTS_RTMP_PULSE_TIMEOUT_US / 1000);
        }
        
        // sendout all messages.
        // 从consumer的msg队列中取出msg转封装,然后发送给播放器
#ifdef SRS_PERF_FAST_FLV_ENCODER
        if (ffe) {
            ret = ffe->write_tags(msgs.msgs, count);
        } else {
            ret = streaming_send_messages(enc, msgs.msgs, count);
        }
#else
        ret = streaming_send_messages(enc, msgs.msgs, count);
#endif
    
        // free the messages.
        for (int i = 0; i < count; i++) {
            SrsSharedPtrMessage* msg = msgs.msgs[i];
            srs_freep(msg);
        }
        
        // check send error code.
        // 检测发送错误
        if (ret != ERROR_SUCCESS) {
            if (!srs_is_client_gracefully_close(ret)) {
                srs_error("http: send messages to client failed. ret=%d", ret);
            }
            return ret;
        }
    }
    
    return ret;
}

收到flv播放请求后,会生成一个连接对象SrsResponseOnlyHttpConn,然后调用其父类SrsHttpConn的方法process_request处理请求,接着该方法中会调用SrsServer中的http流处理器http_server的serve_http(SrsHttpServer类型对象)去处理请求。在SrsHttpServer收到请求后,会首先调用其动态资源处理器SrsHttpStreamServer的路由器SrsHttpServeMux的serve_http方法去处理。如果SrsHttpServeMux找不到对应的处理器就会调用SrsHttpStreamServer的hijack去生成处理器,并且触发回源;否则获取处理器SrsLiveStream的serve_http方法去处理请求。下面来分析一下SrsLiveStream::serve_http做了什么。

        (1)首先根据请求的后缀创建一个ISrsStreamEncoder,这是一个编码器,因为回源器ingest从上游服务器回源得到数据后,是以msg(一帧数据)的形式发送给SrsSource,然后由SrsSource发送给每个SrsConsumer的接收队列中。SrsLiveStream::serve_http处理到最后,正是在循环的从其对应的SrsConsumer的接收队列中读取到msg然后按请求的协议发送给客户端。所以,需要有一个编码器把msg转化为请求协议对应的数据格式发送出去。比如flv,需要把msg打包成tag来发送。

       (2) 创建一个SrsConsumer,并注册到对应的SrsSource中去,这样SrsSource在收到ingest传来的msg后,就会发送到SrsConsumer的接收队列中去。

       (3)创建一个协程SrsHttpRecvThread去循环读取请求端的数据,实际上是不会理会后续的请求的,但是如果http请求解析错误,则会导致本次拉流结束

       (4)接下来是循环处理,从SrsConsumer的接收队列中取出msg,通过编码器进行封装,然后使用编码器中的SrsFileWriter将封装好的数据发送给客户端,当然循环中也会检测客户端有没有继续发送错误的请求

三、总结

       srs实际上参考golang实现了一个http服务器,http-flv请求的前期处理流程就是由http服务器完成。然后flv请求处理的核心是SrsHttpStreamServer::hijack和SrsLiveStream::serve_http。前者是某条流的首次flv请求到来时的处理部分,其会创建SrsSource并触发回源,然后生成处理器SrsLiveStream;后者则是flv请求到来是,这条流已经在回源的情况,这时已经有对应的处理器SrsLiveStream,所以直接由SrsLiveStream调用serve_http去封装数据和吐流。

 

你可能感兴趣的:(直播后台,c++)