From 0e01665e0bd6b473e2a09304890c1841a09d50b7 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Mon, 4 Mar 2013 21:13:17 +0400 Subject: [PATCH] native hls; first version without ffmpeg libs --- hls/config | 4 +- hls/ngx_rtmp_hls_module.c | 756 ++++++++++++-------------------------- hls/ngx_rtmp_mpegts.c | 237 ++++++++++++ hls/ngx_rtmp_mpegts.h | 28 ++ 4 files changed, 509 insertions(+), 516 deletions(-) create mode 100644 hls/ngx_rtmp_mpegts.c create mode 100644 hls/ngx_rtmp_mpegts.h diff --git a/hls/config b/hls/config index 24624cb..1b5c83e 100644 --- a/hls/config +++ b/hls/config @@ -7,7 +7,5 @@ CORE_MODULES="$CORE_MODULES NGX_ADDON_SRCS="$NGX_ADDON_SRCS \ $ngx_addon_dir/ngx_rtmp_hls_module.c \ + $ngx_addon_dir/ngx_rtmp_mpegts.c \ " - -CORE_LIBS="$CORE_LIBS -lavformat -lavcodec -lavutil" - diff --git a/hls/ngx_rtmp_hls_module.c b/hls/ngx_rtmp_hls_module.c index c5d2030..7460cac 100644 --- a/hls/ngx_rtmp_hls_module.c +++ b/hls/ngx_rtmp_hls_module.c @@ -1,16 +1,12 @@ /* - * Copyright (c) 2012 Roman Arutyunyan + * Copyright (c) 2013 Roman Arutyunyan */ #include #include #include - - -#include -#include -#include +#include "ngx_rtmp_mpegts.h" static ngx_rtmp_publish_pt next_publish; @@ -20,37 +16,7 @@ static ngx_rtmp_delete_stream_pt next_delete_stream; static ngx_int_t ngx_rtmp_hls_postconfiguration(ngx_conf_t *cf); static void * ngx_rtmp_hls_create_app_conf(ngx_conf_t *cf); static char * ngx_rtmp_hls_merge_app_conf(ngx_conf_t *cf, - void *parent, void *child); - - -static ngx_log_t *ngx_rtmp_hls_log; - -static void -ngx_rtmp_hls_av_log_callback(void* avcl, int level, const char* fmt, - va_list args) -{ - char *p; - static char buf[1024]; - int n; - - - n = vsnprintf(buf, sizeof(buf), fmt, args); - buf[n] = 0; - - for (p = buf; *p; ++p) { - if (*p == (u_char)'\n' || *p == (u_char)'\r') { - *p = ' '; - } - } - - if (level <= AV_LOG_ERROR) { - ngx_log_error_core(NGX_LOG_ERR, ngx_rtmp_hls_log, 0, "hls: av: %s", - buf); - return; - } - - ngx_log_debug1(NGX_LOG_DEBUG_RTMP, ngx_rtmp_hls_log, 0, "hls: av: %s", buf); -} + void *parent, void *child); #define NGX_RTMP_HLS_BUFSIZE (1024*1024) @@ -64,9 +30,8 @@ typedef struct { unsigned publishing:1; unsigned opened:1; - unsigned audio:1; - unsigned video:1; - unsigned header_sent:1; + + ngx_file_t file; ngx_str_t playlist; ngx_str_t playlist_bak; @@ -75,15 +40,13 @@ typedef struct { ngx_int_t frag; - ngx_int_t out_vstream; - ngx_int_t out_astream; - int8_t nal_bytes; + ngx_uint_t audio_cc; + ngx_uint_t video_cc; int64_t aframe_base; int64_t aframe_num; - AVFormatContext *out_format; - + int8_t nal_bytes; } ngx_rtmp_hls_ctx_t; @@ -187,69 +150,14 @@ ngx_module_t ngx_rtmp_hls_module = { }; -/* convert Flash codec id to FFmpeg codec id */ -#if 0 -static enum CodecID -ngx_rtmp_hls_get_video_codec(ngx_int_t cid) -{ - switch (cid) { - case 1: - return CODEC_ID_JPEG2000; /* JPEG */ - case 2: - return CODEC_ID_FLV1; /* Sorensen H.263 */ - case 3: - return CODEC_ID_FLASHSV; /* Screen video */ - case 4: - return CODEC_ID_VP6F; /* On2 VP6 */ - case 5: - return CODEC_ID_VP6A; /* On2 VP6 Alpha */ - case 6: - return CODEC_ID_FLASHSV2; /* Screen Video 2 */ - case 7: - return CODEC_ID_H264; /* H264 / MPEG4-AVC */ - default: - return CODEC_ID_NONE; - } -} -#endif - -static enum CodecID -ngx_rtmp_hls_get_audio_codec(ngx_int_t cid) -{ - switch (cid) { - case 0: - return CODEC_ID_NONE; /* Uncompressed */ - case 1: - return CODEC_ID_ADPCM_SWF; /* ADPCM */ - case 2: - case 14: - return CODEC_ID_MP3; /* Mp3 */ - case 4: - case 5: - case 6: - return CODEC_ID_NELLYMOSER; /* Nellymoser */ - case 7: - return CODEC_ID_PCM_ALAW; /* G711 */ - case 8: - return CODEC_ID_PCM_MULAW; /* G711Mu */ - case 10: - return CODEC_ID_AAC; /* AAC */ - case 11: - return CODEC_ID_SPEEX; /* Speex */ - default: - return CODEC_ID_NONE; - } -} - - static size_t ngx_rtmp_hls_chain2buffer(u_char *buffer, size_t size, ngx_chain_t *in, - size_t skip) + size_t skip) { ngx_buf_t out; out.pos = buffer; - out.last = buffer + size - FF_INPUT_BUFFER_PADDING_SIZE; + out.last = buffer + size; for (; in; in = in->next) { size = in->buf->last - in->buf->pos; @@ -266,125 +174,6 @@ ngx_rtmp_hls_chain2buffer(u_char *buffer, size_t size, ngx_chain_t *in, } -static ngx_int_t -ngx_rtmp_hls_init_video(ngx_rtmp_session_t *s) -{ - AVStream *stream; - ngx_rtmp_hls_ctx_t *ctx; - ngx_rtmp_codec_ctx_t *codec_ctx; - - - ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module); - codec_ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module); - - if (codec_ctx == NULL - || codec_ctx->video_codec_id != NGX_RTMP_VIDEO_H264 - || ctx->video == 1 - || ctx->out_format == NULL) - { - return NGX_OK; - } - - ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, - "hls: adding video stream"); - - stream = avformat_new_stream(ctx->out_format, NULL); - if (stream == NULL) { - ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, - "hls: av_new_stream failed (video)"); - return NGX_ERROR; - } - - stream->codec->codec_id = CODEC_ID_H264; - stream->codec->codec_type = AVMEDIA_TYPE_VIDEO; - stream->codec->pix_fmt = PIX_FMT_YUV420P; - stream->codec->time_base.den = 25; - stream->codec->time_base.num = 1; - stream->codec->width = 100; - stream->codec->height = 100; - - if (ctx->out_format->oformat->flags & AVFMT_GLOBALHEADER) { - stream->codec->flags |= CODEC_FLAG_GLOBAL_HEADER; - } - - ctx->out_vstream = stream->index; - ctx->video = 1; - - ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, - "hls: video stream: %i", ctx->out_vstream); - - /*if (ctx->header_sent) { - if (av_write_trailer(ctx->out_format) < 0) { - ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, - "hls: av_write_trailer failed"); - } - ctx->header_sent = 0; - }*/ - - return NGX_OK; -} - - -static ngx_int_t -ngx_rtmp_hls_init_audio(ngx_rtmp_session_t *s) -{ - AVStream *stream; - ngx_rtmp_hls_ctx_t *ctx; - ngx_rtmp_codec_ctx_t *codec_ctx; - enum CodecID cid; - - - ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module); - codec_ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module); - - if (codec_ctx == NULL || ctx->audio == 1 || ctx->out_format == NULL) { - return NGX_OK; - } - - ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, - "hls: adding audio stream"); - - cid = ngx_rtmp_hls_get_audio_codec(codec_ctx->audio_codec_id); - if (cid == CODEC_ID_NONE) { - ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, - "hls: no audio"); - return NGX_OK; - } - - stream = avformat_new_stream(ctx->out_format, NULL); - if (stream == NULL) { - ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, - "hls: av_new_stream failed (audio)"); - return NGX_ERROR; - } - - stream->codec->codec_id = cid; - stream->codec->codec_type = AVMEDIA_TYPE_AUDIO; - stream->codec->sample_fmt = (codec_ctx->sample_size == 1 ? - AV_SAMPLE_FMT_U8 : AV_SAMPLE_FMT_S16); - stream->codec->sample_rate = 48000;/*codec_ctx->sample_rate;*/ - stream->codec->bit_rate = 2000000; - stream->codec->channels = codec_ctx->audio_channels; - - ctx->out_astream = stream->index; - ctx->audio = 1; - - ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, - "hls: audio stream: %i %iHz", - ctx->out_astream, codec_ctx->sample_rate); -/* - if (ctx->header_sent) { - if (av_write_trailer(ctx->out_format) < 0) { - ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, - "hls: av_write_trailer failed"); - } - ctx->header_sent = 0; - } -*/ - return NGX_OK; -} - - static ngx_int_t ngx_rtmp_hls_update_playlist(ngx_rtmp_session_t *s) { @@ -400,26 +189,29 @@ ngx_rtmp_hls_update_playlist(ngx_rtmp_session_t *s) hacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_hls_module); ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module); - nretry = 0; retry: + fd = ngx_open_file(ctx->playlist_bak.data, NGX_FILE_WRONLY, - NGX_FILE_TRUNCATE, NGX_FILE_DEFAULT_ACCESS); + NGX_FILE_TRUNCATE, NGX_FILE_DEFAULT_ACCESS); if (fd == NGX_INVALID_FILE) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno, - "hls: open failed: '%V'", - &ctx->playlist_bak); + "hls: open failed: '%V'", &ctx->playlist_bak); + /* try to create parent folder */ + if (nretry == 0 && ngx_create_dir(hacf->path.data, NGX_RTMP_HLS_DIR_ACCESS) != NGX_INVALID_FILE) { ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, - "hls: creating target folder: '%V'", &hacf->path); + "hls: creating target folder: '%V'", &hacf->path); ++nretry; goto retry; } + return NGX_ERROR; } @@ -429,31 +221,31 @@ retry: } p = ngx_snprintf(buffer, sizeof(buffer), - "#EXTM3U\r\n" - "#EXT-X-TARGETDURATION:%i\r\n" - "#EXT-X-ALLOW-CACHE:NO\r\n" - "#EXT-X-MEDIA-SEQUENCE:%i\r\n\r\n", - /*TODO: float*/(ngx_int_t)(hacf->fraglen / 1000), ffrag); + "#EXTM3U\r\n" + "#EXT-X-TARGETDURATION:%i\r\n" + "#EXT-X-ALLOW-CACHE:NO\r\n" + "#EXT-X-MEDIA-SEQUENCE:%i\r\n\r\n", + (ngx_int_t) (hacf->fraglen / 1000), ffrag); + n = write(fd, buffer, p - buffer); if (n < 0) { ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno, - "hls: write failed: '%V'", - &ctx->playlist_bak); + "hls: write failed: '%V'", &ctx->playlist_bak); ngx_close_file(fd); return NGX_ERROR; } for (; ffrag < ctx->frag; ++ffrag) { p = ngx_snprintf(buffer, sizeof(buffer), - "#EXTINF:%i,\r\n" - "%V-%i.ts\r\n", - /*TODO:float*/(ngx_int_t)(hacf->fraglen / 1000), - &ctx->name, ffrag); + "#EXTINF:%i,\r\n" + "%V-%i.ts\r\n", + (ngx_int_t) (hacf->fraglen / 1000), + &ctx->name, ffrag); + n = write(fd, buffer, p - buffer); if (n < 0) { ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno, - "hls: write failed: '%V'", - &ctx->playlist_bak); + "hls: write failed: '%V'", &ctx->playlist_bak); ngx_close_file(fd); return NGX_ERROR; } @@ -463,8 +255,8 @@ retry: if (ngx_rename_file(ctx->playlist_bak.data, ctx->playlist.data)) { ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno, - "hls: rename failed: '%V'->'%V'", - &ctx->playlist_bak, &ctx->playlist); + "hls: rename failed: '%V'->'%V'", + &ctx->playlist_bak, &ctx->playlist); return NGX_ERROR; } @@ -472,113 +264,12 @@ retry: } -static ngx_int_t -ngx_rtmp_hls_initialize(ngx_rtmp_session_t *s) -{ - ngx_rtmp_hls_ctx_t *ctx; - ngx_rtmp_hls_app_conf_t *hacf; - AVOutputFormat *format; - - hacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_hls_module); - ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module); - if (ctx == NULL || ctx->out_format || ctx->publishing == 0) { - return NGX_OK; - } - - ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, - "hls: initialize stream"); - - /* create output format */ - format = av_guess_format("mpegts", NULL, NULL); - if (format == NULL) { - ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, - "hls: av_guess_format failed"); - return NGX_ERROR; - } - ctx->out_format = avformat_alloc_context(); - if (ctx->out_format == NULL) { - ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, - "hls: avformat_alloc_context failed"); - return NGX_ERROR; - } - ctx->out_format->oformat = format; - ctx->out_format->max_delay = (int64_t)hacf->muxdelay * AV_TIME_BASE / 1000; - - return NGX_ERROR; -} - - -static ngx_int_t -ngx_rtmp_hls_open_file(ngx_rtmp_session_t *s, u_char *fpath) -{ - ngx_rtmp_hls_ctx_t *ctx; - ngx_rtmp_codec_ctx_t *codec_ctx; - AVStream *astream; - static u_char buffer[NGX_RTMP_HLS_BUFSIZE]; - - 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 || ctx->out_format == NULL) { - return NGX_OK; - } - - if (!ctx->video && !ctx->audio) { - return NGX_OK; - } - - ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, - "hls: open stream file '%s'", fpath); - - /* open file */ - if (avio_open(&ctx->out_format->pb, (char *)fpath, AVIO_FLAG_WRITE) < 0) { - ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, - "hls: avio_open failed"); - return NGX_ERROR; - } - - astream = NULL; - if (codec_ctx && codec_ctx->audio_codec_id == NGX_RTMP_AUDIO_AAC - && codec_ctx->aac_header && codec_ctx->aac_header->buf->last - - codec_ctx->aac_header->buf->pos > 2 - && ctx->audio) - { - astream = ctx->out_format->streams[ctx->out_astream]; - astream->codec->extradata = buffer; - astream->codec->extradata_size = ngx_rtmp_hls_chain2buffer(buffer, - sizeof(buffer), codec_ctx->aac_header, 2); - ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, - "hls: setting AAC extradata %i bytes", - (ngx_int_t)astream->codec->extradata_size); - } - - /* write header */ - if (!ctx->header_sent && - avformat_write_header(ctx->out_format, NULL) < 0) - { - ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, - "hls: avformat_write_header failed"); - return NGX_ERROR; - } - ctx->header_sent = 1; - - if (astream) { - astream->codec->extradata = NULL; - astream->codec->extradata_size = 0; - } - - ctx->opened = 1; - ctx->nal_bytes = -1; - - return NGX_OK; -} - - static ngx_int_t ngx_rtmp_hls_copy(ngx_rtmp_session_t *s, void *dst, u_char **src, size_t n, - ngx_chain_t **in) + ngx_chain_t **in) { - u_char *last; - size_t pn; + u_char *last; + size_t pn; if (*in == NULL) { return NGX_ERROR; @@ -586,32 +277,40 @@ ngx_rtmp_hls_copy(ngx_rtmp_session_t *s, void *dst, u_char **src, size_t n, for ( ;; ) { last = (*in)->buf->last; + if ((size_t)(last - *src) >= n) { if (dst) { ngx_memcpy(dst, *src, n); } + *src += n; + while (*in && *src == (*in)->buf->last) { *in = (*in)->next; if (*in) { *src = (*in)->buf->pos; } } + return NGX_OK; } pn = last - *src; + if (dst) { ngx_memcpy(dst, *src, pn); dst = (u_char *)dst + pn; } + n -= pn; *in = (*in)->next; + if (*in == NULL) { ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, - "hls: failed to read %uz byte(s)", n); + "hls: failed to read %uz byte(s)", n); return NGX_ERROR; } + *src = (*in)->buf->pos; } } @@ -629,20 +328,22 @@ ngx_rtmp_hls_append_avc_header(ngx_rtmp_session_t *s, ngx_buf_t *out) 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: + /* + * Skip bytes: * - flv fmt * - H264 CONF/PICT (0x00) * - 0 @@ -651,7 +352,9 @@ ngx_rtmp_hls_append_avc_header(ngx_rtmp_session_t *s, ngx_buf_t *out) * - version * - profile * - compatibility - * - level */ + * - level + */ + if (ngx_rtmp_hls_copy(s, NULL, &p, 9, &in) != NGX_OK) { return NGX_ERROR; } @@ -660,36 +363,45 @@ ngx_rtmp_hls_append_avc_header(ngx_rtmp_session_t *s, ngx_buf_t *out) 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); + "h264: 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); + "h264: 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); + "h264: 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"); + "h264: too small buffer for header NAL size"); return NGX_ERROR; } + *out->pos++ = 0; *out->pos++ = 0; *out->pos++ = 0; @@ -698,12 +410,14 @@ ngx_rtmp_hls_append_avc_header(ngx_rtmp_session_t *s, ngx_buf_t *out) /* 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"); + "h264: 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; } @@ -715,97 +429,82 @@ ngx_rtmp_hls_append_avc_header(ngx_rtmp_session_t *s, ngx_buf_t *out) 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); + "h264: PPS number: %uz", nnals); } return NGX_OK; } -static ngx_int_t -ngx_rtmp_hls_close_file(ngx_rtmp_session_t *s) -{ - ngx_rtmp_hls_ctx_t *ctx; - - - ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module); - - if (av_write_trailer(ctx->out_format) < 0) { - ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, - "hls: av_write_trailer failed"); - } - ctx->header_sent = 0; - - avio_flush(ctx->out_format->pb); - - if (avio_close(ctx->out_format->pb) < 0) { - ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, - "hls: avio_close failed"); - } - - ctx->opened = 0; - - return NGX_OK; -} - - static void ngx_rtmp_hls_restart(ngx_rtmp_session_t *s) { - ngx_rtmp_hls_app_conf_t *hacf; - ngx_rtmp_hls_ctx_t *ctx; - + ngx_rtmp_hls_app_conf_t *hacf; + ngx_rtmp_hls_ctx_t *ctx; ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module); + hacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_hls_module); + if (ctx == NULL || hacf == NULL) { return; } ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, - "hls: restart frag=%i", ctx->frag); + "hls: restart frag=%i", ctx->frag); if (ctx->opened) { - ngx_rtmp_hls_close_file(s); + ngx_close_file(ctx->file.fd); + ctx->opened = 0; } - if (ngx_rtmp_hls_initialize(s) != NGX_OK) { - return; - } + /* + * Erase old file + * We should keep old fragments available whole next cycle + */ - if (ngx_rtmp_hls_init_video(s) != NGX_OK) { - ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, - "hls: video init failed"); - } - - if (ngx_rtmp_hls_init_audio(s) != NGX_OK) { - ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, - "hls: audio init failed"); - } - - /* remember we have preallocated memory in ctx->stream */ - - /* erase old file; - * we should keep old fragments available - * whole next cycle */ - if (ctx->frag > (ngx_int_t)hacf->nfrags * 2) { + if (ctx->frag > (ngx_int_t) hacf->nfrags * 2) { *ngx_sprintf(ctx->stream.data + ctx->stream.len, "-%i.ts", - ctx->frag - hacf->nfrags * 2) = 0; - ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, - "hls: delete stream file '%s'", ctx->stream.data); + ctx->frag - hacf->nfrags * 2) = 0; + ngx_delete_file(ctx->stream.data); + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "hls: delete stream file '%s'", ctx->stream.data); } ++ctx->frag; ctx->frag_start = ngx_current_msec; - /* create new one */ - *ngx_sprintf(ctx->stream.data + ctx->stream.len, "-%i.ts", - ctx->frag) = 0; - ngx_rtmp_hls_open_file(s, ctx->stream.data); + /* we have preallocated memory in ctx->stream */ + + *ngx_sprintf(ctx->stream.data + ctx->stream.len, "-%i.ts", ctx->frag) = 0; + + ngx_memzero(&ctx->file, sizeof(ctx->file)); + + ctx->file.log = s->connection->log; + + ngx_str_set(&ctx->file.name, "hls"); + + ctx->file.fd = ngx_open_file(ctx->stream.data, NGX_FILE_WRONLY, + NGX_FILE_TRUNCATE, NGX_FILE_DEFAULT_ACCESS); + + if (ctx->file.fd == NGX_INVALID_FILE) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno, + "hls: error creating fragment file"); + return; + } + + if (ngx_rtmp_mpegts_write_header(&ctx->file) != NGX_OK) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno, + "hls: error writing fragment header"); + return; + } + + ctx->opened = 1; - /* update playlist */ ngx_rtmp_hls_update_playlist(s); } @@ -893,38 +592,45 @@ ngx_rtmp_hls_publish(ngx_rtmp_session_t *s, ngx_rtmp_publish_t *v) } ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, - "hls: publish: name='%s' type='%s'", - v->name, v->type); + "hls: publish: name='%s' type='%s'", + v->name, v->type); - /* create context */ ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module); if (ctx == NULL) { ctx = ngx_palloc(s->connection->pool, sizeof(ngx_rtmp_hls_ctx_t)); ngx_rtmp_set_ctx(s, ctx, ngx_rtmp_hls_module); } + ngx_memzero(ctx, sizeof(ngx_rtmp_hls_ctx_t)); - /* init names & paths */ + /*TODO: escaping does not solve the problem*/ + len = ngx_strlen(v->name); - ctx->name.len = len + (ngx_uint_t)ngx_escape_uri(NULL, v->name, len, - NGX_ESCAPE_URI_COMPONENT); - ctx->name.data = ngx_palloc(s->connection->pool, - ctx->name.len); + ctx->name.len = len + (ngx_uint_t) ngx_escape_uri(NULL, v->name, len, + NGX_ESCAPE_URI_COMPONENT); + ctx->name.data = ngx_palloc(s->connection->pool, ctx->name.len); + ngx_escape_uri(ctx->name.data, v->name, len, NGX_ESCAPE_URI_COMPONENT); + ctx->playlist.data = ngx_palloc(s->connection->pool, - hacf->path.len + 1 + ctx->name.len + sizeof(".m3u8")); + hacf->path.len + 1 + ctx->name.len + + sizeof(".m3u8")); p = ngx_cpymem(ctx->playlist.data, hacf->path.data, hacf->path.len); + if (p[-1] != '/') { *p++ = '/'; } + p = ngx_cpymem(p, ctx->name.data, ctx->name.len); /* ctx->stream_path holds initial part of stream file path * however the space for the whole stream path * is allocated */ + ctx->stream.len = p - ctx->playlist.data; ctx->stream.data = ngx_palloc(s->connection->pool, - ctx->stream.len + 1 + NGX_OFF_T_LEN + sizeof(".ts")); + ctx->stream.len + 1 + NGX_OFF_T_LEN + sizeof(".ts")); + ngx_memcpy(ctx->stream.data, ctx->playlist.data, ctx->stream.len); /* playlist path */ @@ -934,16 +640,17 @@ ngx_rtmp_hls_publish(ngx_rtmp_session_t *s, ngx_rtmp_publish_t *v) /* playlist bak (new playlist) path */ ctx->playlist_bak.data = ngx_palloc(s->connection->pool, - ctx->playlist.len + sizeof(".bak")); + ctx->playlist.len + sizeof(".bak")); p = ngx_cpymem(ctx->playlist_bak.data, ctx->playlist.data, - ctx->playlist.len); + ctx->playlist.len); p = ngx_cpymem(p, ".bak", sizeof(".bak") - 1); + ctx->playlist_bak.len = p - ctx->playlist_bak.data; *p = 0; ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, - "hls: playlist='%V' playlist_bak='%V' stream_pattern='%V'", - &ctx->playlist, &ctx->playlist_bak, &ctx->stream); + "hls: playlist='%V' playlist_bak='%V' stream_pattern='%V'", + &ctx->playlist, &ctx->playlist_bak, &ctx->stream); if (hacf->continuous) { ngx_rtmp_hls_restore_frag(s); @@ -965,29 +672,21 @@ ngx_rtmp_hls_delete_stream(ngx_rtmp_session_t *s, ngx_rtmp_delete_stream_t *v) ngx_rtmp_hls_ctx_t *ctx; hacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_hls_module); + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module); - if (hacf == NULL || !hacf->hls || ctx == NULL - || ctx->publishing == 0) - { + + if (hacf == NULL || !hacf->hls || ctx == NULL || !ctx->publishing) { goto next; } ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, - "hls: delete"); + "hls: delete"); ctx->publishing = 0; - if (ctx->out_format == NULL) { - goto next; - } - if (ctx->opened) { - ngx_rtmp_hls_close_file(s); - } - - if (ctx->out_format) { - avformat_free_context(ctx->out_format); - ctx->out_format = NULL; + ngx_close_file(ctx->file.fd); + ctx->opened = 0; } next: @@ -997,42 +696,53 @@ next: static ngx_int_t ngx_rtmp_hls_audio(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, - ngx_chain_t *in) + 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; int64_t dts, ddts; + ngx_rtmp_mpegts_frame_t frame; + 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) + + if (hacf == NULL || !hacf->hls || ctx == NULL || + codec_ctx == NULL || h->mlen < 1) { return NGX_OK; } /* fragment is restarted in video handler; * however if video stream is missing then do it here */ - if (ctx->video == 0 - && ngx_current_msec - ctx->frag_start > hacf->fraglen) + + if (codec_ctx->avc_header == NULL && + ngx_current_msec - ctx->frag_start > hacf->fraglen) { ngx_rtmp_hls_restart(s); } - if (!ctx->opened || !ctx->audio) { + if (!ctx->opened) { return NGX_OK; } - /* write to file */ - av_init_packet(&packet); - packet.dts = h->timestamp * 90L; - packet.stream_index = ctx->out_astream; - packet.data = buffer; - packet.size = ngx_rtmp_hls_chain2buffer(buffer, sizeof(buffer), in, 1); + ngx_memzero(&frame, sizeof(frame)); + + frame.dts = h->timestamp * 90L; + frame.cc = ctx->audio_cc; + frame.pid = 0x101; + frame.sid = 0xc0; + + ngx_memzero(&out, sizeof(out)); + + out.pos = buffer; + out.last = out.pos + + ngx_rtmp_hls_chain2buffer(buffer, sizeof(buffer), in, 1); if (hacf->sync && codec_ctx->sample_rate) { @@ -1041,7 +751,7 @@ ngx_rtmp_hls_audio(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, dts = ctx->aframe_base + ctx->aframe_num * 90000 * 1024 / codec_ctx->sample_rate; - ddts = dts - packet.dts; + ddts = dts - frame.dts; ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "hls: sync stat ddts=%L (%.5fs)", @@ -1050,63 +760,68 @@ ngx_rtmp_hls_audio(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, if (ddts > (int64_t) hacf->sync * 90 || ddts < (int64_t) hacf->sync * -90) { - ctx->aframe_base = packet.dts; + ctx->aframe_base = frame.dts; ctx->aframe_num = 0; ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "hls: sync breakup ddts=%L (%.5fs)", ddts, ddts / 90000.); } else { - packet.dts = dts; + frame.dts = dts; } ctx->aframe_num++; } - packet.pts = packet.dts; + frame.pts = frame.dts; if (codec_ctx->audio_codec_id == NGX_RTMP_AUDIO_AAC) { - if (packet.size == 0) { + if (out.pos == out.last) { ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, - "hls: malformed AAC packet"); + "hls: malformed AAC packet"); return NGX_OK; } - ++packet.data; - --packet.size; + out.last--; } ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, - "hls: audio dts=%L timestamp=%uD", (int64_t)packet.dts, - h->timestamp); + "hls: audio dts=%uL timestamp=%uD", + frame.dts, h->timestamp); - if (av_interleaved_write_frame(ctx->out_format, &packet) < 0) { + if (ngx_rtmp_mpegts_write_frame(&ctx->file, &frame, &out) != NGX_OK) { ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, - "hls: av_interleaved_write_frame failed"); + "hls: audio frame failed"); } + + ctx->audio_cc = frame.cc; + 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_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]; int32_t cts; + ngx_rtmp_mpegts_frame_t frame; + 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) + + if (hacf == NULL || !hacf->hls || ctx == NULL || + codec_ctx == NULL || h->mlen < 1) { return NGX_OK; } @@ -1124,110 +839,131 @@ ngx_rtmp_hls_video(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, /* 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, &cts, &p, 3, &in) != NGX_OK) { return NGX_ERROR; } - cts = ((cts & 0x00FF0000) >> 16) | ((cts & 0x000000FF) << 16) - | (cts & 0x0000FF00); + + cts = ((cts & 0x00FF0000) >> 16) | ((cts & 0x000000FF) << 16) | + (cts & 0x0000FF00); + + ngx_memzero(&out, sizeof(out)); out.pos = buffer; - out.last = buffer + sizeof(buffer) - FF_INPUT_BUFFER_PADDING_SIZE; + out.last = buffer + sizeof(buffer); /* 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"); + "hls: error appenging H264 header"); } } - if (!ctx->opened || !ctx->video) { + if (!ctx->opened || codec_ctx->avc_header == NULL) { return NGX_OK; } - if (ctx->nal_bytes == -1) { + if (ctx->nal_bytes == 0) { ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, - "hls: nal length size is unknown, " - "waiting for IDR to parse header"); + "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)); + "hls: h264 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"); + "hls: not enough buffer for AnnexB prefix"); return NGX_OK; } + /*TODO: add AUD NAL*/ + /* 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"); + "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 * 90L; - packet.pts = packet.dts + cts * 90; - packet.stream_index = ctx->out_vstream; + /*TODO*/ + out.last = out.pos; + out.pos = buffer; - if (ftype == 1) { - packet.flags |= AV_PKT_FLAG_KEY; - } + ngx_memzero(&frame, sizeof(frame)); - packet.data = buffer; - packet.size = out.pos - buffer; + frame.cc = ctx->video_cc; + frame.dts = h->timestamp * 90L; + frame.pts = frame.dts + cts * 90; + frame.pid = 0x100; + frame.sid = 0xe0; + frame.key = (ftype == 1); - if (av_interleaved_write_frame(ctx->out_format, &packet) < 0) { + if (ngx_rtmp_mpegts_write_frame(&ctx->file, &frame, &out) != NGX_OK) { ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, - "hls: av_interleaved_write_frame failed"); + "hls: video frame failed"); } + ctx->video_cc = frame.cc; + return NGX_OK; } @@ -1257,8 +993,8 @@ ngx_rtmp_hls_create_app_conf(ngx_conf_t *cf) static char * ngx_rtmp_hls_merge_app_conf(ngx_conf_t *cf, void *parent, void *child) { - ngx_rtmp_hls_app_conf_t *prev = parent; - ngx_rtmp_hls_app_conf_t *conf = child; + ngx_rtmp_hls_app_conf_t *prev = parent; + ngx_rtmp_hls_app_conf_t *conf = child; ngx_conf_merge_value(conf->hls, prev->hls, 0); ngx_conf_merge_msec_value(conf->fraglen, prev->fraglen, 5000); @@ -1267,11 +1003,13 @@ ngx_rtmp_hls_merge_app_conf(ngx_conf_t *cf, void *parent, void *child) ngx_conf_merge_msec_value(conf->playlen, prev->playlen, 30000); ngx_conf_merge_str_value(conf->path, prev->path, ""); ngx_conf_merge_value(conf->continuous, prev->continuous, 0); - conf->ctx = ngx_pcalloc(cf->pool, - sizeof(ngx_rtmp_hls_ctx_t *) * conf->nbuckets); + + conf->ctx = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_hls_ctx_t *) * + conf->nbuckets); if (conf->ctx == NULL) { return NGX_CONF_ERROR; } + if (conf->fraglen) { conf->nfrags = conf->playlen / conf->fraglen; } @@ -1283,10 +1021,9 @@ ngx_rtmp_hls_merge_app_conf(ngx_conf_t *cf, void *parent, void *child) static ngx_int_t ngx_rtmp_hls_postconfiguration(ngx_conf_t *cf) { - ngx_rtmp_core_main_conf_t *cmcf; - ngx_rtmp_handler_pt *h; + ngx_rtmp_core_main_conf_t *cmcf; + ngx_rtmp_handler_pt *h; - /* av handler */ cmcf = ngx_rtmp_conf_get_module_main_conf(cf, ngx_rtmp_core_module); h = ngx_array_push(&cmcf->events[NGX_RTMP_MSG_VIDEO]); @@ -1295,18 +1032,11 @@ ngx_rtmp_hls_postconfiguration(ngx_conf_t *cf) h = ngx_array_push(&cmcf->events[NGX_RTMP_MSG_AUDIO]); *h = ngx_rtmp_hls_audio; - /* chain handlers */ next_publish = ngx_rtmp_publish; ngx_rtmp_publish = ngx_rtmp_hls_publish; next_delete_stream = ngx_rtmp_delete_stream; ngx_rtmp_delete_stream = ngx_rtmp_hls_delete_stream; - /* register all ffmpeg stuff */ - av_register_all(); - ngx_rtmp_hls_log = &cf->cycle->new_log; - av_log_set_callback(ngx_rtmp_hls_av_log_callback); - return NGX_OK; } - diff --git a/hls/ngx_rtmp_mpegts.c b/hls/ngx_rtmp_mpegts.c new file mode 100644 index 0000000..fe0afb4 --- /dev/null +++ b/hls/ngx_rtmp_mpegts.c @@ -0,0 +1,237 @@ +/* + * Copyright (c) 2013 Roman Arutyunyan + */ + + +#include "ngx_rtmp_mpegts.h" + + +static u_char ngx_rtmp_mpegts_header[] = { + + /* TS */ + 0x47, 0x40, 0x00, 0x10, 0x00, + /* PSI */ + 0x00, 0xb0, 0x0d, 0x00, 0x01, 0xc1, 0x00, 0x00, + /* PAT */ + 0x00, 0x01, 0xf0, 0x01, + /* CRC */ + 0x2e, 0x70, 0x19, 0x05, + /* stuffing 167 bytes */ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + + /* TS */ + 0x47, 0x50, 0x01, 0x10, 0x00, + /* PSI */ + 0x02, 0xb0, 0x17, 0x00, 0x01, 0xc1, 0x00, 0x00, + /* PMT */ + 0xe1, 0x00, + 0xf0, 0x00, + 0x1b, 0xe1, 0x00, 0xf0, 0x00, /* h264 */ + /*0x0f, 0xe1, 0x01, 0xf0, 0x00,*/ /* aac */ + 0x03, 0xe1, 0x01, 0xf0, 0x00, /* mp3 */ + /* CRC */ +/* 0x2f, 0x44, 0xb9, 0x9b,*/ /* crc for aac */ + 0x4e, 0x59, 0x3d, 0x1e, /* crc for mp3 */ + /* stuffing 157 bytes */ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff +}; + + +/* 700 ms PCR delay */ +#define NGX_RTMP_HLS_DELAY 63000 + + +ngx_int_t +ngx_rtmp_mpegts_write_header(ngx_file_t *file) +{ + ssize_t rc; + + rc = ngx_write_file(file, ngx_rtmp_mpegts_header, + sizeof(ngx_rtmp_mpegts_header), 0); + + return rc > 0 ? NGX_OK : rc; +} + + +static u_char * +ngx_rtmp_mpegts_write_pcr(u_char *p, uint64_t pcr) +{ + *p++ = pcr >> 25; + *p++ = pcr >> 17; + *p++ = pcr >> 9; + *p++ = pcr >> 1; + *p++ = pcr << 7 | 0x7e; + *p++ = 0; + + return p; +} + + +static u_char * +ngx_rtmp_mpegts_write_pts(u_char *p, ngx_uint_t fb, uint64_t pts) +{ + ngx_uint_t val; + + val = fb << 4 | (((pts >> 30) & 0x07) << 1) | 1; + *p++ = val; + + val = (((pts >> 15) & 0x7fff) << 1) | 1; + *p++ = val >> 8; + *p++ = val; + + val = (((pts) & 0x7fff) << 1) | 1; + *p++ = val >> 8; + *p++ = val; + + return p; +} + + +ngx_int_t +ngx_rtmp_mpegts_write_frame(ngx_file_t *file, ngx_rtmp_mpegts_frame_t *f, + ngx_buf_t *b) +{ + ngx_uint_t pes_size, header_size, body_size, in_size, stuff_size, flags; + u_char packet[188], *p; + ngx_int_t first, rc; + + ngx_log_debug6(NGX_LOG_DEBUG_HTTP, file->log, 0, + "mpegts: pid=%ui, sid=%ui, pts=%uL, " + "dts=%uL, key=%ui, size=%ui", + f->pid, f->sid, f->pts, f->dts, + (ngx_uint_t) f->key, (size_t) (b->last - b->pos)); + + first = 1; + + while (b->pos < b->last) { + p = packet; + + f->cc++; + + *p++ = 0x47; + *p++ = (f->pid >> 8); + + if (first) { + p[-1] |= 0x40; + } + + *p++ = f->pid; + *p++ = 0x10 | (f->cc & 0x0f); /* payload */ + + if (first) { + + if (f->key) { + packet[3] |= 0x20; /* adaptation */ + + *p++ = 7; /* size */ + *p++ = 0x50; /* random access + PCR */ + + p = ngx_rtmp_mpegts_write_pcr(p, f->dts - NGX_RTMP_HLS_DELAY); + } + + /* PES header */ + + *p++ = 0x00; + *p++ = 0x00; + *p++ = 0x01; + *p++ = f->sid; + + header_size = 5; + flags = 0x80; /* PTS */ + + if (f->dts != f->pts) { + header_size += 5; + flags |= 0x40; /* DTS */ + } + + pes_size = (b->last - b->pos) + header_size + 3; + + *p++ = (pes_size >> 8); + *p++ = pes_size; + *p++ = 0x80; /* H222 */ + *p++ = flags; + *p++ = header_size; + + p = ngx_rtmp_mpegts_write_pts(p, flags >> 6, f->pts + + NGX_RTMP_HLS_DELAY); + + if (f->dts != f->pts) { + p = ngx_rtmp_mpegts_write_pts(p, 1, f->dts + + NGX_RTMP_HLS_DELAY); + } + + first = 0; + } + + body_size = (ngx_uint_t) (packet + sizeof(packet) - p); + in_size = (ngx_uint_t) (b->last - b->pos); + + if (body_size <= in_size) { + ngx_memcpy(p, b->pos, body_size); + b->pos += body_size; + + } else { + stuff_size = (body_size - in_size); + + if (packet[3] & 0x20) { + packet[4] += stuff_size; + ngx_memset(p, 0xff, stuff_size); + p += stuff_size; + + } else { + packet[3] |= 0x20; + if (stuff_size == 1) { + p = ngx_movemem(&packet[5], &packet[4], p - &packet[4]); + packet[4] = 0; + } else { + p = ngx_movemem(&packet[6], &packet[4], p - &packet[4]); + packet[4] = stuff_size - 1; + packet[5] = 0; + ngx_memset(p, 0xff, stuff_size - 2); + p += (stuff_size - 2); + } + } + + ngx_memcpy(p, b->pos, in_size); + b->pos = b->last; + } + + rc = ngx_write_file(file, packet, sizeof(packet), file->offset); + if (rc < 0) { + return rc; + } + } + + return NGX_OK; +} diff --git a/hls/ngx_rtmp_mpegts.h b/hls/ngx_rtmp_mpegts.h new file mode 100644 index 0000000..011ba62 --- /dev/null +++ b/hls/ngx_rtmp_mpegts.h @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2013 Roman Arutyunyan + */ + + +#ifndef _NGX_RTMP_MPEGTS_H_INCLUDED_ +#define _NGX_RTMP_MPEGTS_H_INCLUDED_ + + +#include + + +typedef struct { + uint64_t pts; + uint64_t dts; + ngx_uint_t pid; + ngx_uint_t sid; + ngx_uint_t cc; + unsigned key:1; +} ngx_rtmp_mpegts_frame_t; + + +ngx_int_t ngx_rtmp_mpegts_write_header(ngx_file_t *file); +ngx_int_t ngx_rtmp_mpegts_write_frame(ngx_file_t *file, + ngx_rtmp_mpegts_frame_t *f, ngx_buf_t *b); + + +#endif /* _NGX_RTMP_MPEGTS_H_INCLUDED_ */