/* * Matroska muxer * Copyright (C) 2005 Mike Matsnev * Copyright (C) 2010 Andreas Ă–man * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include "tvheadend.h" #include "streaming.h" #include "dvr.h" #include "mkmux.h" #include "ebml.h" extern int dvr_iov_max; TAILQ_HEAD(mk_cue_queue, mk_cue); #define MATROSKA_TIMESCALE 1000000 // in nS /** * */ typedef struct mk_track { int index; int enabled; int merge; int type; int tracknum; int disabled; int64_t nextpts; } mk_track; /** * */ struct mk_cue { TAILQ_ENTRY(mk_cue) link; int64_t ts; int tracknum; off_t cluster_pos; }; /** * */ struct mk_mux { int fd; char *filename; int error; off_t fdpos; // Current position in file mk_track *tracks; int ntracks; int64_t totduration; htsbuf_queue_t *cluster; int64_t cluster_tc; off_t cluster_pos; off_t segment_header_pos; off_t segment_pos; off_t segmentinfo_pos; off_t trackinfo_pos; off_t metadata_pos; off_t cue_pos; int addcue; struct mk_cue_queue cues; char uuid[16]; char *title; }; /** * */ static htsbuf_queue_t * mk_build_ebmlheader(void) { htsbuf_queue_t *q = htsbuf_queue_alloc(0); ebml_append_uint(q, 0x4286, 1); ebml_append_uint(q, 0x42f7, 1); ebml_append_uint(q, 0x42f2, 4); ebml_append_uint(q, 0x42f3, 8); ebml_append_string(q, 0x4282, "matroska"); ebml_append_uint(q, 0x4287, 2); ebml_append_uint(q, 0x4285, 2); return q; } /** * */ static void getuuid(char *id) { int r, fd = open("/dev/urandom", O_RDONLY); if(fd != -1) { r = read(fd, id, 16); close(fd); if(r == 16) return; } memset(id, 0x55, 16); } /** * */ static htsbuf_queue_t * mk_build_segment_info(mk_mux_t *mkm) { htsbuf_queue_t *q = htsbuf_queue_alloc(0); extern char *htsversion_full; char app[128]; snprintf(app, sizeof(app), "HTS Tvheadend %s", htsversion_full); ebml_append_bin(q, 0x73a4, mkm->uuid, sizeof(mkm->uuid)); ebml_append_string(q, 0x7ba9, mkm->title); ebml_append_string(q, 0x4d80, "HTS Tvheadend Matroska muxer"); ebml_append_string(q, 0x5741, app); ebml_append_uint(q, 0x2ad7b1, MATROSKA_TIMESCALE); if(mkm->totduration) ebml_append_float(q, 0x4489, (float)mkm->totduration); else ebml_append_pad(q, 7); // Must be equal to floatingpoint duration return q; } /** * */ static htsbuf_queue_t * mk_build_tracks(mk_mux_t *mkm, const struct streaming_start *ss) { const streaming_start_component_t *ssc; const char *codec_id; int i, tracktype; htsbuf_queue_t *q = htsbuf_queue_alloc(0), *t; int tracknum = 0; uint8_t buf4[4]; mkm->tracks = calloc(1, sizeof(mk_track) * ss->ss_num_components); mkm->ntracks = ss->ss_num_components; for(i = 0; i < ss->ss_num_components; i++) { ssc = &ss->ss_components[i]; mkm->tracks[i].disabled = ssc->ssc_disabled; if(ssc->ssc_disabled) continue; mkm->tracks[i].index = ssc->ssc_index; mkm->tracks[i].type = ssc->ssc_type; mkm->tracks[i].nextpts = PTS_UNSET; switch(ssc->ssc_type) { case SCT_MPEG2VIDEO: tracktype = 1; codec_id = "V_MPEG2"; mkm->tracks[i].merge = 1; break; case SCT_H264: tracktype = 1; codec_id = "V_MPEG4/ISO/AVC"; break; case SCT_MPEG2AUDIO: tracktype = 2; codec_id = "A_MPEG/L2"; break; case SCT_AC3: tracktype = 2; codec_id = "A_AC3"; break; case SCT_EAC3: tracktype = 2; codec_id = "A_EAC3"; break; case SCT_AAC: tracktype = 2; codec_id = "A_AAC"; break; case SCT_DVBSUB: tracktype = 0x11; codec_id = "S_DVBSUB"; break; case SCT_TEXTSUB: tracktype = 0x11; codec_id = "S_TEXT/UTF8"; break; default: continue; } mkm->tracks[i].enabled = 1; tracknum++; mkm->tracks[i].tracknum = tracknum; t = htsbuf_queue_alloc(0); ebml_append_uint(t, 0xd7, mkm->tracks[i].tracknum); ebml_append_uint(t, 0x73c5, mkm->tracks[i].tracknum); ebml_append_uint(t, 0x83, tracktype); ebml_append_uint(t, 0x9c, 0); // Lacing ebml_append_string(t, 0x86, codec_id); if(ssc->ssc_lang[0]) ebml_append_string(t, 0x22b59c, ssc->ssc_lang); switch(ssc->ssc_type) { case SCT_H264: case SCT_MPEG2VIDEO: case SCT_AAC: if(ssc->ssc_gh) ebml_append_bin(t, 0x63a2, pktbuf_ptr(ssc->ssc_gh), pktbuf_len(ssc->ssc_gh)); break; case SCT_DVBSUB: buf4[0] = ssc->ssc_composition_id >> 8; buf4[1] = ssc->ssc_composition_id; buf4[2] = ssc->ssc_ancillary_id >> 8; buf4[3] = ssc->ssc_ancillary_id; ebml_append_bin(t, 0x63a2, buf4, 4); break; } if(ssc->ssc_frameduration) { int d = ts_rescale(ssc->ssc_frameduration, 1000000000); ebml_append_uint(t, 0x23e383, d); } if(SCT_ISVIDEO(ssc->ssc_type)) { htsbuf_queue_t *vi = htsbuf_queue_alloc(0); ebml_append_uint(vi, 0xb0, ssc->ssc_width); ebml_append_uint(vi, 0xba, ssc->ssc_height); if(ssc->ssc_aspect_num && ssc->ssc_aspect_den) { ebml_append_uint(vi, 0x54b2, 3); // Display width/height is in DAR ebml_append_uint(vi, 0x54b0, ssc->ssc_aspect_num); ebml_append_uint(vi, 0x54ba, ssc->ssc_aspect_den); } ebml_append_master(t, 0xe0, vi); } if(SCT_ISAUDIO(ssc->ssc_type)) { htsbuf_queue_t *au = htsbuf_queue_alloc(0); ebml_append_float(au, 0xb5, sri_to_rate(ssc->ssc_sri)); ebml_append_uint(au, 0x9f, ssc->ssc_channels); ebml_append_master(t, 0xe1, au); } ebml_append_master(q, 0xae, t); } return q; } /** * */ static void mk_write_to_fd(mk_mux_t *mkm, htsbuf_queue_t *hq) { htsbuf_data_t *hd; int i = 0; TAILQ_FOREACH(hd, &hq->hq_q, hd_link) i++; struct iovec *iov = alloca(sizeof(struct iovec) * i); i = 0; TAILQ_FOREACH(hd, &hq->hq_q, hd_link) { iov[i ].iov_base = hd->hd_data + hd->hd_data_off; iov[i++].iov_len = hd->hd_data_len - hd->hd_data_off; } do { ssize_t r; int iovcnt = i < dvr_iov_max ? i : dvr_iov_max; if((r = writev(mkm->fd, iov, iovcnt)) == -1) { mkm->error = errno; tvhlog(LOG_ERR, "MKV", "%s: Unable to write -- %s", mkm->filename, strerror(errno)); return; } mkm->fdpos += r; i -= iovcnt; iov += iovcnt; } while(i); } /** * */ static void mk_write_queue(mk_mux_t *mkm, htsbuf_queue_t *q) { if(!mkm->error) mk_write_to_fd(mkm, q); htsbuf_queue_flush(q); } /** * */ static void mk_write_master(mk_mux_t *mkm, uint32_t id, htsbuf_queue_t *p) { htsbuf_queue_t q; htsbuf_queue_init(&q, 0); ebml_append_master(&q, id, p); mk_write_queue(mkm, &q); } /** * */ static void mk_write_segment_header(mk_mux_t *mkm, int64_t size) { htsbuf_queue_t q; uint8_t u8[8]; htsbuf_queue_init(&q, 0); ebml_append_id(&q, 0x18538067); u8[0] = 1; if(size == 0) { memset(u8+1, 0xff, 7); } else { u8[1] = size >> 56; u8[2] = size >> 48; u8[3] = size >> 32; u8[4] = size >> 24; u8[5] = size >> 16; u8[6] = size >> 8; u8[7] = size; } htsbuf_append(&q, &u8, 8); mk_write_queue(mkm, &q); } /** * */ static htsbuf_queue_t * build_tag_string(const char *name, const char *value, int targettype, const char *targettypename) { htsbuf_queue_t *q = htsbuf_queue_alloc(0); htsbuf_queue_t *st = htsbuf_queue_alloc(0); htsbuf_queue_t *t = htsbuf_queue_alloc(0); ebml_append_uint(t, 0x68ca, targettype ?: 50); if(targettypename) ebml_append_string(t, 0x63ca, targettypename); ebml_append_master(q, 0x63c0, t); ebml_append_string(st, 0x45a3, name); ebml_append_string(st, 0x4487, value); ebml_append_uint(st, 0x4484, 1); ebml_append_string(st, 0x447a, "und"); ebml_append_master(q, 0x67c8, st); return q; } /** * */ static htsbuf_queue_t * build_tag_int(const char *name, int value, int targettype, const char *targettypename) { char str[64]; snprintf(str, sizeof(str), "%d", value); return build_tag_string(name, str, targettype, targettypename); } /** * */ static void addtag(htsbuf_queue_t *q, htsbuf_queue_t *t) { ebml_append_master(q, 0x7373, t); } /** * */ static htsbuf_queue_t * mk_build_metadata(const dvr_entry_t *de) { htsbuf_queue_t *q = htsbuf_queue_alloc(0); char datestr[64]; struct tm tm; const char *ctype; localtime_r(&de->de_start, &tm); snprintf(datestr, sizeof(datestr), "%04d-%02d-%02d %02d:%02d:%02d", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec); addtag(q, build_tag_string("DATE_BROADCASTED", datestr, 0, NULL)); addtag(q, build_tag_string("ORIGINAL_MEDIA_TYPE", "TV", 0, NULL)); if(de->de_content_type) { ctype = epg_content_group_get_name(de->de_content_type); if(ctype != NULL) addtag(q, build_tag_string("CONTENT_TYPE", ctype, 0, NULL)); } if(de->de_channel != NULL) addtag(q, build_tag_string("TVCHANNEL", de->de_channel->ch_name, 0, NULL)); if(de->de_episode.ee_onscreen) addtag(q, build_tag_string("SYNOPSIS", de->de_episode.ee_onscreen, 0, NULL)); if(de->de_desc != NULL) addtag(q, build_tag_string("SUMMARY", de->de_desc, 0, NULL)); if(de->de_episode.ee_season) addtag(q, build_tag_int("PART_NUMBER", de->de_episode.ee_season, 60, "SEASON")); if(de->de_episode.ee_episode) addtag(q, build_tag_int("PART_NUMBER", de->de_episode.ee_episode, 0, NULL)); if(de->de_episode.ee_part) addtag(q, build_tag_int("PART_NUMBER", de->de_episode.ee_part, 40, "PART")); return q; } /** * */ static htsbuf_queue_t * mk_build_one_metaseek(mk_mux_t *mkm, uint32_t id, off_t pos) { htsbuf_queue_t *q = htsbuf_queue_alloc(0); ebml_append_idid(q, 0x53ab, id); ebml_append_uint(q, 0x53ac, pos - mkm->segment_pos); return q; } /** * */ static htsbuf_queue_t * mk_build_metaseek(mk_mux_t *mkm) { htsbuf_queue_t *q = htsbuf_queue_alloc(0); if(mkm->segmentinfo_pos) ebml_append_master(q, 0x4dbb, mk_build_one_metaseek(mkm, 0x1549a966, mkm->segmentinfo_pos)); if(mkm->trackinfo_pos) ebml_append_master(q, 0x4dbb, mk_build_one_metaseek(mkm, 0x1654ae6b, mkm->trackinfo_pos)); if(mkm->metadata_pos) ebml_append_master(q, 0x4dbb, mk_build_one_metaseek(mkm, 0x1254c367, mkm->metadata_pos)); if(mkm->cue_pos) ebml_append_master(q, 0x4dbb, mk_build_one_metaseek(mkm, 0x1c53bb6b, mkm->cue_pos)); return q; } /** * */ static void mk_write_metaseek(mk_mux_t *mkm, int first) { htsbuf_queue_t q; htsbuf_queue_init(&q, 0); ebml_append_master(&q, 0x114d9b74, mk_build_metaseek(mkm)); assert(q.hq_size < 498); ebml_append_pad(&q, 500 - q.hq_size); if(first) { mk_write_to_fd(mkm, &q); } else { off_t prev = mkm->fdpos; if(lseek(mkm->fd, mkm->segment_pos, SEEK_SET) == (off_t) -1) mkm->error = errno; mk_write_queue(mkm, &q); mkm->fdpos = prev; if(lseek(mkm->fd, mkm->fdpos, SEEK_SET) == (off_t) -1) mkm->error = errno; } htsbuf_queue_flush(&q); } /** * */ mk_mux_t * mk_mux_create(const char *filename, const struct streaming_start *ss, const struct dvr_entry *de, int write_tags) { mk_mux_t *mkm; int fd; fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0777); if(fd == -1) return NULL; mkm = calloc(1, sizeof(struct mk_mux)); getuuid(mkm->uuid); mkm->filename = strdup(filename); mkm->fd = fd; mkm->title = strdup(de->de_title); TAILQ_INIT(&mkm->cues); mk_write_master(mkm, 0x1a45dfa3, mk_build_ebmlheader()); mkm->segment_header_pos = mkm->fdpos; mk_write_segment_header(mkm, 0); mkm->segment_pos = mkm->fdpos; mk_write_metaseek(mkm, 1); // Must be first in segment mkm->segmentinfo_pos = mkm->fdpos; mk_write_master(mkm, 0x1549a966, mk_build_segment_info(mkm)); mkm->trackinfo_pos = mkm->fdpos; mk_write_master(mkm, 0x1654ae6b, mk_build_tracks(mkm, ss)); if(write_tags) { mkm->metadata_pos = mkm->fdpos; mk_write_master(mkm, 0x1254c367, mk_build_metadata(de)); } mk_write_metaseek(mkm, 0); return mkm; } /** * */ static void addcue(mk_mux_t *mkm, int64_t pts, int tracknum) { struct mk_cue *mc = malloc(sizeof(struct mk_cue)); mc->ts = pts; mc->tracknum = tracknum; mc->cluster_pos = mkm->cluster_pos; TAILQ_INSERT_TAIL(&mkm->cues, mc, link); } /** * */ static void mk_close_cluster(mk_mux_t *mkm) { if(mkm->cluster != NULL) mk_write_master(mkm, 0x1f43b675, mkm->cluster); mkm->cluster = NULL; } /** * */ static void mk_write_frame_i(mk_mux_t *mkm, mk_track *t, th_pkt_t *pkt) { int64_t pts = pkt->pkt_pts, delta, nxt; unsigned char c_delta_flags[3]; int keyframe = pkt->pkt_frametype < PKT_P_FRAME; int skippable = pkt->pkt_frametype == PKT_B_FRAME; int vkeyframe = SCT_ISVIDEO(t->type) && keyframe; uint8_t *data; size_t len; const int clusersizemax = 2000000; if(pts == PTS_UNSET) // This is our best guess, it might be wrong but... oh well pts = t->nextpts; if(pts != PTS_UNSET) { t->nextpts = pts + (pkt->pkt_duration >> pkt->pkt_field); nxt = ts_rescale(t->nextpts, 1000000000 / MATROSKA_TIMESCALE); pts = ts_rescale(pts, 1000000000 / MATROSKA_TIMESCALE); if(mkm->totduration < nxt) mkm->totduration = nxt; delta = pts - mkm->cluster_tc; if(delta > 32767ll || delta < -32768ll) mk_close_cluster(mkm); else if(vkeyframe && (delta > 30000ll || delta < -30000ll)) mk_close_cluster(mkm); } else { return; } if(vkeyframe && mkm->cluster && mkm->cluster->hq_size > clusersizemax/4) mk_close_cluster(mkm); else if(mkm->cluster && mkm->cluster->hq_size > clusersizemax) mk_close_cluster(mkm); if(mkm->cluster == NULL) { mkm->cluster_tc = pts; mkm->cluster = htsbuf_queue_alloc(0); mkm->cluster_pos = mkm->fdpos; mkm->addcue = 1; ebml_append_uint(mkm->cluster, 0xe7, mkm->cluster_tc); delta = 0; } if(mkm->addcue && vkeyframe) { mkm->addcue = 0; addcue(mkm, pts, t->tracknum); } data = pktbuf_ptr(pkt->pkt_payload); len = pktbuf_len(pkt->pkt_payload); if(t->type == SCT_AAC) { // Skip ADTS header if(len < 7) return; len -= 7; data += 7; } ebml_append_id(mkm->cluster, 0xa3 ); // SimpleBlock ebml_append_size(mkm->cluster, len + 4); ebml_append_size(mkm->cluster, t->tracknum); c_delta_flags[0] = delta >> 8; c_delta_flags[1] = delta; c_delta_flags[2] = (keyframe << 7) | skippable; htsbuf_append(mkm->cluster, c_delta_flags, 3); htsbuf_append(mkm->cluster, data, len); } /** * */ void mk_mux_write_pkt(mk_mux_t *mkm, struct th_pkt *pkt) { int i; mk_track *t = NULL; for(i = 0; i < mkm->ntracks;i++) { if(mkm->tracks[i].index == pkt->pkt_componentindex && mkm->tracks[i].enabled) { t = &mkm->tracks[i]; break; } } if(t != NULL && !t->disabled) { if(t->merge) pkt = pkt_merge_header(pkt); mk_write_frame_i(mkm, t, pkt); } pkt_ref_dec(pkt); } /** * */ static void mk_write_cues(mk_mux_t *mkm) { struct mk_cue *mc; htsbuf_queue_t *q, *c, *p; if(TAILQ_FIRST(&mkm->cues) == NULL) return; q = htsbuf_queue_alloc(0); while((mc = TAILQ_FIRST(&mkm->cues)) != NULL) { TAILQ_REMOVE(&mkm->cues, mc, link); c = htsbuf_queue_alloc(0); ebml_append_uint(c, 0xb3, mc->ts); p = htsbuf_queue_alloc(0); ebml_append_uint(p, 0xf7, mc->tracknum); ebml_append_uint(p, 0xf1, mc->cluster_pos - mkm->segment_pos); ebml_append_master(c, 0xb7, p); ebml_append_master(q, 0xbb, c); free(mc); } mkm->cue_pos = mkm->fdpos; mk_write_master(mkm, 0x1c53bb6b, q); } /** * */ void mk_mux_close(mk_mux_t *mkm) { int64_t totsize; mk_close_cluster(mkm); mk_write_cues(mkm); mk_write_metaseek(mkm, 0); totsize = mkm->fdpos; // Rewrite segment info to update duration if(lseek(mkm->fd, mkm->segmentinfo_pos, SEEK_SET) == mkm->segmentinfo_pos) mk_write_master(mkm, 0x1549a966, mk_build_segment_info(mkm)); else tvhlog(LOG_ERR, "MKV", "%s: Unable to write duration, seek failed -- %s", mkm->filename, strerror(errno)); // Rewrite segment header to update total size if(lseek(mkm->fd, mkm->segment_header_pos, SEEK_SET) == mkm->segment_header_pos) { mk_write_segment_header(mkm, totsize - mkm->segment_header_pos - 12); } else tvhlog(LOG_ERR, "MKV", "%s: Unable to write total size, seek failed -- %s", mkm->filename, strerror(errno)); close(mkm->fd); free(mkm->filename); free(mkm->tracks); free(mkm->title); free(mkm); }