Skip to content

Commit

Permalink
hls: add vod_hls_mpegts_id3_data variable
Browse files Browse the repository at this point in the history
allows setting the content of the id3 tags returned in segment requests.
for backward compatbility, when the directive is not specified, the
default data is -
`{"timestamp":$vod_segment_time,"sequenceId":"$vod_sequence_id"}`.
also added a new `$vod_segment_time` variable, that can be used with the
new `vod_hls_mpegts_id3_data` directive.
  • Loading branch information
erankor committed Oct 7, 2023
1 parent e521f55 commit edb26f7
Show file tree
Hide file tree
Showing 10 changed files with 240 additions and 71 deletions.
16 changes: 14 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1689,8 +1689,19 @@ padding is added as needed.
* **default**: `off`
* **context**: `http`, `server`, `location`

When enabled, an ID3 TEXT frame will be outputted in each TS segment, containing a JSON with the absolute segment timestamp.
The timestamp is measured in milliseconds since the epoch (unixtime x 1000), the JSON structure is: `{"timestamp":1459779115000}`
When enabled, an ID3 TEXT frame is outputted in each TS segment.
The content of the ID3 TEXT frame can be set using the directive `vod_hls_mpegts_id3_data`.

#### vod_hls_mpegts_id3_data
* **syntax**: `vod_hls_mpegts_id3_data string`
* **default**: `{"timestamp":$vod_segment_time,"sequenceId":"$vod_sequence_id"}`
* **context**: `http`, `server`, `location`

Sets the data of the ID3 TEXT frame outputted in each TS segment, when `vod_hls_mpegts_output_id3_timestamps` is set to `on`.
When the directive is not set, the ID3 frames contain by default a JSON object of the format `{"timestamp":1459779115000,"sequenceId":"{id}"}`:
- `timestamp` - an absolute time measured in milliseconds since the epoch (unixtime x 1000).
- `sequenceId` - the id field of the sequence object, as specified in the mapping JSON. The field is omitted when the sequence id is empty / not specified in the mapping JSON.
The parameter value can contain variables.

#### vod_hls_mpegts_align_pts
* **syntax**: `vod_hls_mpegts_align_pts on/off`
Expand Down Expand Up @@ -1821,6 +1832,7 @@ The module adds the following nginx variables:
`EXPIRED` - the current server time is larger than `expirationTime`
`ALLOC_FAILED` - the module failed to allocate memory
`UNEXPECTED` - a scenario that is not supposed to happen, most likely a bug in the module
* `$vod_segment_time` - for segment requests, contains the absolute timestamp of the first frame in the segment, measured in milliseconds since the epoch (unixtime x 1000).
* `$vod_segment_duration` - for segment requests, contains the duration of the segment in milliseconds
* `$vod_frames_bytes_read` - for segment requests, total number of bytes read while processing media frames

Expand Down
1 change: 1 addition & 0 deletions config
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,7 @@ VOD_SRCS="$VOD_SRCS \
$ngx_addon_dir/vod/language_code.c \
$ngx_addon_dir/vod/manifest_utils.c \
$ngx_addon_dir/vod/media_format.c \
$ngx_addon_dir/vod/media_set.c \
$ngx_addon_dir/vod/media_set_parser.c \
$ngx_addon_dir/vod/mkv/ebml.c \
$ngx_addon_dir/vod/mkv/mkv_builder.c \
Expand Down
141 changes: 130 additions & 11 deletions ngx_http_vod_hls.c
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@

#define SUPPORTED_CODECS (SUPPORTED_CODECS_MP4 | SUPPORTED_CODECS_TS)

#define ID3_TEXT_JSON_FORMAT "{\"timestamp\":%uL}%Z"
#define ID3_TEXT_JSON_SEQUENCE_ID_PREFIX_FORMAT "{\"timestamp\":%uL,\"sequenceId\":\""
#define ID3_TEXT_JSON_SEQUENCE_ID_SUFFIX "\"}"


// content types
static u_char m3u8_content_type[] = "application/vnd.apple.mpegurl";
Expand Down Expand Up @@ -266,6 +270,103 @@ ngx_http_vod_hls_init_segment_encryption(
}
#endif // NGX_HAVE_OPENSSL_EVP

static ngx_int_t
ngx_http_vod_hls_get_default_id3_data(ngx_http_vod_submodule_context_t* submodule_context, ngx_str_t* id3_data)
{
media_set_t* media_set;
vod_str_t* sequence_id;
int64_t timestamp;
u_char* p;
size_t sequence_id_escape;
size_t data_size;

media_set = &submodule_context->media_set;
sequence_id = &media_set->sequences[0].id;
if (sequence_id->len != 0)
{
sequence_id_escape = vod_escape_json(NULL, sequence_id->data, sequence_id->len);
data_size = sizeof(ID3_TEXT_JSON_SEQUENCE_ID_PREFIX_FORMAT) + VOD_INT64_LEN +
sequence_id->len + sequence_id_escape +
sizeof(ID3_TEXT_JSON_SEQUENCE_ID_SUFFIX);
}
else
{
sequence_id_escape = 0;
data_size = sizeof(ID3_TEXT_JSON_FORMAT) + VOD_INT64_LEN;
}

timestamp = media_set_get_segment_time_millis(media_set);

p = ngx_pnalloc(submodule_context->request_context.pool, data_size);
if (p == NULL)
{
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, submodule_context->request_context.log, 0,
"ngx_http_vod_hls_get_default_id3_data: ngx_pnalloc failed");
return ngx_http_vod_status_to_ngx_error(submodule_context->r, VOD_ALLOC_FAILED);
}

id3_data->data = p;

if (sequence_id->len != 0)
{
p = vod_sprintf(p, ID3_TEXT_JSON_SEQUENCE_ID_PREFIX_FORMAT, timestamp);
if (sequence_id_escape)
{
p = (u_char*)vod_escape_json(p, sequence_id->data, sequence_id->len);
}
else
{
p = vod_copy(p, sequence_id->data, sequence_id->len);
}
p = vod_copy(p, ID3_TEXT_JSON_SEQUENCE_ID_SUFFIX, sizeof(ID3_TEXT_JSON_SEQUENCE_ID_SUFFIX));

}
else
{
p = vod_sprintf(p, ID3_TEXT_JSON_FORMAT, timestamp);
}

id3_data->len = p - id3_data->data;

return NGX_OK;
}

static ngx_int_t
ngx_http_vod_hls_init_muxer_conf(ngx_http_vod_submodule_context_t* submodule_context, hls_mpegts_muxer_conf_t* conf)
{
ngx_http_vod_hls_loc_conf_t* hls_conf;

hls_conf = &submodule_context->conf->hls;

conf->interleave_frames = hls_conf->interleave_frames;
conf->align_frames = hls_conf->align_frames;
conf->align_pts = hls_conf->align_pts;

if (!hls_conf->output_id3_timestamps)
{
conf->id3_data.data = NULL;
conf->id3_data.len = 0;
return NGX_OK;
}

if (hls_conf->id3_data != NULL)
{
if (ngx_http_complex_value(
submodule_context->r,
hls_conf->id3_data,
&conf->id3_data) != NGX_OK)
{
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, submodule_context->request_context.log, 0,
"ngx_http_vod_hls_init_muxer_conf: ngx_http_complex_value failed");
return NGX_ERROR;
}

return NGX_OK;
}

return ngx_http_vod_hls_get_default_id3_data(submodule_context, &conf->id3_data);
}

static ngx_int_t
ngx_http_vod_hls_handle_master_playlist(
ngx_http_vod_submodule_context_t* submodule_context,
Expand Down Expand Up @@ -407,6 +508,7 @@ ngx_http_vod_hls_handle_iframe_playlist(
ngx_str_t* content_type)
{
ngx_http_vod_loc_conf_t* conf = submodule_context->conf;
hls_mpegts_muxer_conf_t muxer_conf;
ngx_str_t base_url = ngx_null_string;
vod_status_t rc;

Expand Down Expand Up @@ -442,10 +544,16 @@ ngx_http_vod_hls_handle_iframe_playlist(
return ngx_http_vod_status_to_ngx_error(submodule_context->r, VOD_BAD_REQUEST);
}

rc = ngx_http_vod_hls_init_muxer_conf(submodule_context, &muxer_conf);
if (rc != NGX_OK)
{
return rc;
}

rc = m3u8_builder_build_iframe_playlist(
&submodule_context->request_context,
&conf->hls.m3u8_config,
&conf->hls.mpegts_muxer_config,
&muxer_conf,
&base_url,
&submodule_context->media_set,
response);
Expand Down Expand Up @@ -500,6 +608,7 @@ ngx_http_vod_hls_init_ts_frame_processor(
ngx_str_t* content_type)
{
hls_encryption_params_t encryption_params;
hls_mpegts_muxer_conf_t muxer_conf;
hls_muxer_state_t* state;
vod_status_t rc;
bool_t reuse_output_buffers;
Expand Down Expand Up @@ -528,9 +637,15 @@ ngx_http_vod_hls_init_ts_frame_processor(
reuse_output_buffers = FALSE;
#endif // NGX_HAVE_OPENSSL_EVP

rc = ngx_http_vod_hls_init_muxer_conf(submodule_context, &muxer_conf);
if (rc != NGX_OK)
{
return rc;
}

rc = hls_muxer_init_segment(
&submodule_context->request_context,
&submodule_context->conf->hls.mpegts_muxer_config,
&muxer_conf,
&encryption_params,
submodule_context->request_params.segment_index,
&submodule_context->media_set,
Expand Down Expand Up @@ -996,10 +1111,10 @@ ngx_http_vod_hls_create_loc_conf(
conf->absolute_master_urls = NGX_CONF_UNSET;
conf->absolute_index_urls = NGX_CONF_UNSET;
conf->absolute_iframe_urls = NGX_CONF_UNSET;
conf->mpegts_muxer_config.interleave_frames = NGX_CONF_UNSET;
conf->mpegts_muxer_config.align_frames = NGX_CONF_UNSET;
conf->mpegts_muxer_config.output_id3_timestamps = NGX_CONF_UNSET;
conf->mpegts_muxer_config.align_pts = NGX_CONF_UNSET;
conf->interleave_frames = NGX_CONF_UNSET;
conf->align_frames = NGX_CONF_UNSET;
conf->align_pts = NGX_CONF_UNSET;
conf->output_id3_timestamps = NGX_CONF_UNSET;
conf->encryption_method = NGX_CONF_UNSET_UINT;
conf->m3u8_config.output_iframes_playlist = NGX_CONF_UNSET;
conf->m3u8_config.force_unmuxed_segments = NGX_CONF_UNSET;
Expand Down Expand Up @@ -1034,11 +1149,15 @@ ngx_http_vod_hls_merge_loc_conf(
ngx_conf_merge_value(conf->m3u8_config.force_unmuxed_segments, prev->m3u8_config.force_unmuxed_segments, 0);
ngx_conf_merge_uint_value(conf->m3u8_config.container_format, prev->m3u8_config.container_format, HLS_CONTAINER_AUTO);

ngx_conf_merge_value(conf->mpegts_muxer_config.interleave_frames, prev->mpegts_muxer_config.interleave_frames, 0);
ngx_conf_merge_value(conf->mpegts_muxer_config.align_frames, prev->mpegts_muxer_config.align_frames, 1);
ngx_conf_merge_value(conf->mpegts_muxer_config.output_id3_timestamps, prev->mpegts_muxer_config.output_id3_timestamps, 0);
ngx_conf_merge_value(conf->mpegts_muxer_config.align_pts, prev->mpegts_muxer_config.align_pts, 0);

ngx_conf_merge_value(conf->interleave_frames, prev->interleave_frames, 0);
ngx_conf_merge_value(conf->align_frames, prev->align_frames, 1);
ngx_conf_merge_value(conf->align_pts, prev->align_pts, 0);
ngx_conf_merge_value(conf->output_id3_timestamps, prev->output_id3_timestamps, 0);
if (conf->id3_data == NULL)
{
conf->id3_data = prev->id3_data;
}

ngx_conf_merge_uint_value(conf->encryption_method, prev->encryption_method, HLS_ENC_NONE);

m3u8_builder_init_config(
Expand Down
15 changes: 11 additions & 4 deletions ngx_http_vod_hls_commands.h
Original file line number Diff line number Diff line change
Expand Up @@ -111,28 +111,35 @@
NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1,
ngx_conf_set_flag_slot,
NGX_HTTP_LOC_CONF_OFFSET,
BASE_OFFSET + offsetof(ngx_http_vod_hls_loc_conf_t, mpegts_muxer_config.interleave_frames),
BASE_OFFSET + offsetof(ngx_http_vod_hls_loc_conf_t, interleave_frames),
NULL },

{ ngx_string("vod_hls_mpegts_align_frames"),
NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1,
ngx_conf_set_flag_slot,
NGX_HTTP_LOC_CONF_OFFSET,
BASE_OFFSET + offsetof(ngx_http_vod_hls_loc_conf_t, mpegts_muxer_config.align_frames),
BASE_OFFSET + offsetof(ngx_http_vod_hls_loc_conf_t, align_frames),
NULL },

{ ngx_string("vod_hls_mpegts_output_id3_timestamps"),
NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1,
ngx_conf_set_flag_slot,
NGX_HTTP_LOC_CONF_OFFSET,
BASE_OFFSET + offsetof(ngx_http_vod_hls_loc_conf_t, mpegts_muxer_config.output_id3_timestamps),
BASE_OFFSET + offsetof(ngx_http_vod_hls_loc_conf_t, output_id3_timestamps),
NULL },

{ ngx_string("vod_hls_mpegts_id3_data"),
NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1,
ngx_http_set_complex_value_slot,
NGX_HTTP_LOC_CONF_OFFSET,
BASE_OFFSET + offsetof(ngx_http_vod_hls_loc_conf_t, id3_data),
NULL },

{ ngx_string("vod_hls_mpegts_align_pts"),
NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1,
ngx_conf_set_flag_slot,
NGX_HTTP_LOC_CONF_OFFSET,
BASE_OFFSET + offsetof(ngx_http_vod_hls_loc_conf_t, mpegts_muxer_config.align_pts),
BASE_OFFSET + offsetof(ngx_http_vod_hls_loc_conf_t, align_pts),
NULL },

{ ngx_string("vod_hls_force_unmuxed_segments"),
Expand Down
6 changes: 5 additions & 1 deletion ngx_http_vod_hls_conf.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@ typedef struct
ngx_flag_t absolute_index_urls;
ngx_flag_t absolute_iframe_urls;
ngx_str_t master_file_name_prefix;
hls_mpegts_muxer_conf_t mpegts_muxer_config;
bool_t interleave_frames;
bool_t align_frames;
bool_t align_pts;
bool_t output_id3_timestamps;
ngx_http_complex_value_t* id3_data;
vod_uint_t encryption_method;
ngx_http_complex_value_t* encryption_key_uri;

Expand Down
42 changes: 42 additions & 0 deletions ngx_http_vod_module.c
Original file line number Diff line number Diff line change
Expand Up @@ -593,6 +593,47 @@ ngx_http_vod_set_notification_id_var(ngx_http_request_t *r, ngx_http_variable_va
return NGX_OK;
}

static ngx_int_t
ngx_http_vod_set_segment_time_var(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data)
{
ngx_http_vod_ctx_t* ctx;
media_set_t* media_set;
int64_t value;
u_char* p;

ctx = ngx_http_get_module_ctx(r, ngx_http_vod_module);
if (ctx == NULL)
{
v->not_found = 1;
return NGX_OK;
}

media_set = &ctx->submodule_context.media_set;
if (media_set->filtered_tracks >= media_set->filtered_tracks_end)
{
v->not_found = 1;
return NGX_OK;
}

p = ngx_pnalloc(r->pool, NGX_INT64_LEN);
if (p == NULL)
{
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"ngx_http_vod_set_segment_time_var: ngx_pnalloc failed");
return NGX_ERROR;
}

value = media_set_get_segment_time_millis(media_set);

v->data = p;
v->len = ngx_sprintf(p, "%L", value) - p;
v->valid = 1;
v->no_cacheable = 1;
v->not_found = 0;

return NGX_OK;
}

static ngx_int_t
ngx_http_vod_set_segment_duration_var(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data)
{
Expand Down Expand Up @@ -688,6 +729,7 @@ static ngx_http_vod_variable_t ngx_http_vod_variables[] = {
DEFINE_VAR(dynamic_mapping),
DEFINE_VAR(request_params),
DEFINE_VAR(notification_id),
DEFINE_VAR(segment_time),
DEFINE_VAR(segment_duration),
{ ngx_string("vod_frames_bytes_read"), ngx_http_vod_set_uint32_var, offsetof(ngx_http_vod_ctx_t, frames_bytes_read) },
};
Expand Down
Loading

0 comments on commit edb26f7

Please sign in to comment.