static double
ngx_rtmp_play_index_value(void *src)
{
    double      v;

    ngx_rtmp_rmemcpy(&v, src, 8);
    return v;
}
static ngx_int_t
ngx_rtmp_handshake_parse_challenge(ngx_rtmp_session_t *s,
        ngx_str_t *peer_key, ngx_str_t *key)
{
    ngx_buf_t              *b;
    u_char                 *p;
    ngx_int_t               offs;

    b = s->hs_buf;
    if (*b->pos != '\x03') {
        ngx_log_error(NGX_LOG_INFO, s->connection->log, 0,
                "handshake: unexpected RTMP version: %i",
                (ngx_int_t)*b->pos);
        return NGX_ERROR;
    }
    ++b->pos;
    s->peer_epoch = 0;
    ngx_rtmp_rmemcpy(&s->peer_epoch, b->pos, 4);

    p = b->pos + 4;
    ngx_log_debug5(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
            "handshake: peer version=%i.%i.%i.%i epoch=%uD",
            (ngx_int_t)p[3], (ngx_int_t)p[2],
            (ngx_int_t)p[1], (ngx_int_t)p[0],
            (uint32_t)s->peer_epoch);
    if (*(uint32_t *)p == 0) {
        s->hs_old = 1;
        return NGX_OK;
    }

    offs = ngx_rtmp_find_digest(b, peer_key, 772, s->connection->log);
    if (offs == NGX_ERROR) {
        offs = ngx_rtmp_find_digest(b, peer_key, 8, s->connection->log);
    }
    if (offs == NGX_ERROR) {
        ngx_log_error(NGX_LOG_INFO, s->connection->log, 0,
                "handshake: digest not found");
        s->hs_old = 1;
        return NGX_OK;
    }
    ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
            "handshake: digest found at pos=%i", offs);
    b->pos += offs;
    b->last = b->pos + NGX_RTMP_HANDSHAKE_KEYLEN;
    /* 获得密文 */
    s->hs_digest = ngx_palloc(s->connection->pool, NGX_RTMP_HANDSHAKE_KEYLEN);
    if (ngx_rtmp_make_digest(key, b, NULL, s->hs_digest, s->connection->log)
            != NGX_OK)
    {
        return NGX_ERROR;
    }
    return NGX_OK;
}
static ngx_int_t
ngx_rtmp_play_fill_index(ngx_rtmp_amf_ctx_t *ctx, ngx_rtmp_play_index_t *idx)
{
    uint32_t                        nelts;
    ngx_buf_t                      *b;

    /* we have AMF array pointed by context;
     * need to extract its size (4 bytes) &
     * save offset of actual array data */
    b = ctx->link->buf;
    if (b->last - b->pos < (ngx_int_t) ctx->offset + 4) {
        return NGX_ERROR;
    }

    ngx_rtmp_rmemcpy(&nelts, b->pos + ctx->offset, 4);
    idx->nelts = nelts;
    idx->offset = ctx->offset + 4;

    return NGX_OK;
}
static void
ngx_rtmp_play_send(ngx_event_t *e)
{
    ngx_rtmp_session_t             *s;
    ngx_rtmp_play_ctx_t            *ctx;
    uint32_t                        last_timestamp;
    ngx_rtmp_header_t               h, lh;
    ngx_rtmp_core_srv_conf_t       *cscf;
    ngx_chain_t                    *out, in;
    ngx_buf_t                       in_buf;
    ssize_t                         n;
    uint32_t                        buflen, end_timestamp, size;

    s = e->data;

    cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);
    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_play_module);
    if (ctx == NULL) {
        return;
    }

    if (ctx->offset == -1) {
        ctx->offset = ngx_rtmp_play_timestamp_to_offset(s,
                                                        ctx->start_timestamp);
        ctx->start_timestamp = -1; /* set later from actual timestamp */
    }

    ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
                  "play: read tag at offset=%i", ctx->offset);

    /* read tag header */
    n = ngx_read_file(&ctx->file, ngx_rtmp_play_header, 
                      sizeof(ngx_rtmp_play_header), ctx->offset);
    if (n != sizeof(ngx_rtmp_play_header)) {
        ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
                     "play: could not read flv tag header");
        ngx_rtmp_send_user_stream_eof(s, 1);
        return;
    }

    /* parse header fields */
    ngx_memzero(&h, sizeof(h));
    h.msid = NGX_RTMP_LIVE_MSID;
    h.type = ngx_rtmp_play_header[0];
    size = 0;
    ngx_rtmp_rmemcpy(&size, ngx_rtmp_play_header + 1, 3);
    ngx_rtmp_rmemcpy(&h.timestamp, ngx_rtmp_play_header + 4, 3);
    ((u_char *) &h.timestamp)[3] = ngx_rtmp_play_header[7];

    ctx->offset += (sizeof(ngx_rtmp_play_header) + size + 4);

    last_timestamp = 0;

    switch (h.type) {
        case NGX_RTMP_MSG_AUDIO:
            h.csid = NGX_RTMP_LIVE_CSID_AUDIO;
            last_timestamp = ctx->last_audio;
            ctx->last_audio = h.timestamp;
            break;

        case NGX_RTMP_MSG_VIDEO:
            h.csid = NGX_RTMP_LIVE_CSID_VIDEO;
            last_timestamp = ctx->last_video;
            ctx->last_video = h.timestamp;
            break;

        default:
            goto skip;
    }

    ngx_log_debug4(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
                  "play: read tag type=%i size=%uD timestamp=%uD "
                  "last_timestamp=%uD", 
                  (ngx_int_t) h.type,size, h.timestamp, last_timestamp);

    lh = h;
    lh.timestamp = last_timestamp;

    if (size > sizeof(ngx_rtmp_play_buffer)) {
        ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
                     "play: too big message: %D>%uz", size, 
                      sizeof(ngx_rtmp_play_buffer));
        goto next;
    }

    /* read tag body */
    n = ngx_read_file(&ctx->file, ngx_rtmp_play_buffer, size, 
                      ctx->offset - size - 4);
    if (n != (ssize_t) size) {
        ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
                     "play: could not read flv tag");
        return;
    }

    /* prepare input chain */
    ngx_memzero(&in, sizeof(in));
    ngx_memzero(&in_buf, sizeof(in_buf));
    in.buf = &in_buf;
    in_buf.pos = ngx_rtmp_play_buffer;
    in_buf.last = ngx_rtmp_play_buffer + size;

    /* output chain */
    out = ngx_rtmp_append_shared_bufs(cscf, NULL, &in);
    ngx_rtmp_prepare_message(s, &h, ctx->msg_mask & (1 << h.type) ? 
                             &lh : NULL, out);
    ngx_rtmp_send_message(s, out, 0); /* TODO: priority */
    ngx_rtmp_free_shared_chain(cscf, out);

    ctx->msg_mask |= (1 << h.type);

next:
    if (ctx->start_timestamp == -1) {
        ctx->start_timestamp = h.timestamp;
        ctx->epoch = ngx_current_msec;
        ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
                      "play: start_timestamp=%i", ctx->start_timestamp);
        goto skip;
    }

    buflen = (s->buflen ? s->buflen : NGX_RTMP_PLAY_DEFAULT_BUFLEN);
    end_timestamp = (ngx_current_msec - ctx->epoch) +
                     ctx->start_timestamp + buflen;

    ngx_log_debug5(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
           "play: %s wait=%D timestamp=%D end_timestamp=%D bufen=%i",
            h.timestamp > end_timestamp ? "schedule" : "advance",
            h.timestamp > end_timestamp ? h.timestamp - end_timestamp : 0,
            h.timestamp, end_timestamp, (ngx_int_t) buflen);

    /* too much data sent; schedule timeout */
    if (h.timestamp > end_timestamp) {
        ngx_add_timer(e, h.timestamp - end_timestamp);
        return;
    }

skip:
    ngx_post_event(e, &ngx_posted_events);
}
static void
ngx_rtmp_play_read_meta(ngx_rtmp_session_t *s)
{
    ngx_rtmp_play_ctx_t            *ctx;
    ssize_t                         n;
    ngx_rtmp_header_t               h;
    ngx_chain_t                    *out, in;
    ngx_buf_t                       in_buf;
    ngx_rtmp_core_srv_conf_t       *cscf;
    uint32_t                        size;

    cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);
    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_play_module);
    if (ctx == NULL) {
        return;
    }

    ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
                  "play: read meta");
    
    /* read tag header */
    n = ngx_read_file(&ctx->file, ngx_rtmp_play_header, 
                      sizeof(ngx_rtmp_play_header), NGX_RTMP_PLAY_DATA_OFFSET);
    if (n != sizeof(ngx_rtmp_play_header)) {
        ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
                     "play: could not read metadata tag header");
        return;
    }

    if (ngx_rtmp_play_header[0] != NGX_RTMP_MSG_AMF_META) {
        ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
                      "play: first tag is not metadata, giving up");
        return;
    }

    ngx_memzero(&h, sizeof(h));
    h.type = NGX_RTMP_MSG_AMF_META;
    h.msid = NGX_RTMP_LIVE_MSID;
    h.csid = NGX_RTMP_LIVE_CSID_META;
    size = 0;
    ngx_rtmp_rmemcpy(&size, ngx_rtmp_play_header + 1, 3);

    ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
                  "play: metadata size=%D", size);

    if (size > sizeof(ngx_rtmp_play_buffer)) {
        ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
                     "play: too big metadata");
        return;
    }

    /* read metadata */
    n = ngx_read_file(&ctx->file, ngx_rtmp_play_buffer, 
                      size, sizeof(ngx_rtmp_play_header) + 
                      NGX_RTMP_PLAY_DATA_OFFSET);
    if (n != (ssize_t) size) {
        ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
                      "play: could not read metadata");
        return;
    }

    /* prepare input chain */
    ngx_memzero(&in, sizeof(in));
    ngx_memzero(&in_buf, sizeof(in_buf));
    in.buf = &in_buf;
    in_buf.pos = ngx_rtmp_play_buffer;
    in_buf.last = ngx_rtmp_play_buffer + size;

    ngx_rtmp_play_init_index(s, &in);

    /* output chain */
    out = ngx_rtmp_append_shared_bufs(cscf, NULL, &in);
    ngx_rtmp_prepare_message(s, &h, NULL, out);
    ngx_rtmp_send_message(s, out, 0);
    ngx_rtmp_free_shared_chain(cscf, out);
}
예제 #6
0
static ngx_int_t
ngx_rtmp_flv_send(ngx_rtmp_session_t *s, ngx_file_t *f, ngx_uint_t *ts)
{
    ngx_rtmp_flv_ctx_t             *ctx;
    uint32_t                        last_timestamp;
    ngx_rtmp_header_t               h, lh;
    ngx_rtmp_core_srv_conf_t       *cscf;
    ngx_chain_t                    *out, in;
    ngx_buf_t                       in_buf;
    ngx_int_t                       rc;
    ssize_t                         n;
    uint32_t                        buflen, end_timestamp, size;

    cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);

    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_flv_module);

    if (ctx == NULL) {
        return NGX_ERROR;
    }

    if (ctx->offset == -1) {
        ctx->offset = ngx_rtmp_flv_timestamp_to_offset(s, f,
                      ctx->start_timestamp);
        ctx->start_timestamp = -1; /* set later from actual timestamp */
    }

    ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
                   "flv: read tag at offset=%i", ctx->offset);

    /* read tag header */
    n = ngx_read_file(f, ngx_rtmp_flv_header,
                      sizeof(ngx_rtmp_flv_header), ctx->offset);

    if (n != sizeof(ngx_rtmp_flv_header)) {
        ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
                      "flv: could not read flv tag header");
        return NGX_DONE;
    }

    /* parse header fields */
    ngx_memzero(&h, sizeof(h));

    h.msid = NGX_RTMP_MSID;
    h.type = ngx_rtmp_flv_header[0];

    size = 0;

    ngx_rtmp_rmemcpy(&size, ngx_rtmp_flv_header + 1, 3);
    ngx_rtmp_rmemcpy(&h.timestamp, ngx_rtmp_flv_header + 4, 3);

    ((u_char *) &h.timestamp)[3] = ngx_rtmp_flv_header[7];

    ctx->offset += (sizeof(ngx_rtmp_flv_header) + size + 4);

    last_timestamp = 0;

    switch (h.type) {

    case NGX_RTMP_MSG_AUDIO:
        h.csid = NGX_RTMP_CSID_AUDIO;
        last_timestamp = ctx->last_audio;
        ctx->last_audio = h.timestamp;
        break;

    case NGX_RTMP_MSG_VIDEO:
        h.csid = NGX_RTMP_CSID_VIDEO;
        last_timestamp = ctx->last_video;
        ctx->last_video = h.timestamp;
        break;

    default:
        return NGX_OK;
    }

    ngx_log_debug4(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
                   "flv: read tag type=%i size=%uD timestamp=%uD "
                   "last_timestamp=%uD",
                   (ngx_int_t) h.type,size, h.timestamp, last_timestamp);

    lh = h;
    lh.timestamp = last_timestamp;

    if (size > sizeof(ngx_rtmp_flv_buffer)) {
        ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
                      "flv: too big message: %D>%uz", size,
                      sizeof(ngx_rtmp_flv_buffer));
        goto next;
    }

    /* read tag body */
    n = ngx_read_file(f, ngx_rtmp_flv_buffer, size,
                      ctx->offset - size - 4);

    if (n != (ssize_t) size) {
        ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
                      "flv: could not read flv tag");
        return NGX_ERROR;
    }

    /* prepare input chain */
    ngx_memzero(&in, sizeof(in));
    ngx_memzero(&in_buf, sizeof(in_buf));

    in.buf = &in_buf;
    in_buf.pos  = ngx_rtmp_flv_buffer;
    in_buf.last = ngx_rtmp_flv_buffer + size;

    /* output chain */
    out = ngx_rtmp_append_shared_bufs(cscf, NULL, &in);

    ngx_rtmp_prepare_message(s, &h, ctx->msg_mask & (1 << h.type) ?
                             &lh : NULL, out);
    rc = ngx_rtmp_send_message(s, out, 0);
    ngx_rtmp_free_shared_chain(cscf, out);

    if (rc == NGX_AGAIN) {
        return NGX_AGAIN;
    }

    if (rc != NGX_OK) {
        return NGX_ERROR;
    }

    ctx->msg_mask |= (1 << h.type);

next:
    if (ctx->start_timestamp == -1) {
        ctx->start_timestamp = h.timestamp;
        ctx->epoch = ngx_current_msec;

        ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
                       "flv: start_timestamp=%i", ctx->start_timestamp);
        return NGX_OK;
    }

    buflen = s->buflen + NGX_RTMP_FLV_BUFLEN_ADDON;

    end_timestamp = (ngx_current_msec - ctx->epoch) +
                    ctx->start_timestamp + buflen;

    ngx_log_debug5(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
                   "flv: %s wait=%D timestamp=%D end_timestamp=%D bufen=%i",
                   h.timestamp > end_timestamp ? "schedule" : "advance",
                   h.timestamp > end_timestamp ? h.timestamp - end_timestamp : 0,
                   h.timestamp, end_timestamp, (ngx_int_t) buflen);

    s->current_time = h.timestamp;

    /* too much data sent; schedule timeout */
    if (h.timestamp > end_timestamp) {
        return h.timestamp - end_timestamp;
    }

    return NGX_OK;
}
static ngx_int_t
ngx_rtmp_hls_video(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, 
        ngx_chain_t *in)
{
    ngx_rtmp_hls_app_conf_t        *hacf;
    ngx_rtmp_hls_ctx_t             *ctx;
    ngx_rtmp_codec_ctx_t           *codec_ctx;
    AVPacket                        packet;
    u_char                         *p;
    uint8_t                         fmt, ftype, htype, llen;
    uint32_t                        len, rlen;
    ngx_buf_t                       out;
    static u_char                   buffer[NGX_RTMP_HLS_BUFSIZE];

    hacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_hls_module);
    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module);
    codec_ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module);
    if (hacf == NULL || !hacf->hls || ctx == NULL || codec_ctx == NULL 
            || h->mlen < 1)
    {
        return NGX_OK;
    }

    /* Only H264 is supported */
    if (codec_ctx->video_codec_id != NGX_RTMP_VIDEO_H264) {
        return NGX_OK;
    }

    p = in->buf->pos;
    if (ngx_rtmp_hls_copy(s, &fmt, &p, 1, &in) != NGX_OK) {
        return NGX_ERROR;
    }

    /* 1: keyframe (IDR)
     * 2: inter frame
     * 3: disposable inter frame */
    ftype = (fmt & 0xf0) >> 4;

    /* H264 HDR/PICT */
    if (ngx_rtmp_hls_copy(s, &htype, &p, 1, &in) != NGX_OK) {
        return NGX_ERROR;
    }
    /* proceed only with PICT */
    if (htype != 1) {
        return NGX_OK;
    }
    
    /* 3 bytes: decoder delay */
    if (ngx_rtmp_hls_copy(s, NULL, &p, 3, &in) != NGX_OK) {
        return NGX_ERROR;
    }

    out.pos = buffer;
    out.last = buffer + sizeof(buffer) - FF_INPUT_BUFFER_PADDING_SIZE;
    
    /* keyframe? */
    if (ftype == 1) {
        if (ngx_current_msec - ctx->frag_start > hacf->fraglen) {
            ngx_rtmp_hls_restart(s);
        }

        /* Prepend IDR frame with H264 header for random seeks */
        if (ngx_rtmp_hls_append_avc_header(s, &out) != NGX_OK) {
            ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
                "hls: error appenging H264 header");
        }
    }

    if (!ctx->opened || !ctx->video) {
        return NGX_OK;
    }

    if (ctx->nal_bytes == -1) {
        ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
                "hls: nal length size is unknown, "
                "waiting for IDR to parse header");
        return NGX_OK;
    }

    ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
            "hls: parsing NALs");

    while (in) {
        llen = ctx->nal_bytes;
        if (ngx_rtmp_hls_copy(s, &rlen, &p, llen, &in) != NGX_OK) {
            return NGX_OK;
        }
        len = 0;
        ngx_rtmp_rmemcpy(&len, &rlen, llen);

        ngx_log_debug4(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
                "hls: NAL type=%i llen=%i len=%uD unit_type=%i",
                (ngx_int_t)ftype, (ngx_int_t)llen, len, (ngx_int_t)(*p & 0x1f));

        /* AnnexB prefix */
        if (out.last - out.pos < 4) {
            ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
                    "hls: not enough buffer for AnnexB prefix");
            return NGX_OK;
        }

        /* first AnnexB prefix is long (4 bytes) */
        if (out.pos == buffer) {
            *out.pos++ = 0;
        }
        *out.pos++ = 0;
        *out.pos++ = 0;
        *out.pos++ = 1;

        /* NAL body */
        if (out.last - out.pos < (ngx_int_t) len) {
            ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
                    "hls: not enough buffer for NAL");
            return NGX_OK;
        }
        if (ngx_rtmp_hls_copy(s, out.pos, &p, len, &in) != NGX_OK) {
            return NGX_ERROR;
        }
        out.pos += len;
    }

    av_init_packet(&packet);
    packet.dts = h->timestamp * 90;
    packet.pts = packet.dts;
    packet.stream_index = ctx->out_vstream;
    /*
    if (ftype == 1) {
        packet.flags |= AV_PKT_FLAG_KEY;
    }*/
    packet.data = buffer;
    packet.size = out.pos - buffer;

    if (av_interleaved_write_frame(ctx->out_format, &packet) < 0) {
        ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
                "hls: av_interleaved_write_frame failed");
    }

    return NGX_OK;
}
static ngx_int_t
ngx_rtmp_hls_append_avc_header(ngx_rtmp_session_t *s, ngx_buf_t *out)
{
    ngx_rtmp_codec_ctx_t           *codec_ctx;
    u_char                         *p;
    ngx_chain_t                    *in;
    ngx_rtmp_hls_ctx_t             *ctx;
    int8_t                          nnals;
    uint16_t                        len, rlen;
    ngx_int_t                       n;

    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module);
    codec_ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module);
    if (ctx == NULL || codec_ctx == NULL) {
        return NGX_ERROR;
    }


    in = codec_ctx->avc_header;

    if (in == NULL) {
        return NGX_ERROR;
    }
    p = in->buf->pos;

    /* skip bytes:
     * - flv fmt
     * - H264 CONF/PICT (0x00)
     * - 0
     * - 0
     * - 0
     * - version
     * - profile
     * - compatibility
     * - level */
    if (ngx_rtmp_hls_copy(s, NULL, &p, 9, &in) != NGX_OK) {
        return NGX_ERROR;
    }

    /* NAL size length (1,2,4) */
    if (ngx_rtmp_hls_copy(s, &ctx->nal_bytes, &p, 1, &in) != NGX_OK) {
        return NGX_ERROR;
    }
    ctx->nal_bytes &= 0x03; /* 2 lsb */
    ++ctx->nal_bytes;
    ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
            "hls: NAL size bytes: %uz", ctx->nal_bytes);

    /* number of SPS NALs */
    if (ngx_rtmp_hls_copy(s, &nnals, &p, 1, &in) != NGX_OK) {
        return NGX_ERROR;
    }
    nnals &= 0x1f; /* 5lsb */
    ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
            "hls: SPS number: %uz", nnals);

    /* SPS */
    for (n = 0; ; ++n) {
        for (; nnals; --nnals) {
            /* NAL length */
            if (ngx_rtmp_hls_copy(s, &rlen, &p, 2, &in) != NGX_OK) {
                return NGX_ERROR;
            }
            ngx_rtmp_rmemcpy(&len, &rlen, 2);
            ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
                    "hls: header NAL length: %uz", (size_t)len);

            /* AnnexB prefix */
            if (out->last - out->pos < 4) {
                ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
                        "hls: too small buffer for header NAL length");
                return NGX_ERROR;
            }
            *out->pos++ = 0;
            *out->pos++ = 0;
            *out->pos++ = 0;
            *out->pos++ = 1;

            /* NAL body */
            if (out->last - out->pos < len) {
                ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
                        "hls: too small buffer for header NAL");
                return NGX_ERROR;
            }
            if (ngx_rtmp_hls_copy(s, out->pos, &p, len, &in) != NGX_OK) {
                return NGX_ERROR;
            }
            out->pos += len;
        }

        if (n == 1) {
            break;
        }

        /* number of PPS NALs */
        if (ngx_rtmp_hls_copy(s, &nnals, &p, 1, &in) != NGX_OK) {
            return NGX_ERROR;
        }
        ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
                "hls: PPS number: %uz", nnals);
    }

    return NGX_OK;
}