]> git.ipfire.org Git - thirdparty/tvheadend.git/commitdiff
Add a native matroska muxer used for recording.
authorAndreas Öman <andreas@lonelycoder.com>
Mon, 21 Jun 2010 20:52:36 +0000 (20:52 +0000)
committerAndreas Öman <andreas@lonelycoder.com>
Mon, 21 Jun 2010 20:52:36 +0000 (20:52 +0000)
- Correctly writes global headers. Ticket #61
- Add support for recording AAC audio. Ticket #160

12 files changed:
Makefile
src/avc.c [new file with mode: 0644]
src/avc.h [new file with mode: 0644]
src/dvr/dvr.h
src/dvr/dvr_db.c
src/dvr/dvr_rec.c
src/dvr/ebml.c [new file with mode: 0644]
src/dvr/ebml.h [new file with mode: 0644]
src/dvr/mkmux.c [new file with mode: 0644]
src/dvr/mkmux.h [new file with mode: 0644]
src/plumbing/globalheaders.c [new file with mode: 0644]
src/plumbing/globalheaders.h [new file with mode: 0644]

index cc4525631e25a01a52ff7fa81efb7f8724457971..500afcb84f02c0961b58dafa2fee0964b7f90228 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -69,12 +69,16 @@ SRCS =  src/main.c \
        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 \
@@ -155,7 +159,7 @@ CURVERSION=$(shell cat ${BUILDDIR}/ver || echo "0")
 
 # 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)\"
 
diff --git a/src/avc.c b/src/avc.c
new file mode 100644 (file)
index 0000000..4418de7
--- /dev/null
+++ b/src/avc.c
@@ -0,0 +1,208 @@
+/*
+ * 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;
+}
+
diff --git a/src/avc.h b/src/avc.h
new file mode 100644 (file)
index 0000000..5b8c1ea
--- /dev/null
+++ b/src/avc.h
@@ -0,0 +1,37 @@
+/*
+ * 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 
index aec427d3158e7bfedc47c76fd17e6e968743092b..60df784065f3240513a16178cec1e1e217e586fa 100644 (file)
@@ -68,8 +68,7 @@ typedef enum {
 
 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,
@@ -154,21 +153,13 @@ typedef struct dvr_entry {
   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;
 
index 44892b5ac923177fb06298b6f7a1b32f8a37b1c6..2ffa3c34991025c60c08ee9bfd009dd6598fff13 100644 (file)
@@ -63,11 +63,9 @@ dvr_entry_status(dvr_entry_t *de)
 
     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:
@@ -373,6 +371,7 @@ dvr_entry_remove(dvr_entry_t *de)
 
   LIST_REMOVE(de, de_channel_link);
   LIST_REMOVE(de, de_global_link);
+  de->de_channel = NULL;
 
   dvrdb_changed();
 
index 1362e08ffa468e25345235874f85c36ce230cde0..1a35e2e38d784773368b6cea3c44d92278dee2a7 100644 (file)
 #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);
 
@@ -87,7 +76,9 @@ dvr_rec_subscribe(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);
@@ -109,6 +100,7 @@ dvr_rec_unsubscribe(dvr_entry_t *de, int stopcode)
   de->de_s = NULL;
 
   tsfix_destroy(de->de_tsfix);
+  globalheaders_destroy(de->de_gh);
 
   de->de_last_error = stopcode;
 }
@@ -247,12 +239,12 @@ pvr_generate_filename(dvr_entry_t *de)
 
   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++;
@@ -281,7 +273,7 @@ dvr_rec_fatal_error(dvr_entry_t *de, const char *fmt, ...)
   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);
 }
@@ -293,6 +285,8 @@ dvr_rec_fatal_error(dvr_entry_t *de, const char *fmt, ...)
 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)
@@ -307,16 +301,7 @@ static void
 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) {
@@ -324,113 +309,13 @@ dvr_rec_start(dvr_entry_t *de, const streaming_start_t *ss)
     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\", "
@@ -441,6 +326,20 @@ dvr_rec_start(dvr_entry_t *de, const streaming_start_t *ss)
         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);
+  }
 }
 
 
@@ -454,7 +353,6 @@ dvr_thread(void *aux)
   streaming_queue_t *sq = &de->de_sq;
   streaming_message_t *sm;
   int run = 1;
-  th_pkt_t *pkt;
 
   pthread_mutex_lock(&sq->sq_mutex);
 
@@ -471,22 +369,17 @@ dvr_thread(void *aux)
 
     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;
 
@@ -498,7 +391,7 @@ dvr_thread(void *aux)
        de->de_last_error = 0;
 
        tvhlog(LOG_INFO, 
-              "pvr", "Recording completed: \"%s\"",
+              "dvr", "Recording completed: \"%s\"",
               de->de_filename ?: de->de_title);
 
       } else {
@@ -507,7 +400,7 @@ dvr_thread(void *aux)
          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));
        }
@@ -533,7 +426,7 @@ dvr_thread(void *aux)
        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));
        }
@@ -546,7 +439,7 @@ dvr_thread(void *aux)
        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));
       }
@@ -568,192 +461,6 @@ dvr_thread(void *aux)
 }
 
 
-/** 
- * 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;
-  }
-}
-
 /**
  *
  */
@@ -811,39 +518,8 @@ dvr_spawn_postproc(dvr_entry_t *de)
 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);
diff --git a/src/dvr/ebml.c b/src/dvr/ebml.c
new file mode 100644 (file)
index 0000000..da2ee9a
--- /dev/null
@@ -0,0 +1,155 @@
+/*****************************************************************************
+ * 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);
+}
diff --git a/src/dvr/ebml.h b/src/dvr/ebml.h
new file mode 100644 (file)
index 0000000..22b0b70
--- /dev/null
@@ -0,0 +1,27 @@
+#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__
diff --git a/src/dvr/mkmux.c b/src/dvr/mkmux.c
new file mode 100644 (file)
index 0000000..223fca5
--- /dev/null
@@ -0,0 +1,725 @@
+/*
+ *  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);
+}
diff --git a/src/dvr/mkmux.h b/src/dvr/mkmux.h
new file mode 100644 (file)
index 0000000..bb75576
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+ *  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__
diff --git a/src/plumbing/globalheaders.c b/src/plumbing/globalheaders.c
new file mode 100644 (file)
index 0000000..2677d19
--- /dev/null
@@ -0,0 +1,283 @@
+/**
+ *  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);
+}
+
diff --git a/src/plumbing/globalheaders.h b/src/plumbing/globalheaders.h
new file mode 100644 (file)
index 0000000..160ee12
--- /dev/null
@@ -0,0 +1,29 @@
+/**
+ *  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__