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

本文深入探讨了nginx mp4流媒体模块中如何生成目标MP4文件,包括trak atom的调整,如stts、stss、ctts、stsc、stsz和stco等atom的更新逻辑。通过对请求时间偏移量的处理,裁剪和生成适合客户端的MP4内容。最后,文章概述了MP4处理的收尾工作及内容发送流程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

深入理解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 <
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码农心语

您的鼓励是我写作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值