#include "hls-m3u8.h" #include "hls-param.h" #include "list.h" #include #include #include #include #include #include #define VMAX(a, b) ((a) > (b) ? (a) : (b)) struct hls_m3u8_t { int live; int version; int64_t seq; // m3u8 sequence number (base 0) int64_t duration;// target duration size_t count; struct list_head root; char* ext_x_map; }; struct hls_segment_t { struct list_head link; int64_t pts; // present timestamp (millisecond) int64_t duration; // segment duration (millisecond) int64_t offset; // EXT-X-BYTERANGE offset: a byte offset from the beginning of the resource int64_t bytes; // EXT-X-BYTERANGE length int discontinuity; // EXT-X-DISCONTINUITY flag char* name; size_t capacity; }; struct hls_m3u8_t* hls_m3u8_create(int live, int version) { struct hls_m3u8_t* m3u8; m3u8 = (struct hls_m3u8_t*)calloc(1, sizeof(*m3u8)); if (NULL == m3u8) return NULL; assert(0 == live || live >= HLS_LIVE_NUM); m3u8->version = version; m3u8->live = live; m3u8->seq = 0; m3u8->count = 0; m3u8->duration = 0; LIST_INIT_HEAD(&m3u8->root); return m3u8; } void hls_m3u8_destroy(struct hls_m3u8_t* m3u8) { struct list_head* l, *n; struct hls_segment_t* seg; list_for_each_safe(l, n, &m3u8->root) { seg = list_entry(l, struct hls_segment_t, link); free(seg); } if (m3u8->ext_x_map) free(m3u8->ext_x_map); free(m3u8); } static struct hls_segment_t* hls_segment_alloc(size_t bytes) { struct hls_segment_t* seg; seg = (struct hls_segment_t*)malloc(sizeof(*seg) + bytes); if (seg) { seg->name = (char*)(seg + 1); seg->capacity = bytes; seg->offset = 0; seg->bytes = 0; seg->pts = 0; seg->discontinuity = 0; } return seg; } int hls_m3u8_add(struct hls_m3u8_t* m3u8, const char* name, int64_t pts, int64_t duration, int discontinuity) { return hls_m3u8_add_with_offset(m3u8, name, pts, duration, discontinuity, 0, 0); } int hls_m3u8_add_with_offset(hls_m3u8_t* m3u8, const char* name, int64_t pts, int64_t duration, int discontinuity, int64_t offset, int64_t bytes) { size_t r; struct hls_segment_t* seg; seg = NULL; r = strlen(name); if (0 != m3u8->live && m3u8->count >= (size_t)m3u8->live) { assert(m3u8->count == (size_t)m3u8->live); ++m3u8->seq; // update EXT-X-MEDIA-SEQUENCE // reuse the first segment seg = list_entry(m3u8->root.next, struct hls_segment_t, link); list_remove(&seg->link); // check name length if (r + 1 > seg->capacity) { free(seg); seg = NULL; --m3u8->count; } } if (NULL == seg) { // reserve more space for reuse segment seg = hls_segment_alloc(r + (m3u8->live ? 16 : 1)); if (!seg) return -ENOMEM; ++m3u8->count; } // update EXT-X-TARGETDURATION m3u8->duration = VMAX(m3u8->duration, duration); // segment seg->pts = pts; seg->bytes = bytes; seg->offset = offset; seg->duration = duration; seg->discontinuity = discontinuity; // EXT-X-DISCONTINUITY memcpy(seg->name, name, r + 1); // copy last '\0' list_insert_after(&seg->link, m3u8->root.prev); return 0; } int hls_m3u8_set_x_map(hls_m3u8_t* m3u8, const char* name) { if (m3u8->ext_x_map) free(m3u8->ext_x_map); m3u8->ext_x_map = name ? strdup(name) : NULL; return m3u8->ext_x_map ? 0 : -ENOMEM; } size_t hls_m3u8_count(struct hls_m3u8_t* m3u8) { return m3u8->count; } int hls_m3u8_playlist(struct hls_m3u8_t* m3u8, int eof, char* playlist, size_t bytes) { int r; size_t n; struct list_head* link; struct hls_segment_t* seg; r = snprintf(playlist, bytes, "#EXTM3U\n" // MUST "#EXT-X-VERSION:%d\n" // Optional "#EXT-X-TARGETDURATION:%" PRId64 "\n" // MUST, decimal-integer, in seconds "#EXT-X-MEDIA-SEQUENCE:%" PRId64 "\n" "%s" // #EXT-X-PLAYLIST-TYPE:VOD "%s", // #EXT-X-ALLOW-CACHE:YES m3u8->version, (m3u8->duration + 999) / 1000, m3u8->seq, m3u8->live ? "" : "#EXT-X-PLAYLIST-TYPE:VOD\n", m3u8->live ? "" : "#EXT-X-ALLOW-CACHE:YES\n"); if (r <= 0 || (size_t)r >= bytes) return -ENOMEM; // #EXT-X-MAP:URI="main.mp4",BYTERANGE="1206@0" if (m3u8->ext_x_map) r += snprintf(playlist + r, r < bytes ? bytes - r : 0, "#EXT-X-MAP:URI=\"%s\",\n", m3u8->ext_x_map); n = r; list_for_each(link, &m3u8->root) { if (bytes <= n) break; seg = list_entry(link, struct hls_segment_t, link); if (seg->discontinuity) n += snprintf(playlist + n, n < bytes ? bytes - n : 0, "#EXT-X-DISCONTINUITY\n"); if (bytes > n) { if(seg->bytes > 0) n += snprintf(playlist + n, n < bytes ? bytes - n : 0, "#EXTINF:%.3f,\n#EXT-X-BYTERANGE:%" PRId64 "@%" PRId64 "\n%s\n", seg->duration / 1000.0, seg->bytes, seg->offset, seg->name); else n += snprintf(playlist + n, n < bytes ? bytes - n : 0, "#EXTINF:%.3f,\n%s\n", seg->duration / 1000.0, seg->name); } } if (eof && bytes > n + 15) n += snprintf(playlist + n, n < bytes ? bytes - n : 0, "#EXT-X-ENDLIST\n"); return (bytes > n && n > 0) ? 0 : -ENOMEM; }