update 2026-10-29 18:46:39

This commit is contained in:
action committed 2026-10-29 18:46:39 +08:00
1 parent e2f86e09f4
commit 95f76f26d3
5034 files changed
+2043234

No files matched your search

@@ -0,0 +1,22 @@
Copyright (C) 2017 Roman Arutyunyan
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,256 @@
*************************
NGINX MPEG-TS Live Module
*************************
.. contents::
Features
========
- receives MPEG-TS over HTTP
- produces and manages live HLS_
- produces and manages live MPEG-DASH_
Compatibility
=============
- `nginx <http://nginx.org>`_ version >= 1.11.5
Build
=====
Building nginx with the module:
.. code-block:: bash
# static module
$ ./configure --add-module=/path/to/nginx-ts-module
# dynamic module
$ ./configure --add-dynamic-module=/path/to/nginx-ts-module
Directives
==========
ts
--
========== ========
*Syntax:* ``ts``
*Context:* location
========== ========
Sets up a live MPEG-TS handler for the location.
This directive is **required** for HLS or MPEG-DASH generation.
The last URI component is used as a stream name.
For example, if the URI is ``/foo/bar/baz``, the stream name is ``baz``.
A simple way to stream MPEG-TS over HTTP is by running ``ffmpeg``:
.. code-block:: bash
$ ffmpeg ... -f mpegts http://127.0.0.1:8000/foo
By default, HTTP request body size is limited in nginx.
To enable live streaming without size limitation, use the directive
``client_max_body_size 0``.
ts_hls
------
========== ========
*Syntax:* ``ts_hls path=PATH [segment=MIN[:MAX]] [segments=NUMBER] [max_size=SIZE] [noclean]``
*Context:* location
========== ========
Enables generating live HLS in the location.
The ``PATH`` parameter specifies a directory where HLS playlist and segment
files will be created.
The directory is created if missing.
For every publshed stream a subdirectory with the stream name is created under
the ``PATH`` directory.
The HLS playlist file created in the stream subdirectory is named
``index.m3u8``.
A path handler is installed to watch files in the directory.
The old files in the directory are automatically deleted once they get old
enough and are not supposed to be accessed by clients anymore.
It is not allowed to reuse the path in other ``ts_hls`` or ``ts_dash``
directives.
The ``segment`` parameter specifies minimum and maximum segment durations.
If the stream has video, segments are started at video key frames.
If a key frame does not appear within ``MAX`` duration, the segment is
truncated.
The default value for minimum segment duration is 5 seconds.
If unspecified, maximum segment duration is set to be twice as much as the
minimum.
The ``segments`` parameter specifies the maximum number of segments in a
playlist.
As new segments are added to the playlist, the oldest segments are removed from
it.
The ``max_size`` parameter specifies the maximum size of a segment.
A segment is truncated once it reaches this size.
The ``noclean`` parameter indicates that the old files (segments and the
playlist) should not be automatically deleted from disk.
Example::
location / {
ts;
ts_hls path=/var/hls segment=10s;
}
ts_dash
-------
========== ========
*Syntax:* ``ts_dash path=PATH [segment=MIN[:MAX]] [segments=NUMBER] [max_size=SIZE] [noclean]``
*Context:* location
========== ========
Enables generating live MPEG-DASH in the location.
The ``PATH`` parameter specifies a directory where MPEG-DASH manifest and
segment files will be created.
The directory is created if missing.
For every publshed stream a subdirectory with the stream name is created under
the ``PATH`` directory.
The MPEG-DASH manifest file created in the stream subdirectory is named
``index.mpd``.
A path handler is installed to watch files in the directory.
The old files in the directory are automatically deleted once they get old
enough and are not supposed to be accessed by clients anymore.
It is not allowed to reuse the path in other ``ts_hls`` or ``ts_dash``
directives.
The ``segment`` parameter specifies minimum and maximum segment durations.
If the stream has video, segments are started at video key frames.
If a key frame does not appear within ``MAX`` duration, the segment is
truncated.
The default value for minimum segment duration is 5 seconds.
If unspecified, maximum segment duration is set to be twice as much as the
minimum.
When setting an explicit value for the ``MAX`` parameter, the following
note should be taken into account.
If the next segment is shorter than the previous one by a factor more that
two, dash.js_ can end up in a busy cycle requesting the second segment over
and over again.
The ``segments`` parameter specifies the maximum number of segments in a
manifest.
As new segments are added to the manifest, the oldest segments are removed from
it.
The ``max_size`` parameter specifies the maximum size of a segment.
A segment is truncated once it reaches this size.
The ``noclean`` parameter indicates that the old files (segments and the
manifest) should not be automatically deleted from disk.
Example::
location / {
ts;
ts_dash path=/var/hls segment=10s;
}
Example
=======
nginx.conf::
# nginx.conf
events {
}
http {
server {
listen 8000;
location / {
root html;
}
location /publish/ {
ts;
ts_hls path=/var/media/hls segment=10s;
ts_dash path=/var/media/dash segment=10s;
client_max_body_size 0;
}
location /play/ {
types {
application/x-mpegURL m3u8;
application/dash+xml mpd;
video/MP2T ts;
video/mp4 mp4;
}
alias /var/media/;
}
}
}
HLS in HTML:
.. code-block:: html
<body>
<video width="640" height="480" controls autoplay
src="http://127.0.0.1:8000/play/hls/sintel/index.m3u8">
</video>
</body>
MPEG-DASH in HTML using the dash.js_ player:
.. code-block:: html
<script src="http://cdn.dashjs.org/latest/dash.all.min.js"></script>
<body>
<video data-dashjs-player
width="640" height="480" controls autoplay
src="http://127.0.0.1:8000/play/dash/sintel/index.mpd">
</video>
</body>
Broadcasting a single-bitrate mp4 file:
.. code-block:: bash
$ ffmpeg -re -i ~/Movies/sintel.mp4 -bsf:v h264_mp4toannexb
-c copy -f mpegts http://127.0.0.1:8000/publish/sintel
Broadcasting an mp4 file in multiple bitrates.
For proper HLS generation streams should be grouped into MPEG-TS programs with
the ``-program`` option of ``ffmpeg``:
.. code-block:: bash
$ ffmpeg -re -i ~/Movies/sintel.mp4 -bsf:v h264_mp4toannexb
-map 0:0 -map 0:1 -map 0:0 -map 0:1
-c:v:0 copy
-c:a:0 copy
-c:v:1 libx264 -b:v:1 100k
-c:a:1 libfaac -ac:a:1 1 -b:a:1 32k
-program "st=0:st=1" -program "st=2:st=3"
-f mpegts http://127.0.0.1:8000/publish/sintel
.. _HLS: https://tools.ietf.org/html/draft-pantos-http-live-streaming-23
.. _MPEG-DASH: https://en.wikipedia.org/wiki/Dynamic_Adaptive_Streaming_over_HTTP
.. _dash.js: https://github.com/Dash-Industry-Forum/dash.js
@@ -0,0 +1,18 @@
ngx_module_type=HTTP
ngx_addon_name=ngx_http_ts_module
ngx_module_name=ngx_http_ts_module
ngx_module_incs=
ngx_module_deps="$ngx_addon_dir/src/ngx_ts_stream.h \
$ngx_addon_dir/src/ngx_ts_hls.h \
$ngx_addon_dir/src/ngx_ts_dash.h \
$ngx_addon_dir/src/ngx_ts_avc.h \
$ngx_addon_dir/src/ngx_ts_aac.h"
ngx_module_srcs="$ngx_addon_dir/src/ngx_http_ts_module.c \
$ngx_addon_dir/src/ngx_ts_stream.c \
$ngx_addon_dir/src/ngx_ts_hls.c \
$ngx_addon_dir/src/ngx_ts_dash.c \
$ngx_addon_dir/src/ngx_ts_dash_mp4.c \
$ngx_addon_dir/src/ngx_ts_avc.c \
$ngx_addon_dir/src/ngx_ts_aac.c"
. auto/module
@@ -0,0 +1,263 @@
/*
* Copyright (C) Roman Arutyunyan
*/
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
#include "ngx_ts_stream.h"
#include "ngx_ts_hls.h"
#include "ngx_ts_dash.h"
typedef struct {
ngx_ts_hls_conf_t *hls;
ngx_ts_dash_conf_t *dash;
} ngx_http_ts_loc_conf_t;
typedef struct {
ngx_ts_stream_t *ts;
} ngx_http_ts_ctx_t;
static ngx_int_t ngx_http_ts_handler(ngx_http_request_t *r);
static void ngx_http_ts_init(ngx_http_request_t *r);
static void ngx_http_ts_read_event_handler(ngx_http_request_t *r);
static char *ngx_http_ts(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
static void *ngx_http_ts_create_conf(ngx_conf_t *cf);
static char *ngx_http_ts_merge_conf(ngx_conf_t *cf, void *parent, void *child);
static ngx_command_t ngx_http_ts_commands[] = {
{ ngx_string("ts"),
NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS,
ngx_http_ts,
0,
0,
NULL },
{ ngx_string("ts_hls"),
NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
ngx_ts_hls_set_slot,
NGX_HTTP_LOC_CONF_OFFSET,
offsetof(ngx_http_ts_loc_conf_t, hls),
NULL },
{ ngx_string("ts_dash"),
NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
ngx_ts_dash_set_slot,
NGX_HTTP_LOC_CONF_OFFSET,
offsetof(ngx_http_ts_loc_conf_t, dash),
NULL },
ngx_null_command
};
static ngx_http_module_t ngx_http_ts_module_ctx = {
NULL, /* preconfiguration */
NULL, /* postconfiguration */
NULL, /* create main configuration */
NULL, /* init main configuration */
NULL, /* create server configuration */
NULL, /* merge server configuration */
ngx_http_ts_create_conf, /* create location configuration */
ngx_http_ts_merge_conf /* merge location configuration */
};
ngx_module_t ngx_http_ts_module = {
NGX_MODULE_V1,
&ngx_http_ts_module_ctx, /* module context */
ngx_http_ts_commands, /* module directives */
NGX_HTTP_MODULE, /* module type */
NULL, /* init master */
NULL, /* init module */
NULL, /* init process */
NULL, /* init thread */
NULL, /* exit thread */
NULL, /* exit process */
NULL, /* exit master */
NGX_MODULE_V1_PADDING
};
static ngx_int_t
ngx_http_ts_handler(ngx_http_request_t *r)
{
ngx_int_t rc;
ngx_str_t name;
ngx_uint_t n;
ngx_http_ts_ctx_t *ctx;
ngx_http_ts_loc_conf_t *tlcf;
ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_ts_ctx_t));
if (ctx == NULL) {
return NGX_ERROR;
}
ctx->ts = ngx_pcalloc(r->pool, sizeof(ngx_ts_stream_t));
if (ctx->ts == NULL) {
return NGX_ERROR;
}
ctx->ts->pool = r->pool;
ctx->ts->log = r->connection->log;
for (n = 0; n < r->uri.len; n++) {
if (r->uri.data[r->uri.len - 1 - n] == '/') {
break;
}
}
name.data = &r->uri.data[r->uri.len - n];
name.len = n;
/* XXX detect streams with the same name, add shared zone */
tlcf = ngx_http_get_module_loc_conf(r, ngx_http_ts_module);
if (tlcf->hls) {
if (ngx_ts_hls_create(tlcf->hls, ctx->ts, &name) == NULL) {
return NGX_ERROR;
}
}
if (tlcf->dash) {
if (ngx_ts_dash_create(tlcf->dash, ctx->ts, &name) == NULL) {
return NGX_ERROR;
}
}
ngx_http_set_ctx(r, ctx, ngx_http_ts_module);
r->request_body_no_buffering = 1;
rc = ngx_http_read_client_request_body(r, ngx_http_ts_init);
if (rc >= NGX_HTTP_SPECIAL_RESPONSE) {
return rc;
}
return NGX_DONE;
}
static void
ngx_http_ts_init(ngx_http_request_t *r)
{
ngx_http_ts_ctx_t *ctx;
ngx_http_request_body_t *rb;
rb = r->request_body;
if (rb == NULL) {
ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
return;
}
ctx = ngx_http_get_module_ctx(r, ngx_http_ts_module);
if (ngx_ts_read(ctx->ts, rb->bufs) != NGX_OK) {
ngx_http_finalize_request(r, NGX_ERROR);
return;
}
if (r->reading_body) {
r->read_event_handler = ngx_http_ts_read_event_handler;
}
}
static void
ngx_http_ts_read_event_handler(ngx_http_request_t *r)
{
ngx_int_t rc;
ngx_http_ts_ctx_t *ctx;
ngx_http_request_body_t *rb;
if (ngx_exiting || ngx_terminate) {
ngx_http_finalize_request(r, NGX_HTTP_CLOSE);
return;
}
ctx = ngx_http_get_module_ctx(r, ngx_http_ts_module);
rb = r->request_body;
for ( ;; ) {
rc = ngx_http_read_unbuffered_request_body(r);
if (rc >= NGX_HTTP_SPECIAL_RESPONSE) {
ngx_http_finalize_request(r, rc);
return;
}
if (rb->bufs == NULL) {
return;
}
if (ngx_ts_read(ctx->ts, rb->bufs) != NGX_OK) {
ngx_http_finalize_request(r, NGX_ERROR);
return;
}
if (rc == NGX_OK) {
ngx_http_finalize_request(r, NGX_HTTP_NO_CONTENT);
return;
}
rb->bufs = NULL;
}
}
static char *
ngx_http_ts(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_ts_handler;
return NGX_CONF_OK;
}
static void *
ngx_http_ts_create_conf(ngx_conf_t *cf)
{
ngx_http_ts_loc_conf_t *conf;
conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_ts_loc_conf_t));
if (conf == NULL) {
return NULL;
}
conf->hls = NGX_CONF_UNSET_PTR;
conf->dash = NGX_CONF_UNSET_PTR;
return conf;
}
static char *
ngx_http_ts_merge_conf(ngx_conf_t *cf, void *parent, void *child)
{
ngx_http_ts_loc_conf_t *prev = parent;
ngx_http_ts_loc_conf_t *conf = child;
ngx_conf_merge_ptr_value(conf->hls, prev->hls, NULL);
ngx_conf_merge_ptr_value(conf->dash, prev->dash, NULL);
return NGX_CONF_OK;
}
@@ -0,0 +1,243 @@
/*
* Copyright (C) Roman Arutyunyan
*/
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_stream.h>
#include "ngx_ts_stream.h"
#include "ngx_ts_hls.h"
#include "ngx_ts_dash.h"
typedef struct {
ngx_ts_hls_conf_t *hls;
ngx_ts_dash_conf_t *dash;
} ngx_stream_ts_srv_conf_t;
typedef struct {
ngx_ts_stream_t *ts;
} ngx_stream_ts_ctx_t;
static void ngx_stream_ts_handler(ngx_stream_session_t *s);
static char *ngx_stream_ts(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
static void *ngx_stream_ts_create_conf(ngx_conf_t *cf);
static char *ngx_stream_ts_merge_conf(ngx_conf_t *cf, void *parent,
void *child);
static ngx_command_t ngx_stream_ts_commands[] = {
{ ngx_string("ts"),
NGX_STREAM_SRV_CONF|NGX_CONF_NOARGS,
ngx_stream_ts,
0,
0,
NULL },
{ ngx_string("ts_hls"),
NGX_STREAM_SRV_CONF|NGX_CONF_1MORE,
ngx_ts_hls_set_slot,
NGX_STREAM_SRV_CONF_OFFSET,
offsetof(ngx_stream_ts_srv_conf_t, hls),
NULL },
{ ngx_string("ts_dash"),
NGX_STREAM_SRV_CONF|NGX_CONF_1MORE,
ngx_ts_dash_set_slot,
NGX_STREAM_SRV_CONF_OFFSET,
offsetof(ngx_stream_ts_srv_conf_t, dash),
NULL },
ngx_null_command
};
static ngx_stream_module_t ngx_stream_ts_module_ctx = {
NULL, /* preconfiguration */
NULL, /* postconfiguration */
NULL, /* create main configuration */
NULL, /* init main configuration */
ngx_stream_ts_create_conf, /* create server configuration */
ngx_stream_ts_merge_conf /* merge server configuration */
};
ngx_module_t ngx_stream_ts_module = {
NGX_MODULE_V1,
&ngx_stream_ts_module_ctx, /* module context */
ngx_stream_ts_commands, /* module directives */
NGX_STREAM_MODULE, /* module type */
NULL, /* init master */
NULL, /* init module */
NULL, /* init process */
NULL, /* init thread */
NULL, /* exit thread */
NULL, /* exit process */
NULL, /* exit master */
NGX_MODULE_V1_PADDING
};
static void
ngx_stream_ts_handler(ngx_stream_session_t *s);
{
ngx_str_t name;
ngx_connection_t *c;
ngx_stream_ts_ctx_t *ctx;
ngx_stream_ts_srv_conf_t *tscf;
c = s->connection;
ctx = ngx_pcalloc(c->pool, sizeof(ngx_stream_ts_ctx_t));
if (ctx == NULL) {
ngx_stream_finalize_session(s, NGX_STREAM_INTERNAL_SERVER_ERROR);
return;
}
ctx->ts = ngx_pcalloc(c->pool, sizeof(ngx_ts_stream_t));
if (ctx->ts == NULL) {
ngx_stream_finalize_session(s, NGX_STREAM_INTERNAL_SERVER_ERROR);
return;
}
ctx->ts->pool = c->pool;
ctx->ts->log = c->log;
/* XXX */
ngx_ste_set(&name, "foo");
/* XXX detect streams with the same name, add shared zone */
tscf = ngx_stream_get_module_srv_conf(s, ngx_stream_ts_module);
if (tscf->hls) {
if (ngx_ts_hls_create(tscf->hls, ctx->ts, &name) == NULL) {
ngx_stream_finalize_session(s, NGX_STREAM_INTERNAL_SERVER_ERROR);
return;
}
}
if (tscf->dash) {
if (ngx_ts_dash_create(tscf->dash, ctx->ts, &name) == NULL) {
ngx_stream_finalize_session(s, NGX_STREAM_INTERNAL_SERVER_ERROR);
return;
}
}
ngx_stream_set_ctx(r, ctx, ngx_stream_ts_module);
c->write->handler = ngx_stream_ts_read_handler;
ngx_stream_ts_read_handler(c->write);
}
static voi
ngx_stream_ts_read_handler(ngx_event_t *rev)
{
ssize_t n;
ngx_buf_t b;
ngx_chain_t in;
ngx_connection_t *c;
ngx_stream_ts_ctx_t *ctx;
ngx_stream_session_t *s;
u_char buf[4096];
c = rev->data;
s = c->data;
if (ngx_exiting || ngx_terminate) {
ngx_stream_finalize_request(s, NGX_STREAM_OK);
return;
}
if (rev->timedout) {
ngx_stream_finalize_session(s, NGX_STREAM_OK);
return;
}
ctx = ngx_stream_get_module_ctx(s, ngx_stream_ts_module);
in.buf = &b;
in.next = NULL;
ngx_memzero(&b, sizeof(ngx_buf_t));
while (rev->ready) {
n = c->recv(c, buf, sizeof(buf));
if (n == NGX_ERROR || n == 0) {
ngx_stream_finalize_session(s, NGX_STREAM_OK);
return;
}
if (n == NGX_AGAIN) {
break;
}
b.pos = buf;
b.last = buf + n;
if (ngx_ts_read(ctx->ts, &in) != NGX_OK) {
ngx_stream_finalize_session(s, NGX_STREAM_INTERNAL_SERVER_ERROR);
return;
}
}
ngx_add_timer(rev, 5000); /* XXX */
if (ngx_handle_read_event(rev, 0) != NGX_OK) {
ngx_stream_finalize_session(s, NGX_STREAM_INTERNAL_SERVER_ERROR);
return;
}
}
static char *
ngx_stream_ts(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
ngx_stream_core_srv_conf_t *cscf;
cscf = ngx_stream_conf_get_module_srv_conf(cf, ngx_stream_core_module);
cscf->handler = ngx_stream_ts_handler;
return NGX_CONF_OK;
}
static void *
ngx_stream_ts_create_conf(ngx_conf_t *cf)
{
ngx_stream_ts_srv_conf_t *conf;
conf = ngx_pcalloc(cf->pool, sizeof(ngx_stream_ts_srv_conf_t));
if (conf == NULL) {
return NULL;
}
conf->hls = NGX_CONF_UNSET_PTR;
conf->dash = NGX_CONF_UNSET_PTR;
return conf;
}
static char *
ngx_stream_ts_merge_conf(ngx_conf_t *cf, void *parent, void *child)
{
ngx_stream_ts_srv_conf_t *prev = parent;
ngx_stream_ts_srv_conf_t *conf = child;
ngx_conf_merge_ptr_value(conf->hls, prev->hls, NULL);
ngx_conf_merge_ptr_value(conf->dash, prev->dash, NULL);
return NGX_CONF_OK;
}
@@ -0,0 +1,69 @@
/*
* Copyright (C) Roman Arutyunyan
*/
#include <ngx_config.h>
#include <ngx_core.h>
#include "ngx_ts_aac.h"
ngx_ts_aac_params_t *
ngx_ts_aac_decode_params(ngx_ts_stream_t *ts, u_char *adts, size_t adts_len)
{
/*
* XXX
* https://wiki.multimedia.cx/index.php/ADTS
*/
ngx_ts_aac_params_t *aac;
static ngx_uint_t freq[] = {
96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050,
16000, 12000, 11025, 8000, 7350, 0, 0, 0
};
if (adts_len < 7) {
return NULL;
}
aac = ngx_pcalloc(ts->pool, sizeof(ngx_ts_aac_params_t));
if (aac == NULL) {
return NULL;
}
/* MPEG Version: 0 for MPEG-4, 1 for MPEG-2 */
aac->version = (adts[1] & 0x08) ? 1 : 0;
/*
* MPEG-4 Audio Object Types:
* 1: AAC Main
* 2: AAC LC (Low Complexity)
* 3: AAC SSR (Scalable Sample Rate)
* 4: AAC LTP (Long Term Prediction)
*/
aac->profile = (adts[2] >> 6) + 1;
aac->freq_index = (adts[2] >> 2) & 0x0f;
aac->freq = freq[aac->freq_index];
if (aac->freq == 0) {
goto failed;
}
aac->chan = ((adts[2] & 0x01) << 2) | (adts[3] >> 6);
ngx_log_debug4(NGX_LOG_DEBUG_CORE, ts->log, 0,
"ts aac version:%ui, profile:%ui, freq:%ui, chan:%ui",
aac->version, aac->profile, aac->freq, aac->chan);
return aac;
failed:
ngx_log_error(NGX_LOG_ERR, ts->log, 0, "failed to parse AAC parameters");
return NULL;
}
@@ -0,0 +1,30 @@
/*
* Copyright (C) Roman Arutyunyan
*/
#include <ngx_config.h>
#include <ngx_core.h>
#include "ngx_ts_stream.h"
#ifndef _NGX_TS_AAC_H_INCLUDED_
#define _NGX_TS_AAC_H_INCLUDED_
typedef struct {
ngx_uint_t version;
ngx_uint_t profile;
ngx_uint_t freq_index;
ngx_uint_t freq;
ngx_uint_t chan;
} ngx_ts_aac_params_t;
ngx_ts_aac_params_t *ngx_ts_aac_decode_params(ngx_ts_stream_t *ts, u_char *adts,
size_t adts_len);
#endif /* _NGX_TS_AAC_H_INCLUDED_ */
@@ -0,0 +1,298 @@
/*
* Copyright (C) Roman Arutyunyan
*/
#include <ngx_config.h>
#include <ngx_core.h>
#include "ngx_ts_avc.h"
typedef struct {
u_char *pos;
u_char *last;
ngx_uint_t shift;
ngx_uint_t err; /* unsigned err:1; */
const char *name;
ngx_log_t *log;
} ngx_ts_avc_reader_t;
static void ngx_ts_avc_init_reader(ngx_ts_avc_reader_t *br, u_char *buf,
size_t len, ngx_log_t *log);
static uint64_t ngx_ts_avc_read(ngx_ts_avc_reader_t *br, ngx_uint_t bits);
static uint64_t ngx_ts_avc_read_golomb(ngx_ts_avc_reader_t *br);
static void
ngx_ts_avc_init_reader(ngx_ts_avc_reader_t *br, u_char *buf, size_t len,
ngx_log_t *log)
{
ngx_memzero(br, sizeof(ngx_ts_avc_reader_t));
br->pos = buf;
br->last = buf + len;
br->log = log;
}
static uint64_t
ngx_ts_avc_read(ngx_ts_avc_reader_t *br, ngx_uint_t bits)
{
uint64_t v;
ngx_uint_t k, n;
if (br->err) {
return 0;
}
v = 0;
n = bits;
while (n) {
if (br->pos == br->last) {
br->err = 1;
break;
}
k = ngx_min(8 - br->shift, n);
/*
* [-------------|||||||||--------------]
* br->shift k
*/
v = (v << k) | (*br->pos & (0xff >> br->shift)) >> (8 - br->shift - k);
n -= k;
br->shift += k;
if (br->shift == 8) {
br->shift = 0;
br->pos++;
}
}
ngx_log_debug3(NGX_LOG_DEBUG_CORE, br->log, 0,
"ts avc %s[%uL]:%uL", br->name, bits, v);
return v;
}
static uint64_t
ngx_ts_avc_read_golomb(ngx_ts_avc_reader_t *br)
{
/*
* ISO/IEC 14496-10:2004(E)
* 9.1 Parsing process for Exp-Golomb codes, p. 159
*/
uint64_t v;
ngx_uint_t n;
if (br->err) {
return 0;
}
n = 0;
while (ngx_ts_avc_read(br, 1) == 0) {
if (br->err) {
return 0;
}
n++;
}
v = ((uint64_t) 1 << n) - 1 + ngx_ts_avc_read(br, n);
ngx_log_debug2(NGX_LOG_DEBUG_CORE, br->log, 0, "ts avc %s:%uL",
br->name, v);
return v;
}
ngx_ts_avc_params_t *
ngx_ts_avc_decode_params(ngx_ts_stream_t *ts, u_char *sps, size_t sps_len,
u_char *pps, size_t pps_len)
{
/*
* ISO/IEC 14496-10:2004(E)
* 7.3.2.1 Sequence parameter set RBSP syntax, p. 31
*/
ngx_uint_t type, n, i;
ngx_ts_avc_reader_t br;
ngx_ts_avc_params_t *avc;
/* ignore PPS so far */
avc = ngx_pcalloc(ts->pool, sizeof(ngx_ts_avc_params_t));
if (avc == NULL) {
return NULL;
}
ngx_ts_avc_init_reader(&br, sps, sps_len, ts->log);
br.name = "nalu_type";
type = ngx_ts_avc_read(&br, 8);
if ((type & 0x1f) != 7) {
goto failed;
}
br.name = "profile_idc";
avc->profile_idc = ngx_ts_avc_read(&br, 8);
br.name = "constraints";
avc->constraints = ngx_ts_avc_read(&br, 8);
br.name = "level_idc";
avc->level_idc = ngx_ts_avc_read(&br, 8);
br.name = "seq_parameter_set_id";
avc->sps_id = ngx_ts_avc_read_golomb(&br);
if (avc->profile_idc == 100
|| avc->profile_idc == 110
|| avc->profile_idc == 122
|| avc->profile_idc == 244
|| avc->profile_idc == 44
|| avc->profile_idc == 83
|| avc->profile_idc == 86
|| avc->profile_idc == 118
|| avc->profile_idc == 128
|| avc->profile_idc == 138
|| avc->profile_idc == 139
|| avc->profile_idc == 134)
{
br.name = "chroma_format_idc";
avc->chroma_format_idc = ngx_ts_avc_read_golomb(&br);
if (avc->chroma_format_idc == 3) {
br.name =
"residual_colour_transform_flagseparate_colour_plane_flag";
avc->residual_colour_transform_flagseparate_colour_plane_flag =
ngx_ts_avc_read(&br, 1);
}
br.name = "bit_depth_luma_minus8";
avc->bit_depth_luma = ngx_ts_avc_read_golomb(&br) + 8;
br.name = "bit_depth_chroma_minus8";
avc->bit_depth_chroma = ngx_ts_avc_read_golomb(&br) + 8;
br.name = "qpprime_y_zero_transform_bypass_flag";
avc->qpprime_y_zero_transform_bypass_flag = ngx_ts_avc_read(&br, 1);
br.name = "seq_scaling_matrix_present_flag";
avc->seq_scaling_matrix_present_flag = ngx_ts_avc_read(&br, 1);
if (avc->seq_scaling_matrix_present_flag) {
n = (avc->chroma_format_idc != 3) ? 8 : 12;
for (i = 0; i < n; i++) {
br.name = "seq_scaling_list_present_flag[i]";
if (ngx_ts_avc_read(&br, 1)) {
goto failed;
}
}
}
}
br.name = "log2_max_frame_num_minus4";
avc->max_frame_num = (1 << (ngx_ts_avc_read_golomb(&br) + 4));
br.name = "pic_order_cnt_type";
avc->pic_order_cnt_type = ngx_ts_avc_read_golomb(&br);
if (avc->pic_order_cnt_type == 0) {
br.name = "log2_max_pic_order_cnt_lsb_minus4";
avc->max_pic_order_cnt_lsb = (1 << (ngx_ts_avc_read_golomb(&br) + 4));
} else if (avc->pic_order_cnt_type == 1) {
br.name = "delta_pic_order_always_zero_flag";
avc->delta_pic_order_always_zero_flag = ngx_ts_avc_read(&br, 1);
br.name = "offset_for_non_ref_pic";
avc->offset_for_non_ref_pic = ngx_ts_avc_read_golomb(&br);
br.name = "offset_for_top_to_bottom_field";
avc->offset_for_top_to_bottom_field = ngx_ts_avc_read_golomb(&br);
br.name = "num_ref_frames_in_pic_order_cnt_cycle";
n = ngx_ts_avc_read_golomb(&br);
for (i = 0; i < n; i++) {
br.name = "offset_for_ref_frame[i]";
(void) ngx_ts_avc_read_golomb(&br);
}
}
br.name = "num_ref_frames";
avc->num_ref_frames = ngx_ts_avc_read_golomb(&br);
br.name = "gaps_in_frame_num_value_allowed_flag";
avc->gaps_in_frame_num_value_allowed_flag = ngx_ts_avc_read(&br, 1);
br.name = "pic_width_in_mbs_minus1";
avc->pic_width_in_mbs = ngx_ts_avc_read_golomb(&br) + 1;
br.name = "pic_height_in_map_units_minus1";
avc->pic_height_in_map_units = ngx_ts_avc_read_golomb(&br) + 1;
br.name = "frame_mbs_only_flag";
avc->frame_mbs_only_flag = ngx_ts_avc_read(&br, 1);
if (!avc->frame_mbs_only_flag) {
br.name = "mb_adaptive_frame_field_flag";
avc->mb_adaptive_frame_field_flag = ngx_ts_avc_read(&br, 1);
}
br.name = "direct_8x8_inference_flag";
avc->direct_8x8_inference_flag = ngx_ts_avc_read(&br, 1);
br.name = "frame_cropping_flag";
avc->frame_cropping_flag = ngx_ts_avc_read(&br, 1);
if (avc->frame_cropping_flag) {
br.name = "frame_crop_left_offset";
avc->frame_crop_left_offset = ngx_ts_avc_read_golomb(&br);
br.name = "frame_crop_right_offset";
avc->frame_crop_right_offset = ngx_ts_avc_read_golomb(&br);
br.name = "frame_crop_top_offset";
avc->frame_crop_top_offset = ngx_ts_avc_read_golomb(&br);
br.name = "frame_crop_bottom_offset";
avc->frame_crop_bottom_offset = ngx_ts_avc_read_golomb(&br);
}
if (br.err) {
goto failed;
}
avc->width =
avc->pic_width_in_mbs * 16
- (avc->frame_crop_left_offset + avc->frame_crop_right_offset) * 2;
avc->height =
(2 - avc->frame_mbs_only_flag) * avc->pic_height_in_map_units * 16
- (avc->frame_crop_top_offset + avc->frame_crop_bottom_offset) * 2;
ngx_log_debug2(NGX_LOG_DEBUG_CORE, ts->log, 0,
"ts avc width:%ui, height:%ui", avc->width, avc->height);
return avc;
failed:
ngx_log_error(NGX_LOG_ERR, ts->log, 0,
"failed to parse AVC parameters");
return NULL;
}
@@ -0,0 +1,57 @@
/*
* Copyright (C) Roman Arutyunyan
*/
#include <ngx_config.h>
#include <ngx_core.h>
#include "ngx_ts_stream.h"
#ifndef _NGX_TS_AVC_H_INCLUDED_
#define _NGX_TS_AVC_H_INCLUDED_
typedef struct {
ngx_uint_t profile_idc;
ngx_uint_t constraints;
ngx_uint_t level_idc;
ngx_uint_t sps_id;
ngx_uint_t chroma_format_idc;
ngx_uint_t bit_depth_luma;
ngx_uint_t bit_depth_chroma;
ngx_uint_t max_frame_num;
ngx_uint_t pic_order_cnt_type;
ngx_uint_t max_pic_order_cnt_lsb;
ngx_int_t offset_for_non_ref_pic;
ngx_int_t offset_for_top_to_bottom_field;
ngx_uint_t num_ref_frames_in_pic_order_cnt_cycle;
ngx_uint_t num_ref_frames;
ngx_uint_t pic_width_in_mbs;
ngx_uint_t pic_height_in_map_units;
ngx_uint_t frame_crop_left_offset;
ngx_uint_t frame_crop_right_offset;
ngx_uint_t frame_crop_top_offset;
ngx_uint_t frame_crop_bottom_offset;
ngx_uint_t width;
ngx_uint_t height;
unsigned delta_pic_order_always_zero_flag:1;
unsigned gaps_in_frame_num_value_allowed_flag:1;
unsigned frame_mbs_only_flag:1;
unsigned mb_adaptive_frame_field_flag:1;
unsigned direct_8x8_inference_flag:1;
unsigned frame_cropping_flag:1;
unsigned residual_colour_transform_flagseparate_colour_plane_flag:1;
unsigned qpprime_y_zero_transform_bypass_flag:1;
unsigned seq_scaling_matrix_present_flag:1;
} ngx_ts_avc_params_t;
ngx_ts_avc_params_t *ngx_ts_avc_decode_params(ngx_ts_stream_t *ts, u_char *sps,
size_t sps_len, u_char *pps, size_t pps_len);
#endif /* _NGX_TS_AVC_H_INCLUDED_ */
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,134 @@
/*
* Copyright (C) Roman Arutyunyan
*/
#include <ngx_config.h>
#include <ngx_core.h>
#include "ngx_ts_stream.h"
#include "ngx_ts_avc.h"
#include "ngx_ts_aac.h"
#ifndef _NGX_TS_DASH_H_INCLUDED_
#define _NGX_TS_DASH_H_INCLUDED_
typedef struct {
ngx_path_t *path;
ngx_msec_t min_seg;
ngx_msec_t max_seg;
ngx_msec_t analyze;
size_t max_size;
ngx_uint_t nsegs;
} ngx_ts_dash_conf_t;
typedef struct {
uint64_t start;
uint64_t duration;
} ngx_ts_dash_segment_t;
typedef struct {
u_char *dts; /* 64-bit */
u_char *pts; /* 64-bit */
u_char *seq;
u_char *duration;
u_char *sample_duration;
u_char *nsamples;
u_char *traf;
u_char *trun;
u_char *moof;
u_char *moof_mdat;
u_char *moof_data;
u_char *mdat;
} ngx_ts_dash_subs_t;
typedef struct {
ngx_ts_es_t *es;
ngx_ts_dash_segment_t *segs;
ngx_uint_t nsegs;
ngx_uint_t seg;
uint64_t seg_pts;
uint64_t seg_dts;
uint64_t dts;
ngx_uint_t bandwidth;
ngx_uint_t bandwidth_bytes;
uint64_t bandwidth_dts;
u_char *sps;
u_char *pps;
size_t sps_len;
size_t pps_len;
ngx_ts_avc_params_t *avc;
ngx_ts_aac_params_t *aac;
ngx_str_t path;
u_char *init_path;
u_char *init_tmp_path;
ngx_chain_t *meta;
ngx_chain_t *last_meta;
ngx_chain_t *data;
ngx_chain_t *last_data;
ngx_uint_t nsamples;
ngx_uint_t nmeta;
ngx_uint_t ndata;
ngx_ts_dash_subs_t subs;
} ngx_ts_dash_rep_t;
typedef struct {
ngx_ts_dash_rep_t *reps;
ngx_uint_t nreps;
ngx_uint_t video; /* unsigned video:1; */
} ngx_ts_dash_set_t;
typedef struct {
ngx_ts_stream_t *ts;
ngx_ts_dash_conf_t *conf;
u_char *mpd_path;
u_char *mpd_tmp_path;
ngx_str_t path;
size_t playlist_len;
size_t init_seg_len;
time_t availability_start;
ngx_chain_t *free;
ngx_ts_dash_set_t *sets;
ngx_uint_t nsets;
ngx_uint_t flush; /* unsigned flush:1; */
} ngx_ts_dash_t;
ngx_ts_dash_t *ngx_ts_dash_create(ngx_ts_dash_conf_t *conf, ngx_ts_stream_t *ts,
ngx_str_t *name);
char *ngx_ts_dash_set_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
void ngx_ts_dash_write_init_segment(ngx_buf_t *b, ngx_ts_dash_rep_t *rep);
ngx_int_t ngx_ts_dash_start_segment(ngx_ts_dash_t *dash,
ngx_ts_dash_rep_t *rep);
ngx_chain_t *ngx_ts_dash_end_segment(ngx_ts_dash_t *dash,
ngx_ts_dash_rep_t *rep);
void ngx_ts_dash_free_segment(ngx_ts_dash_t *dash, ngx_ts_dash_rep_t *rep,
ngx_chain_t *out);
ngx_int_t ngx_ts_dash_append_meta(ngx_ts_dash_t *dash, ngx_ts_dash_rep_t *rep,
size_t size, uint64_t dts);
ngx_int_t ngx_ts_dash_append_data(ngx_ts_dash_t *dash, ngx_ts_dash_rep_t *rep,
u_char *data, size_t len);
#endif /* _NGX_TS_DASH_H_INCLUDED_ */
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,75 @@
/*
* Copyright (C) Roman Arutyunyan
*/
#include <ngx_config.h>
#include <ngx_core.h>
#include "ngx_ts_stream.h"
#ifndef _NGX_TS_HLS_H_INCLUDED_
#define _NGX_TS_HLS_H_INCLUDED_
typedef struct {
ngx_path_t *path;
ngx_msec_t min_seg;
ngx_msec_t max_seg;
ngx_msec_t analyze;
size_t max_size;
ngx_uint_t nsegs;
} ngx_ts_hls_conf_t;
typedef struct {
ngx_uint_t id;
uint64_t duration;
off_t size;
} ngx_ts_hls_segment_t;
typedef struct {
ngx_file_t file;
ngx_chain_t *prologue;
ngx_uint_t bandwidth;
ngx_uint_t bandwidth_bytes;
uint64_t bandwidth_dts;
ngx_ts_hls_segment_t *segs;
ngx_uint_t nsegs;
ngx_uint_t seg;
uint64_t seg_dts;
u_char *m3u8_path;
u_char *m3u8_tmp_path;
ngx_str_t path;
ngx_ts_program_t *prog;
} ngx_ts_hls_variant_t;
typedef struct {
ngx_ts_stream_t *ts;
ngx_ts_hls_conf_t *conf;
u_char *m3u8_path;
u_char *m3u8_tmp_path;
ngx_str_t path;
ngx_ts_hls_variant_t *vars;
ngx_uint_t nvars;
ngx_uint_t done; /* unsigned done:1; */
} ngx_ts_hls_t;
ngx_ts_hls_t *ngx_ts_hls_create(ngx_ts_hls_conf_t *conf, ngx_ts_stream_t *ts,
ngx_str_t *name);
char *ngx_ts_hls_set_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
#endif /* _NGX_TS_HLS_H_INCLUDED_ */
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,113 @@
/*
* Copyright (C) Roman Arutyunyan
*/
#include <ngx_config.h>
#include <ngx_core.h>
#ifndef _NGX_TS_STREAM_H_INCLUDED_
#define _NGX_TS_STREAM_H_INCLUDED_
/*
* ISO/IEC 13818-1 : 2000 (E)
* Table 2-29 – Stream type assignments, p. 48
*
* ISO/IEC 13818-1:2007/Amd.3:2009 (E)
* Table 2-34 – Stream type assignments, p. 6
*/
#define NGX_TS_VIDEO_MPEG1 0x01 /* ISO/IEC 11172-2, MPEG-1 Video */
#define NGX_TS_VIDEO_MPEG2 0x02 /* ISO/IEC 13818-2, MPEG-2 Video */
#define NGX_TS_VIDEO_MPEG4 0x10 /* ISO/IEC 14496-2, MPEG-4 Video */
#define NGX_TS_VIDEO_AVC 0x1b /* ISO/IEC 14496-10, AVC */
#define NGX_TS_AUDIO_MPEG1 0x03 /* ISO/IEC 11172-3, MPEG-1 Audio */
#define NGX_TS_AUDIO_MPEG2 0x04 /* ISO/IEC 13818-3, MPEG-2 Audio */
#define NGX_TS_AUDIO_AAC 0x0f /* ISO/IEC 13818-7, MPEG-2 AAC ADTS Audio */
typedef enum {
NGX_TS_PAT = 0,
NGX_TS_PMT,
NGX_TS_PES
} ngx_ts_event_e;
typedef struct {
u_char type;
u_char sid;
u_char cont;
uint16_t pid;
uint64_t pts;
uint64_t dts;
unsigned ptsf:1;
unsigned rand:1;
unsigned video:1;
ngx_chain_t *bufs; /* ES */
} ngx_ts_es_t;
typedef struct {
uint16_t number;
uint16_t pid;
uint16_t pcr_pid;
uint64_t pcr;
ngx_uint_t video; /* unisgned video:1; */
ngx_uint_t nes;
ngx_ts_es_t *es;
ngx_chain_t *bufs; /* PMT */
} ngx_ts_program_t;
typedef struct ngx_ts_handler_s ngx_ts_handler_t;
typedef struct {
ngx_uint_t nprogs;
ngx_ts_program_t *progs;
ngx_log_t *log;
ngx_pool_t *pool;
ngx_buf_t *buf;
ngx_chain_t *free;
ngx_chain_t *bufs; /* PAT */
ngx_ts_handler_t *handlers;
void *data;
} ngx_ts_stream_t;
typedef struct {
ngx_ts_event_e event;
ngx_ts_stream_t *ts;
ngx_ts_program_t *prog;
ngx_ts_es_t *es;
ngx_chain_t *bufs;
void *data;
} ngx_ts_handler_data_t;
typedef ngx_int_t (*ngx_ts_handler_pt)(ngx_ts_handler_data_t *hd);
struct ngx_ts_handler_s {
ngx_ts_handler_pt handler;
void *data;
ngx_ts_handler_t *next;
};
ngx_int_t ngx_ts_add_handler(ngx_ts_stream_t *ts, ngx_ts_handler_pt handler,
void *data);
ngx_int_t ngx_ts_read(ngx_ts_stream_t *ts, ngx_chain_t *in);
ngx_chain_t *ngx_ts_write_pat(ngx_ts_stream_t *ts, ngx_ts_program_t *prog);
ngx_chain_t *ngx_ts_write_pmt(ngx_ts_stream_t *ts, ngx_ts_program_t *prog);
ngx_chain_t *ngx_ts_write_pes(ngx_ts_stream_t *ts, ngx_ts_program_t *prog,
ngx_ts_es_t *es, ngx_chain_t *bufs);
void ngx_ts_free_chain(ngx_ts_stream_t *ts, ngx_chain_t **ll);
ngx_uint_t ngx_ts_dash_get_oti(u_char type);
#endif /* _NGX_TS_STREAM_H_INCLUDED_ */