src/htsstr.c \
src/rawtsinput.c \
src/iptv_input.c \
+ src/avc.c \
SRCS += src/plumbing/tsfix.c \
+ src/plumbing/globalheaders.c \
SRCS += src/dvr/dvr_db.c \
src/dvr/dvr_rec.c \
- src/dvr/dvr_autorec.c
+ src/dvr/dvr_autorec.c \
+ src/dvr/ebml.c \
+ src/dvr/mkmux.c \
SRCS-${CONFIG_LINUXDVB} += \
src/dvb/dvb.c \
# Common CFLAGS for all files
CFLAGS_com = -g -funsigned-char -O2
-CFLAGS_com += -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64
+CFLAGS_com += -D_FILE_OFFSET_BITS=64
CFLAGS_com += -I${BUILDDIR} -I${CURDIR}/src -I${CURDIR}
CFLAGS_com += -DHTS_VERSION=\"$(VERSION)\"
--- /dev/null
+/*
+ * AVC helper functions for muxers
+ * Copyright (c) 2006 Baptiste Coudurier <baptiste.coudurier@smartjog.com>
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "avc.h"
+
+const uint8_t *avc_find_startcode(const uint8_t *p, const uint8_t *end)
+{
+ const uint8_t *a = p + 4 - ((long)p & 3);
+
+ for( end -= 3; p < a && p < end; p++ ) {
+ if( p[0] == 0 && p[1] == 0 && p[2] == 1 )
+ return p;
+ }
+
+ for( end -= 3; p < end; p += 4 ) {
+ uint32_t x = *(const uint32_t*)p;
+// if( (x - 0x01000100) & (~x) & 0x80008000 ) // little endian
+// if( (x - 0x00010001) & (~x) & 0x00800080 ) // big endian
+ if( (x - 0x01010101) & (~x) & 0x80808080 ) { // generic
+ if( p[1] == 0 ) {
+ if( p[0] == 0 && p[2] == 1 )
+ return p-1;
+ if( p[2] == 0 && p[3] == 1 )
+ return p;
+ }
+ if( p[3] == 0 ) {
+ if( p[2] == 0 && p[4] == 1 )
+ return p+1;
+ if( p[4] == 0 && p[5] == 1 )
+ return p+2;
+ }
+ }
+ }
+
+ for( end += 3; p < end; p++ ) {
+ if( p[0] == 0 && p[1] == 0 && p[2] == 1 )
+ return p;
+ }
+
+ return end + 3;
+}
+
+int avc_parse_nal_units(ByteIOContext *pb, const uint8_t *buf_in, int size)
+{
+ const uint8_t *p = buf_in;
+ const uint8_t *end = p + size;
+ const uint8_t *nal_start, *nal_end, *b;
+
+ printf("CONVERT SIZE %d\n", size);
+
+ size = 0;
+ nal_start = avc_find_startcode(p, end);
+ while (nal_start < end) {
+ while(!*(nal_start++));
+ nal_end = avc_find_startcode(nal_start, end);
+ printf("%4d bytes %5d : %d\n", nal_end - nal_start,
+ nal_start - buf_in,
+ nal_end - buf_in);
+
+ int l = nal_end - nal_start;
+
+ b = nal_start;
+ while(l > 3 && !(b[l-3] | b[l-2] | b[l-1]))
+ l--;
+ put_be32(pb, l);
+ put_buffer(pb, nal_start, l);
+ size += 4 + l;
+ nal_start = nal_end;
+ }
+ printf("size=%d\n", size);
+ return size;
+}
+
+
+int avc_parse_nal_units_buf(const uint8_t *buf_in, uint8_t **buf, int *size)
+{
+ ByteIOContext *pb;
+ int ret = url_open_dyn_buf(&pb);
+ if(ret < 0)
+ return ret;
+
+ avc_parse_nal_units(pb, buf_in, *size);
+
+ av_freep(buf);
+ *size = url_close_dyn_buf(pb, buf);
+ return 0;
+}
+
+
+static uint32_t
+RB32(const uint8_t *d)
+{
+ return (d[0] << 24) | (d[1] << 16) | (d[2] << 8) | d[3];
+}
+
+static uint32_t
+RB24(const uint8_t *d)
+{
+ return (d[0] << 16) | (d[1] << 8) | d[2];
+}
+
+
+static int isom_write_avcc(ByteIOContext *pb, const uint8_t *data, int len)
+{
+ if (len > 6) {
+ /* check for h264 start code */
+ if (RB32(data) == 0x00000001 ||
+ RB24(data) == 0x000001) {
+ uint8_t *buf=NULL, *end, *start;
+ uint32_t sps_size=0, pps_size=0;
+ uint8_t *sps=0, *pps=0;
+
+ int ret = avc_parse_nal_units_buf(data, &buf, &len);
+ if (ret < 0)
+ return ret;
+ start = buf;
+ end = buf + len;
+
+ /* look for sps and pps */
+ while (buf < end) {
+ unsigned int size;
+ uint8_t nal_type;
+ size = RB32(buf);
+ nal_type = buf[4] & 0x1f;
+ if (nal_type == 7) { /* SPS */
+ sps = buf + 4;
+ sps_size = size;
+ } else if (nal_type == 8) { /* PPS */
+ pps = buf + 4;
+ pps_size = size;
+ }
+ buf += size + 4;
+ }
+ if(!sps || !pps) {
+ av_free(start);
+ return -1;
+ }
+
+ put_byte(pb, 1); /* version */
+ put_byte(pb, sps[1]); /* profile */
+ put_byte(pb, sps[2]); /* profile compat */
+ put_byte(pb, sps[3]); /* level */
+ put_byte(pb, 0xff); /* 6 bits reserved (111111) + 2 bits nal size length - 1 (11) */
+ put_byte(pb, 0xe1); /* 3 bits reserved (111) + 5 bits number of sps (00001) */
+
+ put_be16(pb, sps_size);
+ put_buffer(pb, sps, sps_size);
+ put_byte(pb, 1); /* number of pps */
+ put_be16(pb, pps_size);
+ put_buffer(pb, pps, pps_size);
+ av_free(start);
+ } else {
+ put_buffer(pb, data, len);
+ }
+ }
+ return 0;
+}
+
+
+
+
+th_pkt_t *
+avc_convert_pkt(th_pkt_t *src)
+{
+ ByteIOContext *payload, *headers;
+
+ th_pkt_t *pkt = malloc(sizeof(th_pkt_t));
+ *pkt = *src;
+ pkt->pkt_refcount = 1;
+
+ url_open_dyn_buf(&payload);
+ avc_parse_nal_units(payload, pkt->pkt_payload, pkt->pkt_payloadlen);
+
+ pkt->pkt_payloadlen = url_close_dyn_buf(payload, &pkt->pkt_payload);
+
+ if(pkt->pkt_globaldata) {
+ url_open_dyn_buf(&headers);
+
+ isom_write_avcc(headers, pkt->pkt_globaldata, pkt->pkt_globaldata_len);
+ pkt->pkt_globaldata_len = url_close_dyn_buf(headers, &pkt->pkt_globaldata);
+
+ hexdump("annexB: ", src->pkt_globaldata, src->pkt_globaldata_len);
+ hexdump(" AVC: ", pkt->pkt_globaldata, pkt->pkt_globaldata_len);
+
+
+ }
+ pkt_ref_dec(src);
+ return pkt;
+}
+
--- /dev/null
+/*
+ * AVC helper functions for muxers
+ * Copyright (c) 2006 Baptiste Coudurier <baptiste.coudurier@smartjog.com>
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef AVC_H__
+#define AVC_H__
+
+#include <stdint.h>
+#include <libavformat/avformat.h>
+#include <libavformat/avio.h>
+#include "tvhead.h"
+#include "packet.h"
+
+const uint8_t *avc_find_startcode(const uint8_t *p, const uint8_t *end);
+int avc_parse_nal_units(ByteIOContext *pb, const uint8_t *buf_in, int size);
+int avc_parse_nal_units_buf(const uint8_t *buf_in, uint8_t **buf, int *size);
+th_pkt_t *avc_convert_pkt(th_pkt_t *src);
+
+
+#endif
typedef enum {
DVR_RS_PENDING,
- DVR_RS_WAIT_AUDIO_LOCK,
- DVR_RS_WAIT_VIDEO_LOCK,
+ DVR_RS_WAIT_PROGRAM_START,
DVR_RS_RUNNING,
DVR_RS_COMMERCIAL,
DVR_RS_ERROR,
th_subscription_t *de_s;
streaming_queue_t de_sq;
streaming_target_t *de_tsfix;
+ streaming_target_t *de_gh;
/**
* Initialized upon SUBSCRIPTION_TRANSPORT_RUN
*/
- int64_t de_ts_offset; /* Offset to compensate for PTS/DTS not beeing
- 0 at start of recording */
- int64_t de_ts_com_start; /* Starttime for last/current commercial break */
- int64_t de_ts_com_offset; /* Offset to subtract to PTS/DTS to skip over
- all commercial breaks so far */
-
- struct dvr_rec_stream_list de_streams;
- AVFormatContext *de_fctx;
-
- int de_header_written;
+ struct mk_mux *de_mkmux;
} dvr_entry_t;
switch(de->de_rec_state) {
case DVR_RS_PENDING:
- return "Pending start";
- case DVR_RS_WAIT_AUDIO_LOCK:
- return "Waiting for audio lock";
- case DVR_RS_WAIT_VIDEO_LOCK:
- return "Waiting for video lock";
+ return "Waiting for stream";
+ case DVR_RS_WAIT_PROGRAM_START:
+ return "Waiting for program start";
case DVR_RS_RUNNING:
return "Running";
case DVR_RS_COMMERCIAL:
LIST_REMOVE(de, de_channel_link);
LIST_REMOVE(de, de_global_link);
+ de->de_channel = NULL;
dvrdb_changed();
#include "transports.h"
#include "plumbing/tsfix.h"
+#include "plumbing/globalheaders.h"
-static const AVRational mpeg_tc = {1, 90000};
-
-typedef struct dvr_rec_stream {
- LIST_ENTRY(dvr_rec_stream) drs_link;
-
- int drs_source_index;
- AVStream *drs_lavf_stream;
-
- int drs_decoded;
-
-} dvr_rec_stream_t;
-
+#include "mkmux.h"
/**
*
*/
static void *dvr_thread(void *aux);
-static void dvr_thread_new_pkt(dvr_entry_t *de, th_pkt_t *pkt);
static void dvr_spawn_postproc(dvr_entry_t *de);
static void dvr_thread_epilog(dvr_entry_t *de);
else
weight = 300;
- de->de_tsfix = tsfix_create(&de->de_sq.sq_st);
+ de->de_gh = globalheaders_create(&de->de_sq.sq_st);
+
+ de->de_tsfix = tsfix_create(de->de_gh);
de->de_s = subscription_create_from_channel(de->de_channel, weight,
buf, de->de_tsfix, 0);
de->de_s = NULL;
tsfix_destroy(de->de_tsfix);
+ globalheaders_destroy(de->de_gh);
de->de_last_error = stopcode;
}
while(1) {
if(stat(fullname, &st) == -1) {
- tvhlog(LOG_DEBUG, "pvr", "File \"%s\" -- %s -- Using for recording",
+ tvhlog(LOG_DEBUG, "dvr", "File \"%s\" -- %s -- Using for recording",
fullname, strerror(errno));
break;
}
- tvhlog(LOG_DEBUG, "pvr", "Overwrite protection, file \"%s\" exists",
+ tvhlog(LOG_DEBUG, "dvr", "Overwrite protection, file \"%s\" exists",
fullname);
tally++;
vsnprintf(msgbuf, sizeof(msgbuf), fmt, ap);
va_end(ap);
- tvhlog(LOG_ERR, "pvr",
+ tvhlog(LOG_ERR, "dvr",
"Recording error: \"%s\": %s",
de->de_filename ?: de->de_title, msgbuf);
}
static void
dvr_rec_set_state(dvr_entry_t *de, dvr_rs_state_t newstate, int error)
{
+ if(de->de_rec_state == newstate)
+ return;
de->de_rec_state = newstate;
de->de_last_error = error;
if(error)
dvr_rec_start(dvr_entry_t *de, const streaming_start_t *ss)
{
const source_info_t *si = &ss->ss_si;
- dvr_rec_stream_t *drs;
- AVOutputFormat *fmt;
- AVFormatContext *fctx;
- AVCodecContext *ctx;
- AVCodec *codec;
- enum CodecID codec_id;
- enum CodecType codec_type;
- const char *codec_name;
- char urlname[512];
- int err, r;
+ const streaming_start_component_t *ssc;
int i;
if(pvr_generate_filename(de) != 0) {
return;
}
- /* Find lavf format */
+ de->de_mkmux = mk_mux_create(de->de_filename, ss, de);
- fmt = guess_format(dvr_format, NULL, NULL);
- if(fmt == NULL) {
- dvr_rec_fatal_error(de, "Unable to open file format \"%s\" for output",
- dvr_format);
+ if(de->de_mkmux == NULL) {
+ dvr_rec_fatal_error(de, "Unable to open file");
return;
}
- /* Init format context */
-
- fctx = avformat_alloc_context();
-
- av_strlcpy(fctx->title, de->de_ititle ?: "",
- sizeof(fctx->title));
-
- av_strlcpy(fctx->comment, de->de_desc ?: "",
- sizeof(fctx->comment));
-
- av_strlcpy(fctx->copyright, de->de_channel->ch_name,
- sizeof(fctx->copyright));
-
- fctx->oformat = fmt;
-
- /* Open output file */
-
- snprintf(urlname, sizeof(urlname), "file:%s", de->de_filename);
-
- if((err = url_fopen(&fctx->pb, urlname, URL_WRONLY)) < 0) {
- av_free(fctx);
- dvr_rec_fatal_error(de, "Unable to create output file \"%s\". "
- "FFmpeg error %d", urlname, err);
- return;
- }
-
- av_set_parameters(fctx, NULL);
-
-
- /**
- * Setup each stream
- */
- for(i = 0; i < ss->ss_num_components; i++) {
- const streaming_start_component_t *ssc = &ss->ss_components[i];
-
- switch(ssc->ssc_type) {
- case SCT_AC3:
- codec_id = CODEC_ID_AC3;
- codec_type = CODEC_TYPE_AUDIO;
- codec_name = "AC-3";
- break;
- case SCT_MPEG2AUDIO:
- codec_id = CODEC_ID_MP2;
- codec_type = CODEC_TYPE_AUDIO;
- codec_name = "MPEG";
- break;
- case SCT_MPEG2VIDEO:
- codec_id = CODEC_ID_MPEG2VIDEO;
- codec_type = CODEC_TYPE_VIDEO;
- codec_name = "MPEG-2";
- break;
- case SCT_H264:
- codec_id = CODEC_ID_H264;
- codec_type = CODEC_TYPE_VIDEO;
- codec_name = "H264";
- break;
- default:
- continue;
- }
-
- codec = avcodec_find_decoder(codec_id);
- if(codec == NULL) {
- tvhlog(LOG_ERR, "dvr",
- "%s - Cannot find codec for %s, ignoring stream",
- de->de_ititle, codec_name);
- continue;
- }
-
- drs = calloc(1, sizeof(dvr_rec_stream_t));
- drs->drs_source_index = ssc->ssc_index;
-
- drs->drs_lavf_stream = av_new_stream(fctx, fctx->nb_streams);
-
- ctx = drs->drs_lavf_stream->codec;
- ctx->codec_id = codec_id;
- ctx->codec_type = codec_type;
-
- pthread_mutex_lock(&ffmpeg_lock);
- r = avcodec_open(ctx, codec);
- pthread_mutex_unlock(&ffmpeg_lock);
-
- if(r < 0) {
- tvhlog(LOG_ERR, "dvr",
- "%s - Cannot open codec for %s, ignoring stream",
- de->de_ititle, codec_name);
- free(ctx);
- continue;
- }
-
- memcpy(drs->drs_lavf_stream->language, ssc->ssc_lang, 4);
-
- LIST_INSERT_HEAD(&de->de_streams, drs, drs_link);
- }
-
- de->de_fctx = fctx;
- de->de_ts_offset = AV_NOPTS_VALUE;
-
-
tvhlog(LOG_INFO, "dvr", "%s from "
"adapter: \"%s\", "
"network: \"%s\", mux: \"%s\", provider: \"%s\", "
si->si_mux ?: "<N/A>",
si->si_provider ?: "<N/A>",
si->si_service ?: "<N/A>");
+
+
+ for(i = 0; i < ss->ss_num_components; i++) {
+ ssc = &ss->ss_components[i];
+
+ tvhlog(LOG_INFO, "dvr",
+ "%2d %-20s %-4s %5d x %-5d %-5d",
+ ssc->ssc_index,
+ streaming_component_type2txt(ssc->ssc_type),
+ ssc->ssc_lang,
+ ssc->ssc_width,
+ ssc->ssc_height,
+ ssc->ssc_frameduration);
+ }
}
streaming_queue_t *sq = &de->de_sq;
streaming_message_t *sm;
int run = 1;
- th_pkt_t *pkt;
pthread_mutex_lock(&sq->sq_mutex);
switch(sm->sm_type) {
case SMT_PACKET:
- pkt = sm->sm_data;
- sm->sm_data = NULL;
if(dispatch_clock > de->de_start - (60 * de->de_start_extra)) {
- pkt = pkt_merge_global(pkt);
- dvr_thread_new_pkt(de, pkt);
+ dvr_rec_set_state(de, DVR_RS_RUNNING, 0);
+ mk_mux_write_pkt(de->de_mkmux, sm->sm_data);
+ sm->sm_data = NULL;
}
- pkt_ref_dec(pkt);
break;
case SMT_START:
- assert(de->de_fctx == NULL);
-
pthread_mutex_lock(&global_lock);
+ dvr_rec_set_state(de, DVR_RS_WAIT_PROGRAM_START, 0);
dvr_rec_start(de, sm->sm_data);
- de->de_header_written = 0;
- dvr_rec_set_state(de, DVR_RS_WAIT_AUDIO_LOCK, 0);
pthread_mutex_unlock(&global_lock);
break;
de->de_last_error = 0;
tvhlog(LOG_INFO,
- "pvr", "Recording completed: \"%s\"",
+ "dvr", "Recording completed: \"%s\"",
de->de_filename ?: de->de_title);
} else {
dvr_rec_set_state(de, DVR_RS_ERROR, sm->sm_code);
tvhlog(LOG_ERR,
- "pvr", "Recording stopped: \"%s\": %s",
+ "dvr", "Recording stopped: \"%s\": %s",
de->de_filename ?: de->de_title,
streaming_code2txt(sm->sm_code));
}
if(de->de_last_error != code) {
dvr_rec_set_state(de, DVR_RS_ERROR, code);
tvhlog(LOG_ERR,
- "pvr", "Streaming error: \"%s\": %s",
+ "dvr", "Streaming error: \"%s\": %s",
de->de_filename ?: de->de_title,
streaming_code2txt(code));
}
dvr_rec_set_state(de, DVR_RS_ERROR, sm->sm_code);
tvhlog(LOG_ERR,
- "pvr", "Recording unable to start: \"%s\": %s",
+ "dvr", "Recording unable to start: \"%s\": %s",
de->de_filename ?: de->de_title,
streaming_code2txt(sm->sm_code));
}
}
-/**
- * Check if all streams of the given type has been decoded
- */
-static int
-is_all_decoded(dvr_entry_t *de, enum CodecType type)
-{
- dvr_rec_stream_t *drs;
- AVStream *st;
-
- LIST_FOREACH(drs, &de->de_streams, drs_link) {
- st = drs->drs_lavf_stream;
- if(st->codec->codec->type == type && drs->drs_decoded == 0)
- return 0;
- }
- return 1;
-}
-
-
-/**
- *
- */
-static void
-dvr_thread_new_pkt(dvr_entry_t *de, th_pkt_t *pkt)
-{
- AVFormatContext *fctx = de->de_fctx;
- dvr_rec_stream_t *drs;
- AVStream *st, *stx;
- AVCodecContext *ctx;
- AVPacket avpkt;
- void *abuf;
- AVFrame pic;
- int r, data_size, i;
- void *buf;
- size_t bufsize;
- char txt[100];
- int64_t pts, dts;
-
- buf = pkt->pkt_payload;
- bufsize = pkt->pkt_payloadlen;
- LIST_FOREACH(drs, &de->de_streams, drs_link)
- if(drs->drs_source_index == pkt->pkt_componentindex)
- break;
-
- if(drs == NULL)
- return;
-
- st = drs->drs_lavf_stream;
- ctx = st->codec;
-
- if(de->de_ts_offset == AV_NOPTS_VALUE)
- de->de_ts_offset = pkt->pkt_dts;
-
- switch(de->de_rec_state) {
- default:
- break;
-
- case DVR_RS_WAIT_AUDIO_LOCK:
- if(ctx->codec_type != CODEC_TYPE_AUDIO || drs->drs_decoded)
- break;
-
- data_size = AVCODEC_MAX_AUDIO_FRAME_SIZE;
- abuf = av_malloc(data_size);
-
- r = avcodec_decode_audio2(ctx, abuf, &data_size, buf, bufsize);
- av_free(abuf);
-
- if(r != 0 && data_size) {
- tvhlog(LOG_DEBUG, "dvr", "%s - "
- "Stream #%d: \"%s\" decoded a complete audio frame: "
- "%d channels in %d Hz",
- de->de_ititle, st->index, ctx->codec->name,
- ctx->channels, ctx->sample_rate);
-
- drs->drs_decoded = 1;
- }
-
- if(is_all_decoded(de, CODEC_TYPE_AUDIO))
- dvr_rec_set_state(de, DVR_RS_WAIT_VIDEO_LOCK, 0);
- break;
-
- case DVR_RS_WAIT_VIDEO_LOCK:
- if(ctx->codec_type != CODEC_TYPE_VIDEO || drs->drs_decoded)
- break;
-
- r = avcodec_decode_video(st->codec, &pic, &data_size, buf, bufsize);
- if(r != 0 && data_size) {
- tvhlog(LOG_DEBUG, "dvr", "%s - "
- "Stream #%d: \"%s\" decoded a complete video frame: "
- "%d x %d at %.2fHz",
- de->de_ititle, st->index, ctx->codec->name,
- ctx->width, st->codec->height,
- (float)ctx->time_base.den / (float)ctx->time_base.num);
-
- drs->drs_decoded = 1;
-
- st->sample_aspect_ratio = st->codec->sample_aspect_ratio;
- }
-
- if(!is_all_decoded(de, CODEC_TYPE_VIDEO))
- break;
-
- /* All Audio & Video decoded, start recording */
-
- dvr_rec_set_state(de, DVR_RS_RUNNING, 0);
-
- if(!de->de_header_written) {
-
- if(av_write_header(fctx)) {
- tvhlog(LOG_ERR, "dvr",
- "%s - Unable to write header",
- de->de_ititle);
- break;
- }
-
- de->de_header_written = 1;
-
- tvhlog(LOG_INFO, "dvr",
- "%s - Header written to file, stream dump:",
- de->de_ititle);
-
- for(i = 0; i < fctx->nb_streams; i++) {
- stx = fctx->streams[i];
-
- avcodec_string(txt, sizeof(txt), stx->codec, 1);
-
- tvhlog(LOG_INFO, "dvr", "%s - Stream #%d: %s [%d/%d]",
- de->de_ititle, i, txt,
- stx->time_base.num, stx->time_base.den);
-
- }
- }
- /* FALLTHRU */
-
- case DVR_RS_RUNNING:
-
- if(de->de_header_written == 0)
- break;
-
- if(pkt->pkt_commercial == COMMERCIAL_YES &&
- st->codec->codec->type == CODEC_TYPE_VIDEO &&
- pkt->pkt_frametype == PKT_I_FRAME) {
-
- de->de_ts_com_start = pkt->pkt_dts;
-
- dvr_rec_set_state(de, DVR_RS_COMMERCIAL, 0);
- break;
- }
- outputpacket:
- dts = pkt->pkt_dts - de->de_ts_offset - de->de_ts_com_offset;
- pts = pkt->pkt_pts - de->de_ts_offset - de->de_ts_com_offset;
- if(dts < 0 || pts < 0)
- break;
-
- av_init_packet(&avpkt);
- avpkt.stream_index = st->index;
-
- avpkt.dts = av_rescale_q(dts, mpeg_tc, st->time_base);
- avpkt.pts = av_rescale_q(pts, mpeg_tc, st->time_base);
- avpkt.data = buf;
- avpkt.size = bufsize;
- avpkt.duration = av_rescale_q(pkt->pkt_duration, mpeg_tc, st->time_base);
- avpkt.flags = pkt->pkt_frametype >= PKT_P_FRAME ? 0 : PKT_FLAG_KEY;
- r = av_interleaved_write_frame(fctx, &avpkt);
- break;
-
-
- case DVR_RS_COMMERCIAL:
-
- if(pkt->pkt_commercial != COMMERCIAL_YES &&
- st->codec->codec->type == CODEC_TYPE_VIDEO &&
- pkt->pkt_frametype == PKT_I_FRAME) {
-
- /* Switch out of commercial mode */
-
- de->de_ts_com_offset += (pkt->pkt_dts - de->de_ts_com_start);
- dvr_rec_set_state(de, DVR_RS_RUNNING, 0);
-
- tvhlog(LOG_INFO, "dvr",
- "%s - Skipped %" PRId64 " seconds of commercials",
- de->de_ititle, (pkt->pkt_dts - de->de_ts_com_start) / 90000);
- goto outputpacket;
- }
- break;
- }
-}
-
/**
*
*/
static void
dvr_thread_epilog(dvr_entry_t *de)
{
- AVFormatContext *fctx = de->de_fctx;
- dvr_rec_stream_t *drs;
- AVStream *st;
- int i;
-
- if(fctx == NULL)
- return;
-
- /* Write trailer if we've written anything at all */
-
- if(de->de_header_written)
- av_write_trailer(fctx);
-
- /* Close lavf streams and format */
-
- for(i = 0; i < fctx->nb_streams; i++) {
- st = fctx->streams[i];
- pthread_mutex_lock(&ffmpeg_lock);
- avcodec_close(st->codec);
- pthread_mutex_unlock(&ffmpeg_lock);
- free(st->codec);
- free(st);
- }
-
- url_fclose(fctx->pb);
- free(fctx);
-
- de->de_fctx = NULL;
-
- while((drs = LIST_FIRST(&de->de_streams)) != NULL) {
- LIST_REMOVE(drs, drs_link);
- free(drs);
- }
+ mk_mux_close(de->de_mkmux);
+ de->de_mkmux = NULL;
if(dvr_postproc)
dvr_spawn_postproc(de);
--- /dev/null
+/*****************************************************************************
+ * matroska_ebml.c:
+ *****************************************************************************
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111, USA.
+ *****************************************************************************/
+
+#include <assert.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include "ebml.h"
+
+void
+ebml_append_id(htsbuf_queue_t *q, uint32_t id)
+{
+ uint8_t u8[4] = {id >> 24, id >> 16, id >> 8, id};
+
+ if(u8[0])
+ return htsbuf_append(q, u8, 4);
+ if(u8[1])
+ return htsbuf_append(q, u8+1, 3);
+ if(u8[2])
+ return htsbuf_append(q, u8+2, 2);
+ return htsbuf_append(q, u8+3, 1);
+}
+
+void
+ebml_append_size(htsbuf_queue_t *q, uint32_t size)
+{
+ uint8_t u8[5] = { 0x08, size >> 24, size >> 16, size >> 8, size };
+
+ if(size < 0x7f) {
+ u8[4] |= 0x80;
+ return htsbuf_append(q, u8+4, 1);
+ }
+
+ if(size < 0x3fff) {
+ u8[3] |= 0x40;
+ return htsbuf_append(q, u8+3, 2);
+ }
+ if(size < 0x1fffff) {
+ u8[2] |= 0x20;
+ return htsbuf_append(q, u8+2, 3);
+ }
+ if(size < 0x0fffffff) {
+ u8[1] |= 0x10;
+ return htsbuf_append(q, u8+1, 4);
+ }
+ return htsbuf_append(q, u8, 5);
+}
+
+
+void
+ebml_append_bin(htsbuf_queue_t *q, unsigned id, const void *data, size_t len)
+{
+ ebml_append_id(q, id);
+ ebml_append_size(q, len);
+ return htsbuf_append(q, data, len);
+}
+
+void
+ebml_append_string(htsbuf_queue_t *q, unsigned id, const char *str)
+{
+ return ebml_append_bin(q, id, str, strlen(str));
+}
+
+void
+ebml_append_uint(htsbuf_queue_t *q, unsigned id, int64_t ui)
+{
+ uint8_t u8[8] = {ui >> 56, ui >> 48, ui >> 40, ui >> 32,
+ ui >> 24, ui >> 16, ui >> 8, ui };
+ int i = 0;
+ while( i < 7 && !u8[i] )
+ ++i;
+ return ebml_append_bin(q, id, u8 + i, 8 - i);
+}
+
+void
+ebml_append_float(htsbuf_queue_t *q, unsigned id, float f)
+{
+ union
+ {
+ float f;
+ unsigned u;
+ } u;
+ unsigned char c_f[4];
+
+ u.f = f;
+ c_f[0] = u.u >> 24;
+ c_f[1] = u.u >> 16;
+ c_f[2] = u.u >> 8;
+ c_f[3] = u.u;
+
+ return ebml_append_bin(q, id, c_f, 4);
+}
+
+void
+ebml_append_master(htsbuf_queue_t *q, uint32_t id, htsbuf_queue_t *p)
+{
+ ebml_append_id(q, id);
+ ebml_append_size(q, p->hq_size);
+ htsbuf_appendq(q, p);
+ free(p);
+}
+
+void
+ebml_append_void(htsbuf_queue_t *q)
+{
+ char n[1] = {0};
+ ebml_append_bin(q, 0xec, n, 1);
+}
+
+
+void
+ebml_append_pad(htsbuf_queue_t *q, size_t pad)
+{
+ assert(pad > 2);
+ pad -= 2;
+ assert(pad < 0x3fff);
+ pad -= pad > 0x7e;
+
+ void *data = alloca(pad);
+ memset(data, 0, pad);
+ ebml_append_bin(q, 0xec, data, pad);
+}
+
+
+void
+ebml_append_idid(htsbuf_queue_t *q, uint32_t id0, uint32_t id)
+{
+ uint8_t u8[4] = {id >> 24, id >> 16, id >> 8, id};
+
+ if(u8[0])
+ return ebml_append_bin(q, id0, u8, 4);
+ if(u8[1])
+ return ebml_append_bin(q, id0, u8+1, 3);
+ if(u8[2])
+ return ebml_append_bin(q, id0, u8+2, 2);
+ return ebml_append_bin(q, id0, u8+3, 1);
+}
--- /dev/null
+#ifndef EBML_H__
+#define EBML_H__
+
+#include "htsbuf.h"
+
+void ebml_append_id(htsbuf_queue_t *q, uint32_t id);
+
+void ebml_append_size(htsbuf_queue_t *q, uint32_t size);
+
+void ebml_append_bin(htsbuf_queue_t *q, unsigned id,
+ const void *data, size_t len);
+
+void ebml_append_string(htsbuf_queue_t *q, unsigned id, const char *str);
+
+void ebml_append_uint(htsbuf_queue_t *q, unsigned id, int64_t ui);
+
+void ebml_append_float(htsbuf_queue_t *q, unsigned id, float f);
+
+void ebml_append_master(htsbuf_queue_t *q, uint32_t id, htsbuf_queue_t *p);
+
+void ebml_append_void(htsbuf_queue_t *q);
+
+void ebml_append_pad(htsbuf_queue_t *q, size_t pad);
+
+void ebml_append_idid(htsbuf_queue_t *q, uint32_t id0, uint32_t id);
+
+#endif // EBML_H__
--- /dev/null
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <assert.h>
+
+#include "tvhead.h"
+#include "streaming.h"
+#include "dvr.h"
+#include "mkmux.h"
+#include "ebml.h"
+
+TAILQ_HEAD(mk_cue_queue, mk_cue);
+
+#define MATROSKA_TIMESCALE 1000000 // in nS
+
+
+static const AVRational mpeg_tc = {1, 90000};
+static const AVRational mkv_tc = {1, 1000};
+static const AVRational ns_tc = {1, 1000000000};
+
+/**
+ *
+ */
+typedef struct mk_track {
+ int index;
+ int enabled;
+ int merge;
+ int type;
+ int tracknum;
+ 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;
+ 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_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;
+
+ 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].index = ssc->ssc_index;
+ mkm->tracks[i].type = ssc->ssc_type;
+ mkm->tracks[i].nextpts = AV_NOPTS_VALUE;
+
+ 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_AAC:
+ tracktype = 2;
+ codec_id = "A_AAC";
+ break;
+
+ default:
+ continue;
+ }
+
+ mkm->tracks[i].enabled = 1;
+ mkm->tracks[i].tracknum = 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);
+
+ if(ssc->ssc_global_header_len) {
+ switch(ssc->ssc_type) {
+ case SCT_H264:
+ case SCT_MPEG2VIDEO:
+ case SCT_AAC:
+ ebml_append_bin(t, 0x63a2, ssc->ssc_global_header,
+ ssc->ssc_global_header_len);
+ break;
+ }
+ }
+
+ if(ssc->ssc_frameduration) {
+ int d = av_rescale_q(ssc->ssc_frameduration, mpeg_tc, ns_tc);
+ 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);
+
+ ebml_append_master(t, 0xe0, vi);
+ }
+ 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;
+ }
+
+ if(writev(mkm->fd, iov, i) != hq->hq_size) {
+ mkm->error = errno;
+ } else {
+ mkm->fdpos += hq->hq_size;
+ }
+}
+
+
+/**
+ *
+ */
+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(mk_mux_t *mkm)
+{
+ htsbuf_queue_t q;
+ char ff = 0xff;
+ htsbuf_queue_init(&q, 0);
+
+ ebml_append_id(&q, 0x18538067);
+ htsbuf_append(&q, &ff, 1);
+
+ 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 *t = htsbuf_queue_alloc(0);
+ htsbuf_queue_t *st = htsbuf_queue_alloc(0);
+
+ if(targettype == 0 && targettypename == NULL) {
+ ebml_append_void(t);
+ } else {
+ if(targettype)
+ ebml_append_uint(t, 0x68ca, targettype);
+ 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_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);
+
+ 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_title != NULL)
+ addtag(q, build_tag_string("SUMMARY", de->de_title, 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)
+{
+ 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->fd = fd;
+ mkm->title = strdup(de->de_title);
+ TAILQ_INIT(&mkm->cues);
+
+ mk_write_master(mkm, 0x1a45dfa3, mk_build_ebmlheader());
+ mk_write_segment(mkm);
+
+ 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));
+
+ 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 == AV_NOPTS_VALUE)
+ // This is our best guess, it might be wrong but... oh well
+ pts = t->nextpts;
+
+ if(pts != AV_NOPTS_VALUE) {
+ t->nextpts = pts + (pkt->pkt_duration >> pkt->pkt_field);
+
+ nxt = av_rescale_q(t->nextpts, mpeg_tc, mkv_tc);
+ pts = av_rescale_q(pts, mpeg_tc, mkv_tc);
+
+ 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 = pkt->pkt_payload;
+ len = pkt->pkt_payloadlen;
+
+ 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) {
+ if(t->merge)
+ pkt = pkt_merge_global(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)
+{
+ mk_close_cluster(mkm);
+ mk_write_cues(mkm);
+ mk_write_metaseek(mkm, 0);
+
+ // 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));
+
+ close(mkm->fd);
+ free(mkm->tracks);
+ free(mkm->title);
+ free(mkm);
+}
--- /dev/null
+/*
+ * Matroska muxer
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef MKMUX_H__
+#define MKMUX_H__
+
+typedef struct mk_mux mk_mux_t;
+
+struct streaming_start;
+struct dvr_entry;
+struct th_pkt;
+
+mk_mux_t *mk_mux_create(const char *filename,
+ const struct streaming_start *ss,
+ const struct dvr_entry *de);
+
+void mk_mux_write_pkt(mk_mux_t *mkm, struct th_pkt *pkt);
+
+void mk_mux_close(mk_mux_t *mk_mux);
+
+#endif // MKMUX_H__
--- /dev/null
+/**
+ * Global header modification
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <assert.h>
+#include "tvhead.h"
+#include "streaming.h"
+#include "globalheaders.h"
+#include "avc.h"
+
+typedef struct globalheaders {
+ streaming_target_t gh_input;
+
+ streaming_target_t *gh_output;
+
+ struct th_pktref_queue gh_holdq;
+
+ streaming_start_t *gh_ss;
+
+ int gh_passthru;
+
+} globalheaders_t;
+
+
+/**
+ *
+ */
+static void
+gh_flush(globalheaders_t *gh)
+{
+ if(gh->gh_ss != NULL)
+ streaming_start_unref(gh->gh_ss);
+ gh->gh_ss = NULL;
+
+ pktref_clear_queue(&gh->gh_holdq);
+}
+
+
+
+/**
+ *
+ */
+static void
+apply_header(streaming_start_component_t *ssc, th_pkt_t *pkt)
+{
+ uint8_t *d;
+ if(ssc->ssc_frameduration == 0 && pkt->pkt_duration != 0)
+ ssc->ssc_frameduration = pkt->pkt_duration;
+
+ if(ssc->ssc_global_header != NULL)
+ return;
+
+ switch(ssc->ssc_type) {
+ case SCT_AAC:
+ d = ssc->ssc_global_header = malloc(2);
+ ssc->ssc_global_header_len = 2;
+
+ const int profile = 2;
+ d[0] = (profile << 3) | ((pkt->pkt_sri & 0xe) >> 1);
+ d[1] = ((pkt->pkt_sri & 0x1) << 7) | (pkt->pkt_channels << 3);
+ break;
+
+ case SCT_H264:
+ case SCT_MPEG2VIDEO:
+ if(pkt->pkt_globaldata != NULL) {
+ ssc->ssc_global_header = malloc(pkt->pkt_globaldata_len +
+ FF_INPUT_BUFFER_PADDING_SIZE);
+
+ memcpy(ssc->ssc_global_header, pkt->pkt_globaldata,
+ pkt->pkt_globaldata_len);
+ ssc->ssc_global_header_len = pkt->pkt_globaldata_len;
+ }
+ break;
+
+ }
+
+}
+
+
+/**
+ *
+ */
+static int
+headers_complete(globalheaders_t *gh)
+{
+ streaming_start_t *ss = gh->gh_ss;
+ streaming_start_component_t *ssc;
+ int i;
+
+ assert(ss != NULL);
+
+ for(i = 0; i < ss->ss_num_components; i++) {
+ ssc = &ss->ss_components[i];
+
+ if((SCT_ISAUDIO(ssc->ssc_type) || SCT_ISVIDEO(ssc->ssc_type)) &&
+ ssc->ssc_frameduration == 0)
+ return 0;
+
+ if(ssc->ssc_global_header == NULL &&
+ (ssc->ssc_type == SCT_H264 ||
+ ssc->ssc_type == SCT_MPEG2VIDEO ||
+ ssc->ssc_type == SCT_AAC))
+ return 0;
+ }
+
+ return 1;
+}
+
+
+
+/**
+ *
+ */
+static th_pkt_t *
+convertpkt(streaming_start_component_t *ssc, th_pkt_t *pkt)
+{
+ switch(ssc->ssc_type) {
+ case SCT_H264:
+ // return avc_convert_pkt(pkt);
+
+ default:
+ return pkt;
+ }
+}
+
+
+/**
+ *
+ */
+static void
+gh_hold(globalheaders_t *gh, streaming_message_t *sm)
+{
+ th_pkt_t *pkt;
+ th_pktref_t *pr;
+ streaming_start_component_t *ssc;
+
+ switch(sm->sm_type) {
+ case SMT_PACKET:
+ pkt = sm->sm_data;
+ ssc = streaming_start_component_find_by_index(gh->gh_ss,
+ pkt->pkt_componentindex);
+ assert(ssc != NULL);
+
+ pkt = convertpkt(ssc, pkt);
+
+ apply_header(ssc, pkt);
+
+ pr = pktref_create(pkt);
+ TAILQ_INSERT_TAIL(&gh->gh_holdq, pr, pr_link);
+
+ free(sm);
+
+ if(!headers_complete(gh))
+ break;
+
+ // Send our modified start
+ sm = streaming_msg_create_data(SMT_START,
+ streaming_start_copy(gh->gh_ss));
+ streaming_target_deliver2(gh->gh_output, sm);
+
+ // Send all pending packets
+ while((pr = TAILQ_FIRST(&gh->gh_holdq)) != NULL) {
+ TAILQ_REMOVE(&gh->gh_holdq, pr, pr_link);
+ sm = streaming_msg_create_pkt(pr->pr_pkt);
+ streaming_target_deliver2(gh->gh_output, sm);
+ pkt_ref_dec(pr->pr_pkt);
+ free(pr);
+ }
+ gh->gh_passthru = 1;
+ break;
+
+ case SMT_START:
+ assert(gh->gh_ss == NULL);
+ gh->gh_ss = streaming_start_copy(sm->sm_data);
+ streaming_msg_free(sm);
+ break;
+
+ case SMT_STOP:
+ gh_flush(gh);
+ streaming_msg_free(sm);
+ break;
+
+ case SMT_EXIT:
+ case SMT_TRANSPORT_STATUS:
+ case SMT_NOSTART:
+ case SMT_MPEGTS:
+ streaming_target_deliver2(gh->gh_output, sm);
+ break;
+ }
+}
+
+
+/**
+ *
+ */
+static void
+gh_pass(globalheaders_t *gh, streaming_message_t *sm)
+{
+ th_pkt_t *pkt;
+ streaming_start_component_t *ssc;
+
+ switch(sm->sm_type) {
+ case SMT_START:
+ abort(); // Should not happen
+
+ case SMT_STOP:
+ gh->gh_passthru = 0;
+ gh_flush(gh);
+ // FALLTHRU
+ case SMT_EXIT:
+ case SMT_TRANSPORT_STATUS:
+ case SMT_NOSTART:
+ case SMT_MPEGTS:
+ streaming_target_deliver2(gh->gh_output, sm);
+ break;
+
+ case SMT_PACKET:
+ pkt = sm->sm_data;
+ ssc = streaming_start_component_find_by_index(gh->gh_ss,
+ pkt->pkt_componentindex);
+ sm->sm_data = convertpkt(ssc, pkt);
+ streaming_target_deliver2(gh->gh_output, sm);
+ break;
+ }
+}
+
+
+/**
+ *
+ */
+static void
+globalheaders_input(void *opaque, streaming_message_t *sm)
+{
+ globalheaders_t *gh = opaque;
+
+ if(gh->gh_passthru)
+ gh_pass(gh, sm);
+ else
+ gh_hold(gh, sm);
+}
+
+
+/**
+ *
+ */
+streaming_target_t *
+globalheaders_create(streaming_target_t *output)
+{
+ globalheaders_t *gh = calloc(1, sizeof(globalheaders_t));
+
+ TAILQ_INIT(&gh->gh_holdq);
+
+ gh->gh_output = output;
+ streaming_target_init(&gh->gh_input, globalheaders_input, gh, 0);
+ return &gh->gh_input;
+}
+
+
+/**
+ *
+ */
+void
+globalheaders_destroy(streaming_target_t *pad)
+{
+ globalheaders_t *gh = (globalheaders_t *)pad;
+ gh_flush(gh);
+ free(gh);
+}
+
--- /dev/null
+/**
+ * Global header modification
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GLOBALHEADERS_H__
+#define GLOBALHEADERS_H__
+
+#include "tvhead.h"
+
+streaming_target_t *globalheaders_create(streaming_target_t *output);
+
+void globalheaders_destroy(streaming_target_t *gh);
+
+
+#endif // GLOBALHEADERS_H__