深入理解nginx mp4流媒体模块[上]
深入理解nginx mp4流媒体模块[中]
深入理解nginx mp4流媒体模块[下]
深入理解nginx mp4流媒体模块[下下]
3.2.2 生成目标MP4文件
重新回到ngx_http_mp4_process,在ngx_http_mp4_read_atom函数将moov读取到缓冲区后,接下去要准备生成用于发送给用户的mp4文件了。
首先它会判断是否读取到了trak,如果一个trak都没有加载到,那么认为这个MP4文件是有问题的,接着在判断是否MP4文件里面是否有mdat atom,如果没有那么也是有个问题的,源码如下:
if (mp4->trak.nelts == 0) {
ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
"no mp4 trak atoms were found in \"%s\"",
mp4->file.name.data);
return NGX_ERROR;
}
if (mp4->mdat_atom.buf == NULL) {
ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
"no mp4 mdat atom was found in \"%s\"",
mp4->file.name.data);
return NGX_ERROR;
}
接着就开始把之前读取到的atom缓冲区用ngx_chain_t链接起来,如下:
prev = &mp4->out;
if (mp4->ftyp_atom.buf) {
*prev = &mp4->ftyp_atom;
prev = &mp4->ftyp_atom.next;
}
*prev = &mp4->moov_atom;
prev = &mp4->moov_atom.next;
if (mp4->mvhd_atom.buf) {
mp4->moov_size += mp4->mvhd_atom_buf.last - mp4->mvhd_atom_buf.pos;
*prev = &mp4->mvhd_atom;
prev = &mp4->mvhd_atom.next;
}
......
由以上代码可以看到,mp4->out中最终保存了准备要响应给客户端的ngx_chain_t链。这里县链接的是ftyp atom,接着是moov atom,然后是mvhd atom…
在以上代码中 mp4->moov_size 用来保存目标MP4文件中的moov atom的大小。
接下去就是来生成各个trak atom了,在生成trak atom的时候,需要根据请求的时间偏移量对相应的atom进行调整。
for (i = 0; i < mp4->trak.nelts; i++) {
/* 更新调整各个atom */
if (ngx_http_mp4_update_stts_atom(mp4, &trak[i]) != NGX_OK) {
return NGX_ERROR;
}
if (ngx_http_mp4_update_stss_atom(mp4, &trak[i]) != NGX_OK) {
return NGX_ERROR;
}
ngx_http_mp4_update_ctts_atom(mp4, &trak[i]);
if (ngx_http_mp4_update_stsc_atom(mp4, &trak[i]) != NGX_OK) {
return NGX_ERROR;
}
if (ngx_http_mp4_update_stsz_atom(mp4, &trak[i]) != NGX_OK) {
return NGX_ERROR;
}
if (trak[i].out[NGX_HTTP_MP4_CO64_DATA].buf) {
if (ngx_http_mp4_update_co64_atom(mp4, &trak[i]) != NGX_OK) {
return NGX_ERROR;
}
} else {
if (ngx_http_mp4_update_stco_atom(mp4, &trak[i]) != NGX_OK) {
return NGX_ERROR;
}
}
ngx_http_mp4_update_stbl_atom(mp4, &trak[i]);
ngx_http_mp4_update_minf_atom(mp4, &trak[i]);
ngx_http_mp4_update_mdhd_atom(mp4, &trak[i]);
trak[i].size += trak[i].hdlr_size;
ngx_http_mp4_update_mdia_atom(mp4, &trak[i]);
trak[i].size += trak[i].tkhd_size;
ngx_http_mp4_update_edts_atom(mp4, &trak[i]);
ngx_http_mp4_update_trak_atom(mp4, &trak[i]);
/* 统计moov atom的大小 */
mp4->moov_size += trak[i].size;
/* start_ofset和end_offset分别为准备要发送给客户端的mdat atom
中帧数据的起始和结束偏移量
*/
if (start_offset > trak[i].start_offset) {
start_offset = trak[i].start_offset;
}
if (end_offset < trak[i].end_offset) {
end_offset = trak[i].end_offset;
}
/* 将当前trak atom中的各个子孙atom链接到输出链中 */
*prev = &trak[i].out[NGX_HTTP_MP4_TRAK_ATOM];
prev = &trak[i].out[NGX_HTTP_MP4_TRAK_ATOM].next;
for (j = 0; j < NGX_HTTP_MP4_LAST_ATOM + 1; j++) {
if (trak[i].out[j].buf) {
*prev = &trak[i].out[j];
prev = &trak[i].out[j].next;
}
}
}
1. 调整stts atom
stts atom即Decoding Time to Samle Box, 记录了每个sample对应的解码时间。需要根据客户端的请求时间区间[start, end]来进行裁减。源码如下:
static ngx_int_t
ngx_http_mp4_update_stts_atom(ngx_http_mp4_file_t *mp4,
ngx_http_mp4_trak_t *trak)
{
size_t atom_size;
ngx_buf_t *atom, *data;
ngx_mp4_stts_atom_t *stts_atom;
/*
* mdia.minf.stbl.stts updating requires trak->timescale
* from mdia.mdhd atom which may reside after mdia.minf
*/
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
"mp4 stts atom update");
data = trak->out[NGX_HTTP_MP4_STTS_DATA].buf;
if (data == NULL) {
ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
"no mp4 stts atoms were found in \"%s\"",
mp4->file.name.data);
return NGX_ERROR;
}
/* 裁减stts atom的头部记录 */
if (ngx_http_mp4_crop_stts_data(mp4, trak, 1) != NGX_OK) {
return NGX_ERROR;
}
/* 裁减stts atom的尾部记录 */
if (ngx_http_mp4_crop_stts_data(mp4, trak, 0) != NGX_OK) {
return NGX_ERROR;
}
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
"time-to-sample entries:%uD", trak->time_to_sample_entries);
atom_size = sizeof(ngx_mp4_stts_atom_t) + (data->last - data->pos);
trak->size += atom_size; /* 更新当前trak atom的大小 */
/* 更新stts atom头部信息 */
atom = trak->out[NGX_HTTP_MP4_STTS_ATOM].buf;
stts_atom = (ngx_mp4_stts_atom_t *) atom->pos;
ngx_mp4_set_32value(stts_atom->size, atom_size);
ngx_mp4_set_32value(stts_atom->entries, trak->time_to_sample_entries);
return NGX_OK;
}
所以,裁减的主要逻辑是在ngx_http_mp4_crop_stts_data函数中来实现的。
static ngx_int_t
ngx_http_mp4_crop_stts_data(ngx_http_mp4_file_t *mp4,
ngx_http_mp4_trak_t *trak, ngx_uint_t start)
{
uint32_t count, duration, rest, key_prefix;
uint64_t start_time;
ngx_buf_t *data;
ngx_uint_t start_sample, entries, start_sec;
ngx_mp4_stts_entry_t *entry, *end;
if (start) {
start_sec = mp4->start;
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
"mp4 stts crop start_time:%ui", start_sec);
} else if (mp4->length) {
start_sec = mp4->length;
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
"mp4 stts crop end_time:%ui", start_sec);
} else {
return NGX_OK;
}
data = trak->out[NGX_HTTP_MP4_STTS_DATA].buf;
/* 将要裁减的时间戳单位转换为当前trak的时间单位*/
start_time = (uint64_t) start_sec * trak->timescale / 1000 + trak->prefix;
entries = trak->time_to_sample_entries; /* 当前stts总共有多少记录 */
start_sample = 0; /* 当前记录的起始sample id */
entry = (ngx_mp4_stts_entry_t *) data->pos; /* stts记录的起始位置 */
end = (ngx_mp4_stts_entry_t *) data->last; /* stts记录的结束位置 */
/* 查找start_time对应的记录位置 */
while <