Skip to content

Commit

Permalink
Merge pull request #1480 from kaltura/hls-id3-data-var
Browse files Browse the repository at this point in the history
hls: add vod_hls_mpegts_id3_data variable
  • Loading branch information
david-winder-kaltura authored Dec 25, 2023
2 parents 13072f2 + 0b8abb6 commit 3f618d0
Show file tree
Hide file tree
Showing 10 changed files with 257 additions and 76 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
64 changes: 59 additions & 5 deletions ngx_http_vod_module.c
Original file line number Diff line number Diff line change
Expand Up @@ -410,21 +410,33 @@ static ngx_int_t
ngx_http_vod_set_sequence_id_var(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data)
{
ngx_http_vod_ctx_t *ctx;
media_sequence_t* cur_sequence;
ngx_str_t* value;

ctx = ngx_http_get_module_ctx(r, ngx_http_vod_module);
if (ctx == NULL ||
ctx->cur_sequence < ctx->submodule_context.media_set.sequences ||
ctx->cur_sequence >= ctx->submodule_context.media_set.sequences_end)
if (ctx == NULL)
{
v->not_found = 1;
return NGX_OK;
}

cur_sequence = ctx->cur_sequence;
if (cur_sequence == NULL && ctx->submodule_context.media_set.sequence_count == 1)
{
cur_sequence = ctx->submodule_context.media_set.sequences;
}

value = &ctx->cur_sequence->id;
if (cur_sequence < ctx->submodule_context.media_set.sequences ||
cur_sequence >= ctx->submodule_context.media_set.sequences_end)
{
v->not_found = 1;
return NGX_OK;
}

value = &cur_sequence->id;
if (value->len == 0)
{
value = &ctx->cur_sequence->stripped_uri;
value = &cur_sequence->stripped_uri;
if (value->len == 0)
{
v->not_found = 1;
Expand Down Expand Up @@ -593,6 +605,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 +741,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 3f618d0

Please sign in to comment.