前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >深入理解nginx mp4流媒体模块[上]

深入理解nginx mp4流媒体模块[上]

作者头像
码农心语
发布2024-04-09 15:58:48
2200
发布2024-04-09 15:58:48
举报
文章被收录于专栏:码农心语码农心语

1. 引言

??在当今数字化时代,视频已成为互联网上最主要的内容形式之一。NGINX作为一款高性能的Web服务器和反向代理服务器,提供了强大的MP4模块,用于优化MP4视频的点播传输功能,并支持播放器的任意拖拽功能。本文将通过通过源码分析深入探讨NGINX MP4模块的实现源码,介绍其功能和实现原理。

NGINX MP4模块的作用和优势

NGINX MP4模块的主要作用是优化MP4视频的点播传输功能,提供快速启动和流畅播放的体验。它通过减少客户端和Web服务器之间的交互,降低额外数据消耗,显著减少流媒体播放的启动时间。以下是NGINX MP4模块的优势:

  • 快速启动时间:通过预读取视频文件的元数据,NGINX MP4模块实现了快速的启动时间。用户请求播放视频时,只需加载视频的元数据,无需等待整个视频文件加载完毕。
  • 支持任意拖拽功能:现代浏览器在Web服务器支持HTTP Range请求的情况下,可以通过MP4模块实现视频的任意拖拽功能,提供更好的用户体验。
  • 减少数据传输:MP4模块减少了不必要的HTTP请求,通过边播边加载的方式为用户提供视频流,减少额外的性能消耗。

NGINX MP4模块的实现原理

NGINX MP4模块通过读取和解析MP4视频文件的元数据,实现优化的点播传输。它预读取视频文件的元数据,包括视频的时长、编码信息、音频信息等,并将这些信息缓存到内存中。当用户请求播放视频时,NGINX MP4模块直接从内存中获取元数据,根据客户端的请求,按需传输视频片段,实现快速启动和流畅播放的效果。

2. 配置

??要使用NGINX MP4模块,需要在NGINX的配置文件中进行相应的配置。以下是一个简单的配置示例:

代码语言:javascript
复制
location /videos/ {
    root html;
    mp4;                     # 开启mp4流媒体功能
    mp4_buffer_size 1m;      # mp4 moov元数据缓存的默认空间大小
    mp4_max_buffer_size 10m; # mp4 moov元数据缓存的最大空间
}

??通过以上配置,就可以通过 curl模拟播放器访问了。例如:

代码语言:javascript
复制
#从头开始播放
curl "http://127.0.0.1/videos/test.mp4"                     

#从第100s播放到200s
curl "http://127.0.0.1/videos/test.mp4?start=100&end=200"
代码语言:javascript
复制
   

??这里需要强调的是,对于一些特别大的mp4文件,可能moov元数据的大小就超过了mp4_max_buffer_size,会导致nginx报错的情况,但是如果设置太大,特别是mp4_buffer_size设置得太大,就会使得nginx消耗太多的内存,引起其他问题。因此,需要预先对moov大小有一个预估。

3. 源码分析

3.1 配置指令

3.1.1 mp4

代码语言:javascript
复制
{ ngx_string("mp4"),
  NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS,
  ngx_http_mp4,
  0,
  0,
  NULL },

? 这个指令开启mp4流媒体功能,从以上定义可以知道这个指令只能在location中配置。

??在ngx_http_mp4配置指令解析函数中,设置了ngx_http_mp4_handler回调函数,如下:

代码语言:javascript
复制
static char *
ngx_http_mp4(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
    ngx_http_core_loc_conf_t  *clcf;

    clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
    clcf->handler = ngx_http_mp4_handler;

    return NGX_CONF_OK;
}

??该回调函数会在NGX_HTTP_CONTENT_PHRASE阶段回调这个函数进行mp4的处理。

3.1.2 mp4_buffer_size

代码语言:javascript
复制
{ ngx_string("mp4_buffer_size"),
  NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
  ngx_conf_set_size_slot,
  NGX_HTTP_LOC_CONF_OFFSET,
  offsetof(ngx_http_mp4_conf_t, buffer_size),
  NULL },

??这个指令定义了moov数据缓冲区的默认大小,可以在http/server/location中配置。

3.1.3 mp4_max_buffer_size

代码语言:javascript
复制
{ ngx_string("mp4_max_buffer_size"),
  NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
  ngx_conf_set_size_slot,
  NGX_HTTP_LOC_CONF_OFFSET,
  offsetof(ngx_http_mp4_conf_t, max_buffer_size),
  NULL },

??这个指令定义了moov数据缓冲区的最大空间,可以在http/server/location中配置。

3.1.4 mp4_start_key_frame

代码语言:javascript
复制
{ ngx_string("mp4_start_key_frame"),
  NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
  ngx_conf_set_flag_slot,
  NGX_HTTP_LOC_CONF_OFFSET,
  offsetof(ngx_http_mp4_conf_t, start_key_frame),
  NULL },

??这个指令设置是否将视频起始帧对齐到最近的关键帧开始发送数据。

3.2 MP4的请求处理过程

??下面以ngx_http_mp4_handler函数为分析对象,概要说明MP4的请求处理过程。

3.2.1 预处理

过滤非GET/HEAD请求。

代码语言:javascript
复制
代码语言:javascript
复制
if (!(r->method & (NGX_HTTP_GET|NGX_HTTP_HEAD))) {
  return NGX_HTTP_NOT_ALLOWED;
}

取消接收客户端请求的http body部分。

代码语言:javascript
复制
代码语言:javascript
复制
rc = ngx_http_discard_request_body(r);

3.2.2 找到并打开本地mp4文件

  • 获取mp4文件的完整路径
代码语言:javascript
复制
last = ngx_http_map_uri_to_path(r, &path, &root, 0);
if (last == NULL) {
  return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
log = r->connection->log;
path.len = last - path.data;
代码语言:javascript
复制
打开mp4文件

of.read_ahead = clcf->read_ahead;
of.directio = NGX_MAX_OFF_T_VALUE;
of.valid = clcf->open_file_cache_valid;
of.min_uses = clcf->open_file_cache_min_uses;
of.errors = clcf->open_file_cache_errors;
of.events = clcf->open_file_cache_events;

/*
  用于设置NGINX服务器是否允许访问符号链接文件的功能。
  当启用该功能时,NGINX将拒绝通过符号链接文件访问文件系统中的文件。
*/
if (ngx_http_set_disable_symlinks(r, clcf, &path, &of) != NGX_OK) {
    return NGX_HTTP_INTERNAL_SERVER_ERROR;
}

if (ngx_open_cached_file(clcf->open_file_cache, &path, &of, r->pool)
    != NGX_OK)
{
......
}

3.2.3 解析请求参数

??从http请求的querystring部分提取到start和end参数,这两个参数的单位都是秒。

代码语言:javascript
复制
if (r->args.len) {

    if (ngx_http_arg(r, (u_char *) "start", 5, &value) == NGX_OK) {

        /*
         * A Flash player may send start value with a lot of digits
         * after dot so a custom function is used instead of ngx_atofp().
         */

        start = ngx_http_mp4_atofp(value.data, value.len, 3);
    }

    if (ngx_http_arg(r, (u_char *) "end", 3, &value) == NGX_OK) {

        end = ngx_http_mp4_atofp(value.data, value.len, 3);

        if (end > 0) {
            if (start < 0) {
                start = 0;
            }

            if (end > start) {
                length = end - start;
            }
        }
    }
}

3.2.4 MP4文件的处理

代码语言:javascript
复制
if (start >= 0) {
    r->single_range = 1;

    /* 分配并初始化mp4处理上下文 */
    mp4 = ngx_pcalloc(r->pool, sizeof(ngx_http_mp4_file_t));
    if (mp4 == NULL) {
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }

    mp4->file.fd = of.fd;
    mp4->file.name = path;
    mp4->file.log = r->connection->log;
    mp4->end = of.size;
    mp4->start = (ngx_uint_t) start;   /* 设置视频起始偏移位置,单位s */
    mp4->length = length;              /* 设置待响应的视频时长,=0表示一直到末尾*/
    mp4->request = r;

    /* 加载并调整mp4的moov元信息帧索引 */
    switch (ngx_http_mp4_process(mp4)) {

    case NGX_DECLINED:     /* 跳过mp4的处理,直接返回整个文件 */
        if (mp4->buffer) {
            ngx_pfree(r->pool, mp4->buffer);
        }

        ngx_pfree(r->pool, mp4);
        mp4 = NULL;

        break;

    case NGX_OK:          /* 处理ok */
        r->headers_out.content_length_n = mp4->content_length;
        break;

    default:              /* NGX_ERROR */
        if (mp4->buffer) {
            ngx_pfree(r->pool, mp4->buffer);
        }

        ngx_pfree(r->pool, mp4);

        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }
}

??以下对ngx_http_mp4_file_t的结构定义进行说明:

代码语言:javascript
复制
typedef struct {
    ngx_file_t            file;              # mp4文件对象

    u_char               *buffer;            # 用于mp4分析的缓冲区
    u_char               *buffer_start;      # buffer空闲的起始位置
    u_char               *buffer_pos;        # buffer中可用于分析的起始位置
    u_char               *buffer_end;        # buffer中可用于分析的结束位置
    size_t                buffer_size;       # mp4分析缓冲区buffer的大小

    off_t                 offset;            # buffer_start对应的mp4文件读取的偏移量
    off_t                 end;               # 当前mp4文件的文件大小
    off_t                 content_length;    # 最终发送给客户端响应的内容长度
    ngx_uint_t            start;             # 请求的起始偏移时间
    ngx_uint_t            length;            # 请求的视频时长
    uint32_t              timescale;         # mp4文件中设置的时间scale值
    ngx_http_request_t   *request;           # 对应当前的http request对象
    ngx_array_t           trak;              # mp4包含的track列表,引用traks,最多2个
    ngx_http_mp4_trak_t   traks[2];          # mp4包含的track列表

    size_t                ftyp_size;         # ftyp atom的大小
    size_t                moov_size;         # moov atom的大小

    ngx_chain_t          *out;
    ngx_chain_t           ftyp_atom;         # 链接了ftyp_atom_buf的缓冲区链
    ngx_chain_t           moov_atom;         # 链接了moov_atom_buf的缓冲区链
    ngx_chain_t           mvhd_atom;         # 链接了mvhd_atom_buf的缓冲区链
    ngx_chain_t           mdat_atom;         # 链接了mdat_atom_buf的缓冲区链
    ngx_chain_t           mdat_data;         # 链接了mdat_data_buf的缓冲区链

    ngx_buf_t             ftyp_atom_buf;     # ftyp atom的缓冲区
    ngx_buf_t             moov_atom_buf;     # moov atom的缓冲区
    ngx_buf_t             mvhd_atom_buf;     # mvhd atom的缓冲区
    ngx_buf_t             mdat_atom_buf;     # mdat atom的缓冲区
    ngx_buf_t             mdat_data_buf;     # mdat atom的缓冲区

    u_char                moov_atom_header[8];
    u_char                mdat_atom_header[16];
} ngx_http_mp4_file_t;

3.2.4 MP4文件的发送

??其实现源码如下,源码里面进行了详细的备注说明:

代码语言:javascript
复制
log->action = "sending mp4 to client";

    /* 如果需要,开启directio */
    if (clcf->directio <= of.size) {

        /*
         * DIRECTIO is set on transfer only
         * to allow kernel to cache "moov" atom
         */

        if (ngx_directio_on(of.fd) == NGX_FILE_ERROR) {
            ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
                          ngx_directio_on_n " \"%s\" failed", path.data);
        }

        of.is_directio = 1;

        if (mp4) {
            mp4->file.directio = 1;
        }
    }

    /* 设置响应状态和相关响应头信息,然后将响应头发送给用户 */
    r->headers_out.status = NGX_HTTP_OK;
    r->headers_out.last_modified_time = of.mtime;

    if (ngx_http_set_etag(r) != NGX_OK) {
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }

    if (ngx_http_set_content_type(r) != NGX_OK) {
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }

    if (mp4 == NULL) {
        b = ngx_calloc_buf(r->pool);
        if (b == NULL) {
            return NGX_HTTP_INTERNAL_SERVER_ERROR;
        }

        b->file = ngx_pcalloc(r->pool, sizeof(ngx_file_t));
        if (b->file == NULL) {
            return NGX_HTTP_INTERNAL_SERVER_ERROR;
        }
    }

    rc = ngx_http_send_header(r);

    if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
        return rc;
    }

    /* 如果mp4处理上下文已经创建过,那么就发送mp4->out缓冲区中的内容给用户
    if (mp4) {
        return ngx_http_output_filter(r, mp4->out);
    }

    /* 将完整的mp4文件发送出去 */
    b->file_pos = 0;
    b->file_last = of.size;

    b->in_file = b->file_last ? 1 : 0;
    b->last_buf = (r == r->main) ? 1 : 0;
    b->last_in_chain = 1;
    b->sync = (b->last_buf || b->in_file) ? 0 : 1;

    b->file->fd = of.fd;
    b->file->name = path;
    b->file->log = log;
    b->file->directio = of.is_directio;

    out.buf = b;
    out.next = NULL;

    return ngx_http_output_filter(r, &out);

3.3 MP4文件格式分析器的定义

??关于mp4文件的详细可以可以参见相应的标准文档,互联网上也有大量的文章,甚至可以用工具自己打开一个mp4文件来对照分析。以下对照mp4文件格式,在ngx_http_mp4_module中如何来定义分析器进行说明。?? 对应mp4文件的文件级的atom,本模块进行了如下定义:

代码语言:javascript
复制
static ngx_http_mp4_atom_handler_t  ngx_http_mp4_atoms[] = {
    { "ftyp", ngx_http_mp4_read_ftyp_atom },
    { "moov", ngx_http_mp4_read_moov_atom },
    { "mdat", ngx_http_mp4_read_mdat_atom },
    { NULL, NULL }
};

??譬如对于ftyp atom,在分析程序读取到ftyp atom的时候,就会调用ngx_http_mp4_read_ftyp_atom进行解析。其余的atom都是类似的处理方式。对于包含子atom的容器类atom,那么在对应的处理函数里面会递归解析子atom。譬如moov,下面又递归定义了子atom的处理函数,如下:

代码语言:javascript
复制
static ngx_http_mp4_atom_handler_t  ngx_http_mp4_moov_atoms[] = {
    { "mvhd", ngx_http_mp4_read_mvhd_atom },
    { "trak", ngx_http_mp4_read_trak_atom },
    { "cmov", ngx_http_mp4_read_cmov_atom },
    { NULL, NULL }
};

??下面是trak atom的子atom处理函数定义:

代码语言:javascript
复制
static ngx_http_mp4_atom_handler_t  ngx_http_mp4_trak_atoms[] = {
    { "tkhd", ngx_http_mp4_read_tkhd_atom },
    { "mdia", ngx_http_mp4_read_mdia_atom },
    { NULL, NULL }
};

??这里的trak atom对应每一条音频流或者视频流,所以可以包含多个。

??下面是mdia的子atom处理函数定义:

代码语言:javascript
复制
static ngx_http_mp4_atom_handler_t  ngx_http_mp4_mdia_atoms[] = {
    { "mdhd", ngx_http_mp4_read_mdhd_atom },
    { "hdlr", ngx_http_mp4_read_hdlr_atom },
    { "minf", ngx_http_mp4_read_minf_atom },
    { NULL, NULL }
};

??下面是mdia的子minf处理函数定义:

代码语言:javascript
复制
static ngx_http_mp4_atom_handler_t  ngx_http_mp4_minf_atoms[] = {
    { "vmhd", ngx_http_mp4_read_vmhd_atom },
    { "smhd", ngx_http_mp4_read_smhd_atom },
    { "dinf", ngx_http_mp4_read_dinf_atom },
    { "stbl", ngx_http_mp4_read_stbl_atom },
    { NULL, NULL }
};

??下面是mdia的子stbl处理函数定义:

代码语言:javascript
复制
static ngx_http_mp4_atom_handler_t  ngx_http_mp4_stbl_atoms[] = {
    { "stsd", ngx_http_mp4_read_stsd_atom },
    { "stts", ngx_http_mp4_read_stts_atom },
    { "stss", ngx_http_mp4_read_stss_atom },
    { "ctts", ngx_http_mp4_read_ctts_atom },
    { "stsc", ngx_http_mp4_read_stsc_atom },
    { "stsz", ngx_http_mp4_read_stsz_atom },
    { "stco", ngx_http_mp4_read_stco_atom },
    { "co64", ngx_http_mp4_read_co64_atom },
    { NULL, NULL }
};

下面将上面定义的架构画成对应的图:

3.2 MP4文件的处理

3.2.1 MP4的处理框架

?? MP4的处理过程一言以蔽之,就是读取MP4文件头ftyp atom,以及moov atom,然后根据用户请求的start起始时间位置,对moov头中的stbl的各个子 atom进行调整(包括裁减和索引调整),生成新的moov放在ngx_http_mp4_file_t结构体中,最后交由nginx文件异步发送逻辑将mp4发送给客户端。mp4文件的处理核心逻辑都在ngx_http_mp4_process函数中来实现的,主要分为两大步骤:

  • 首先,通过ngx_http_mp4_read_atom将mp4加载到内存中。??当然,加载的内容不包括mdat部分,因为mdat是真正的音视频内容,太大了。加载到内存的主要是ftyp和moov两部分,以及预读取到的部分mdat的内容(最大是4k字节)。
  • 其次,对mp4的几个trak进行遍历,根据用户的请求[start,end]区间来调整stbl atom下面的子atom中的索引信息。

??下面首先对MP4的加载逻辑进行分析:

3.2.2 MP4的加载

??MP4的加载逻辑的入口函数是ngx_http_mp4_read_atom,函数的定义如下:

代码语言:javascript
复制
static ngx_int_t
ngx_http_mp4_read_atom(ngx_http_mp4_file_t *mp4,
    ngx_http_mp4_atom_handler_t *atom, 
    uint64_t atom_data_size);

??这个函数是可以进行递归调用的,当目前的atom容器需要解析其包含的子atom的时候,就进行一次递归。譬如,对于完整的MP4文件分析,我们可以认为完整的MP4本身就是一个大容器,那么这么来调用:

代码语言:javascript
复制
代码语言:javascript
复制
ngx_http_mp4_read_atom(mp4, ngx_http_mp4_atoms, mp4->end);
代码语言:javascript
复制
   其中mp4->end表示整个MP4文件的大小。

??那么对于当前是moov atom的情况下,我们可以这么来调用:

代码语言:javascript
复制
代码语言:javascript
复制
ngx_http_mp4_read_atom(mp4, ngx_http_mp4_moov_atoms, atom_data_size);
代码语言:javascript
复制
   其中atom_data_size表示moov atom的大小。

以下是ngx_http_mp4_read_atom的实现代码:

代码语言:javascript
复制
static ngx_int_t
ngx_http_mp4_read_atom(ngx_http_mp4_file_t *mp4,
    ngx_http_mp4_atom_handler_t *atom, uint64_t atom_data_size)
{
    off_t        end;
    size_t       atom_header_size;
    u_char      *atom_header, *atom_name;
    uint64_t     atom_size;
    ngx_int_t    rc;
    ngx_uint_t   n;

    end = mp4->offset + atom_data_size;

    /* 如果分析的文件偏移量还没有到atom末尾则继续循环执行分析 */
    while (mp4->offset < end) {
        /* 确保buffer中至少有atom头中的4字节长度可供分析 */
        if (ngx_http_mp4_read(mp4, sizeof(uint32_t)) != NGX_OK) {
            return NGX_ERROR;
        }

        atom_header = mp4->buffer_pos;   /* buffer_pos是当前的缓冲区读指针 */
        atom_size = ngx_mp4_get_32value(atom_header);
        atom_header_size = sizeof(ngx_mp4_atom_header_t);

        if (atom_size == 0) {
            ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
                           "mp4 atom end");
            return NGX_OK;
        }

        if (atom_size < sizeof(ngx_mp4_atom_header_t)) {

            if (atom_size == 1) {   /* atom_size=1 表示是一个支持64位长度的atom */
               /* 确保buffer中至少有atom头中的ngx_mp4_atom_header64_t长度供分析 */
                if (ngx_http_mp4_read(mp4, sizeof(ngx_mp4_atom_header64_t))
                    != NGX_OK)
                {
                    return NGX_ERROR;
                }

                /* 64-bit atom size */
                atom_header = mp4->buffer_pos;
                atom_size = ngx_mp4_get_64value(atom_header + 8);
                atom_header_size = sizeof(ngx_mp4_atom_header64_t);

                if (atom_size < sizeof(ngx_mp4_atom_header64_t)) {
                    ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
                                  "\"%s\" mp4 atom is too small:%uL",
                                  mp4->file.name.data, atom_size);
                    return NGX_ERROR;
                }

            } else {
                ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
                              "\"%s\" mp4 atom is too small:%uL",
                              mp4->file.name.data, atom_size);
                return NGX_ERROR;
            }
        }
        /* 确保buffer中至少有atom头中的ngx_mp4_atom_header_t长度供分析 */
        if (ngx_http_mp4_read(mp4, sizeof(ngx_mp4_atom_header_t)) != NGX_OK) {
            return NGX_ERROR;
        }

        atom_header = mp4->buffer_pos;
        atom_name = atom_header + sizeof(uint32_t);

        ngx_log_debug4(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
                       "mp4 atom: %*s @%O:%uL",
                       (size_t) 4, atom_name, mp4->offset, atom_size);

        if (atom_size > (uint64_t) (NGX_MAX_OFF_T_VALUE - mp4->offset)
            || mp4->offset + (off_t) atom_size > end)
        {
            ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
                          "\"%s\" mp4 atom too large:%uL",
                          mp4->file.name.data, atom_size);
            return NGX_ERROR;
        }

        /* 查找当前atom容器下面的子容器定义是否包含刚刚读取到的名字为atom_name的atom 
           如果找到了,则调用前面注册的回调函数
           如果没有找到,则忽略之,本模块不用关心,也不是流媒体播放所必须的。
        */
        for (n = 0; atom[n].name; n++) {

            if (ngx_strncmp(atom_name, atom[n].name, 4) == 0) {

                ngx_mp4_atom_next(mp4, atom_header_size);

                rc = atom[n].handler(mp4, atom_size - atom_header_size);
                if (rc != NGX_OK) {
                    return rc;
                }

                goto next;
            }
        }
        /* 移动buffer_pos指针,在缓冲区中跳过本atom */
        ngx_mp4_atom_next(mp4, atom_size);

    next:
        continue;
    }

    return NGX_OK;
}

?? 这个函数的主要逻辑就是读取atom头,然后交由前面定义的atom处理函数进行处理,如果当前的atom本模块没有对应的定义则直接忽略,然后切换到下一个atom继续分析,直到整个mp4文件分析完成。以下对代码中的部分内容再做进一步的解析:

?? mp4的atom支持最大支持232-1大小的普通atom,和支持264-1大小的64位的atom,当是后者的情况,在atom头部的前4个字节读取后,得到是1,表示它是64位的atom,需要另行读取64位长度的长度字段,64位的atom的定义如下:

代码语言:javascript
复制
typedef struct {
    u_char    size[4];           /* 设置的是1 */
    u_char    name[4];
    u_char    size64[8];
} ngx_mp4_atom_header64_t;

??而32位的atom定义如下:

代码语言:javascript
复制
typedef struct {
    u_char    size[4];
    u_char    name[4];
} ngx_mp4_atom_header_t;

??在ngx_http_mp4_read_atom中还调用了ngx_http_mp4_read,对ngx_http_mp4_read函数的分析可以帮助我们理解ngx_http_mp4_file_t 中offset/end、buffer_pos/buffer_end以及buffer_size字段的具体含义,所以我们再看一下ngx_http_mp4_read函数的实现逻辑,代码如下:

代码语言:javascript
复制
static ngx_int_t
ngx_http_mp4_read(ngx_http_mp4_file_t *mp4, size_t size)
{
    ssize_t  n;

    /* 如果当前的缓冲区读位置+待读取的字节数 < 缓冲区结束位置,
       说明缓冲区里面还有足够数据,直接返回OK即可
    */
    if (mp4->buffer_pos + size <= mp4->buffer_end) {
        return NGX_OK;
    }

    /* 如果当前的文件读偏移量+缓冲区大小超过的了文件结束位置
       则调整缓冲区大小值,避免越过文件结束位置
     */
    if (mp4->offset + (off_t) mp4->buffer_size > mp4->end) {
        mp4->buffer_size = (size_t) (mp4->end - mp4->offset);
    }

    if (mp4->buffer_size < size) {
        ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
                      "\"%s\" mp4 file truncated", mp4->file.name.data);
        return NGX_ERROR;
    }

    if (mp4->buffer == NULL) {
        mp4->buffer = ngx_palloc(mp4->request->pool, mp4->buffer_size);
        if (mp4->buffer == NULL) {
            return NGX_ERROR;
        }

        mp4->buffer_start = mp4->buffer;
    }

    /* 从文件中读取偏移量为mp4->offset,大小为mp4->buffer_size的数据到mp4->buffer_start
       缓冲区中
     */
    n = ngx_read_file(&mp4->file, mp4->buffer_start, mp4->buffer_size,
                      mp4->offset);

    if (n == NGX_ERROR) {
        return NGX_ERROR;
    }

    if ((size_t) n != mp4->buffer_size) {
        ngx_log_error(NGX_LOG_CRIT, mp4->file.log, 0,
                      ngx_read_file_n " read only %z of %z from \"%s\"",
                      n, mp4->buffer_size, mp4->file.name.data);
        return NGX_ERROR;
    }

    mp4->buffer_pos = mp4->buffer_start;
    mp4->buffer_end = mp4->buffer_start + mp4->buffer_size;

    return NGX_OK;
}

??最后再看ngx_mp4_atom_next,它是一个宏定义,源码如下:

代码语言:javascript
复制
#define ngx_mp4_atom_next(mp4, n)                                             \
                                                                              \
    if (n > (size_t) (mp4->buffer_end - mp4->buffer_pos)) {                   \
        mp4->buffer_pos = mp4->buffer_end;                                    \
                                                                              \
    } else {                                                                  \
        mp4->buffer_pos += (size_t) n;                                        \
    }                                                                         \
                                                                              \
    mp4->offset += n

??这个宏说白了就是移动缓冲区的读指针buffer_pos和文件读偏移量offset,目的是用来跳过某个atom到下一个atom,以便后续继续对mp4文件进行分析。那么为什么offset可以直接加n,而buffer_pos不能呢?因为可能某个atom并没有完整的读取到buffer中,所以直接让buffer_pos + n可能会越过了buffer_end,而offset则不同,它是文件读偏移量,offset+n正好是在文件中越过了该atom。

本篇到此结束,关于moov元素的分析、stbl视频帧索引的调整部分的内容将在下篇进行分析介绍。

本文参与?腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2024-03-27,如有侵权请联系?cloudcommunity@tencent.com 删除

本文分享自 码农心语 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与?腾讯云自媒体分享计划? ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 引言
  • 2. 配置
  • 3. 源码分析
    • 3.1 配置指令
      • 3.1.1 mp4
      • 3.1.2 mp4_buffer_size
      • 3.1.3 mp4_max_buffer_size
      • 3.1.4 mp4_start_key_frame
    • 3.2 MP4的请求处理过程
      • 3.2.1 预处理
      • 3.2.2 找到并打开本地mp4文件
      • 3.2.3 解析请求参数
      • 3.2.4 MP4文件的处理
      • 3.2.4 MP4文件的发送
    • 3.3 MP4文件格式分析器的定义
      • 3.2 MP4文件的处理
        • 3.2.1 MP4的处理框架
          • 3.2.2 MP4的加载
      相关产品与服务
      容器服务
      腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
      http://www.vxiaotou.com