目录
深入理解nginx mp4流媒体模块[上]
深入理解nginx mp4流媒体模块[中]
深入理解nginx mp4流媒体模块[下]
深入理解nginx mp4流媒体模块[下下]
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的配置文件中进行相应的配置。以下是一个简单的配置示例:
location /videos/ {
root html;
mp4; # 开启mp4流媒体功能
mp4_buffer_size 1m; # mp4 moov元数据缓存的默认空间大小
mp4_max_buffer_size 10m; # mp4 moov元数据缓存的最大空间
}
通过以上配置,就可以通过 curl模拟播放器访问了。例如:
#从头开始播放
curl "http://127.0.0.1/videos/test.mp4"
#从第100s播放到200s
curl "http://127.0.0.1/videos/test.mp4?start=100&end=200"
这里需要强调的是,对于一些特别大的mp4文件,可能moov元数据的大小就超过了mp4_max_buffer_size,会导致nginx报错的情况,但是如果设置太大,特别是mp4_buffer_size设置得太大,就会使得nginx消耗太多的内存,引起其他问题。因此,需要预先对moov大小有一个预估。
3. 源码分析
3.1 配置指令
3.1.1 mp4
{ ngx_string("mp4"),
NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS,
ngx_http_mp4,
0,
0,
NULL },
&emps;这个指令开启mp4流媒体功能,从以上定义可以知道这个指令只能在location中配置。
在ngx_http_mp4配置指令解析函数中,设置了ngx_http_mp4_handler回调函数,如下:
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
{ 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
{ 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
{ 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请求。
if (!(r->method & (NGX_HTTP_GET|NGX_HTTP_HEAD))) {
return NGX_HTTP_NOT_ALLOWED;
}
- 取消接收客户端请求的http body部分。
rc = ngx_http_discard_request_body(r);
3.2.2 找到并打开本地mp4文件
- 获取mp4文件的完整路径
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;
- 打开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参数,这两个参数的单位都是秒。
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文件的处理
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;
mp4->length = length;
mp4->request = r;
/* 加载并调整mp4的moov元信息帧索引 */
switch (ngx_http_mp4_process(mp4)) {
case NGX_DECLINED:
if (mp4->buffer) {
ngx_pfree(r->pool, mp4->buffer);
}
ngx_pfree(r->pool, mp4);
mp4 = NULL;
break;
case NGX_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的结构定义进行说明:
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; # 当前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;
<未完待续>
下接:深入理解nginx mp4流媒体模块[中]