]> git.ipfire.org Git - thirdparty/tvheadend.git/commitdiff
dvr: added initial support for libavformat muxing
authorJohn Törnblom <john.tornblom@gmail.com>
Mon, 7 Jan 2013 18:52:48 +0000 (19:52 +0100)
committerJohn Törnblom <john.tornblom@gmail.com>
Mon, 7 Jan 2013 18:52:48 +0000 (19:52 +0100)
Makefile
configure
src/libav.c [new file with mode: 0644]
src/libav.h [new file with mode: 0644]
src/main.c
src/muxer.c
src/muxer.h
src/muxer/muxer_libav.c [new file with mode: 0644]
src/muxer/muxer_libav.h [new file with mode: 0644]
src/webui/extjs.c
src/webui/static/app/dvr.js

index d88413977a7d43b4b2855102d96900f9ea33222e..d8d6197e0f741deabadd3b1be116446977c9c161 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -171,6 +171,10 @@ SRCS-${CONFIG_V4L} += \
 # Avahi
 SRCS-$(CONFIG_AVAHI) += src/avahi.c
 
+# libav
+SRCS-$(CONFIG_LIBAV) += src/libav.c \
+       src/muxer/muxer_libav.c
+
 # CWC
 SRCS-${CONFIG_CWC} += src/cwc.c \
        src/capmt.c
index 765a98987818e28e8bb9f5c7e33a4abd7e407f57..5a5ce0ee639d934f2d19174cc7e6e53274ebb54c 100755 (executable)
--- a/configure
+++ b/configure
@@ -23,6 +23,7 @@ OPTIONS=(
   "imagecache:auto"
   "avahi:auto"
   "zlib:auto"
+  "libav:auto"
   "bundle:no"
   "dvbcsa:no"
 )
@@ -100,6 +101,32 @@ if enabled_or_auto avahi; then
   fi
 fi
 
+#
+# libav
+#
+if enabled_or_auto libav; then
+  has_libav=true
+
+  if $has_libav && ! check_pkg libavutil ">=50.43.0"; then
+    has_libav=false
+  fi
+
+  if $has_libav && ! check_pkg libavformat "<=55.0.0"; then
+    has_libav=false
+  fi
+
+  if $has_libav && ! check_pkg libavformat ">=50.43.0"; then
+    has_libav=false
+  fi
+
+  if $has_libav; then
+    enable libav
+  elif enabled libav; then
+    die "libav development support not found (use --disable-libav)"
+  fi
+fi
+
+
 #
 # DVB scan
 #
diff --git a/src/libav.c b/src/libav.c
new file mode 100644 (file)
index 0000000..e867eea
--- /dev/null
@@ -0,0 +1,104 @@
+#include "libav.h"
+
+/**
+ *
+ */
+static void
+libav_log_callback(void *ptr, int level, const char *fmt, va_list vl)
+{
+    char message[8192];
+    char *nl;
+    char *l;
+
+    memset(message, 0, sizeof(message));
+    vsnprintf(message, sizeof(message), fmt, vl);
+
+    l = message;
+
+    if(level == AV_LOG_DEBUG)
+      level = LOG_DEBUG;
+    else if(level == AV_LOG_VERBOSE)
+      level = LOG_INFO;
+    else if(level == AV_LOG_INFO)
+      level = LOG_NOTICE;
+    else if(level == AV_LOG_WARNING)
+      level = LOG_WARNING;
+    else if(level == AV_LOG_ERROR)
+      level = LOG_ERR;
+    else if(level == AV_LOG_FATAL)
+      level = LOG_CRIT;
+    else if(level == AV_LOG_PANIC)
+      level = LOG_EMERG;
+
+    while(l < message + sizeof(message)) {
+      nl = strstr(l, "\n");
+      if(nl)
+       *nl = '\0';
+
+      if(!strlen(l))
+       break;
+
+      tvhlog(level, "libav", "%s", l);
+
+      l += strlen(message);
+
+      if(!nl)
+       break;
+    }
+}
+
+/**
+ * Translate a component type to a libavcodec id
+ */
+enum CodecID
+streaming_component_type2codec_id(streaming_component_type_t type)
+{
+  enum CodecID codec_id = CODEC_ID_NONE;
+
+  switch(type) {
+  case SCT_H264:
+    codec_id = CODEC_ID_H264;
+    break;
+  case SCT_MPEG2VIDEO:
+    codec_id = CODEC_ID_MPEG2VIDEO;
+    break;
+  case SCT_AC3:
+    codec_id = CODEC_ID_AC3;
+    break;
+  case SCT_EAC3:
+    codec_id = CODEC_ID_EAC3;
+    break;
+  case SCT_AAC:
+    codec_id = CODEC_ID_AAC;
+    break;
+  case SCT_MPEG2AUDIO:
+    codec_id = CODEC_ID_MP2;
+    break;
+  case SCT_DVBSUB:
+    codec_id = CODEC_ID_DVB_SUBTITLE;
+    break;
+  case SCT_TEXTSUB:
+    codec_id = CODEC_ID_TEXT;
+    break;
+ case SCT_TELETEXT:
+    codec_id = CODEC_ID_DVB_TELETEXT;
+    break;
+  default:
+    codec_id = CODEC_ID_NONE;
+    break;
+  }
+
+  return codec_id;
+}
+
+/**
+ * 
+ */ 
+void
+libav_init(void)
+{
+  av_log_set_callback(libav_log_callback);
+  av_log_set_level(AV_LOG_VERBOSE);
+  av_register_all();
+}
+
diff --git a/src/libav.h b/src/libav.h
new file mode 100644 (file)
index 0000000..c8a6ed7
--- /dev/null
@@ -0,0 +1,31 @@
+/*
+ *  tvheadend, libav utils
+ *  Copyright (C) 2012 John Törnblom
+ *
+ *  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 <htmlui://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIBAV_H_
+#define LIBAV_H_
+
+
+#include <libavformat/avformat.h>
+#include "tvheadend.h"
+
+enum CodecID streaming_component_type2codec_id(streaming_component_type_t type);
+
+void libav_init(void);
+
+#endif
+
index 704f6e4b400b8518ba93fc33f18c121a5b286a9c..f0a58be50169e19cafd953f338bee4df8d7478ce 100644 (file)
@@ -61,6 +61,9 @@
 #include "muxes.h"
 #include "config2.h"
 #include "imagecache.h"
+#if ENABLE_LIBAV
+#include "libav.h"
+#endif
 
 int running;
 time_t dispatch_clock;
@@ -466,6 +469,10 @@ main(int argc, char **argv)
    * Initialize subsystems
    */
 
+#if ENABLE_LIBAV
+  libav_init();
+#endif
+
   config_init();
 
   imagecache_init();
index 6ec3a8a18879a45e2c7a85d19c57eb569763fb74..2de07edf9dce25ac0c9e6d92137253bbc53d2145 100644 (file)
@@ -23,7 +23,9 @@
 #include "muxer.h"
 #include "muxer/muxer_tvh.h"
 #include "muxer/muxer_pass.h"
-
+#if CONFIG_LIBAV
+#include "muxer/muxer_libav.h"
+#endif
 
 /**
  * Mime type for containers containing only audio
@@ -140,6 +142,45 @@ muxer_container_type2txt(muxer_container_type_t mc)
 }
 
 
+/**
+ * Get a list of supported containers
+ */
+int
+muxer_container_list(htsmsg_t *array)
+{
+  htsmsg_t *mc;
+  int c = 0;
+
+  mc = htsmsg_create_map();
+  htsmsg_add_str(mc, "name",        muxer_container_type2txt(MC_MATROSKA));
+  htsmsg_add_str(mc, "description", "Matroska");
+  htsmsg_add_msg(array, NULL, mc);
+  c++;
+  
+  mc = htsmsg_create_map();
+  htsmsg_add_str(mc, "name",        muxer_container_type2txt(MC_PASS));
+  htsmsg_add_str(mc, "description", "Same as source (pass through)");
+  htsmsg_add_msg(array, NULL, mc);
+  c++;
+
+#if ENABLE_LIBAV
+  mc = htsmsg_create_map();
+  htsmsg_add_str(mc, "name",        muxer_container_type2txt(MC_MPEGTS));
+  htsmsg_add_str(mc, "description", "MPEG-TS");
+  htsmsg_add_msg(array, NULL, mc);
+  c++;
+
+  mc = htsmsg_create_map();
+  htsmsg_add_str(mc, "name",        muxer_container_type2txt(MC_MPEGPS));
+  htsmsg_add_str(mc, "description", "MPEG-PS (DVD)");
+  htsmsg_add_msg(array, NULL, mc);
+  c++;
+#endif
+
+  return c;
+}
+
+
 /**
  * Convert a container name to a container type
  */
@@ -194,6 +235,11 @@ muxer_create(muxer_container_type_t mc)
   if(!m)
     m = tvh_muxer_create(mc);
 
+#if CONFIG_LIBAV
+  if(!m)
+    m = lav_muxer_create(mc);
+#endif
+
   if(!m)
     tvhlog(LOG_ERR, "mux", "Can't find a muxer that supports '%s' container",
           muxer_container_type2txt(mc));
index 766c5dd4239d021d9b707ce5dc37770aa688fb23..6d57cd7f71cd59edc442a39254788167659eec41 100644 (file)
@@ -19,6 +19,8 @@
 #ifndef MUXER_H_
 #define MUXER_H_
 
+#include "htsmsg.h"
+
 typedef enum {
   MC_UNKNOWN     = 0,
   MC_MATROSKA    = 1,
@@ -65,6 +67,8 @@ muxer_container_type_t muxer_container_mime2type (const char *str);
 
 const char*            muxer_container_suffix(muxer_container_type_t mc, int video);
 
+int muxer_container_list(htsmsg_t *array);
+
 // Muxer factory
 muxer_t *muxer_create(muxer_container_type_t mc);
 
diff --git a/src/muxer/muxer_libav.c b/src/muxer/muxer_libav.c
new file mode 100644 (file)
index 0000000..7837afe
--- /dev/null
@@ -0,0 +1,508 @@
+/*
+ *  tvheadend, libavformat based muxer
+ *  Copyright (C) 2012 John Törnblom
+ *
+ *  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 <htmlui://www.gnu.org/licenses/>.
+ */
+
+#include <assert.h>
+#include <unistd.h>
+#include <libavformat/avformat.h>
+#include <libavutil/mathematics.h>
+
+#include "tvheadend.h"
+#include "streaming.h"
+#include "epg.h"
+#include "channels.h"
+#include "libav.h"
+#include "muxer_libav.h"
+
+typedef struct lav_muxer {
+  muxer_t;
+  AVFormatContext *lm_oc;
+  AVBitStreamFilterContext *lm_h264_filter;
+  int lm_fd;
+} lav_muxer_t;
+
+#define MUX_BUF_SIZE 4096
+
+static const AVRational mpeg_tc = {1, 90000};
+
+
+/**
+ * Callback function for libavformat
+ */
+static int 
+lav_muxer_write(void *opaque, uint8_t *buf, int buf_size)
+{
+  int r;
+  lav_muxer_t *lm = (lav_muxer_t*)opaque;
+  
+  r = write(lm->lm_fd, buf, buf_size);
+  lm->m_errors += (r != buf_size);
+  
+  return r;
+}
+
+
+/**
+ * Add a stream to the muxer
+ */
+static int
+lav_muxer_add_stream(lav_muxer_t *lm, 
+                    const streaming_start_component_t *ssc)
+{
+  AVStream *st;
+  AVCodecContext *c;
+
+  st = avformat_new_stream(lm->lm_oc, NULL);
+  if (!st)
+    return -1;
+
+  st->id = ssc->ssc_index;
+  c = st->codec;
+  c->codec_id = streaming_component_type2codec_id(ssc->ssc_type);
+
+  switch(lm->m_container) {
+  case MC_MATROSKA:
+    st->time_base.num = 1000000;
+    st->time_base.den = 1;
+    break;
+
+  case MC_MPEGPS:
+    c->rc_buffer_size = 224*1024*8;
+    //Fall-through
+  case MC_MPEGTS:
+    st->time_base.num = 90000;
+    st->time_base.den = 1;
+    break;
+
+  default:
+    st->time_base = AV_TIME_BASE_Q;
+    break;
+  }
+
+
+
+  if(ssc->ssc_gh) {
+    c->extradata_size = pktbuf_len(ssc->ssc_gh);
+    c->extradata = av_malloc(c->extradata_size);
+    memcpy(c->extradata, pktbuf_ptr(ssc->ssc_gh), 
+          pktbuf_len(ssc->ssc_gh));
+  }
+
+  if(SCT_ISAUDIO(ssc->ssc_type)) {
+    c->codec_type    = AVMEDIA_TYPE_AUDIO;
+    c->sample_fmt    = AV_SAMPLE_FMT_S16;
+
+    c->sample_rate   = sri_to_rate(ssc->ssc_sri);
+    c->channels      = ssc->ssc_channels;
+
+    c->time_base.num = 1;
+    c->time_base.den = c->sample_rate;
+
+    av_dict_set(&st->metadata, "language", ssc->ssc_lang, 0);
+
+  } else if(SCT_ISVIDEO(ssc->ssc_type)) {
+    c->codec_type = AVMEDIA_TYPE_VIDEO;
+    c->width      = ssc->ssc_width;
+    c->height     = ssc->ssc_height;
+
+    c->time_base.num  = 1;
+    c->time_base.den = 25;
+
+    c->sample_aspect_ratio.num = ssc->ssc_aspect_num;
+    c->sample_aspect_ratio.den = ssc->ssc_aspect_den;
+
+    st->sample_aspect_ratio.num = c->sample_aspect_ratio.num;
+    st->sample_aspect_ratio.den = c->sample_aspect_ratio.den;
+
+  } else if(SCT_ISSUBTITLE(ssc->ssc_type)) {
+    c->codec_type = AVMEDIA_TYPE_SUBTITLE;
+    av_dict_set(&st->metadata, "language", ssc->ssc_lang, 0);
+  }
+
+  if(lm->lm_oc->oformat->flags & AVFMT_GLOBALHEADER)
+    c->flags |= CODEC_FLAG_GLOBAL_HEADER;
+
+  return 0;
+}
+
+
+/**
+ * Check if a container supports a given streaming component
+ */
+static int
+lav_muxer_support_stream(muxer_container_type_t mc, 
+                        streaming_component_type_t type)
+{
+  int ret = 0;
+
+  switch(mc) {
+  case MC_MATROSKA:
+    ret |= SCT_ISAUDIO(type);
+    ret |= SCT_ISVIDEO(type);
+    ret |= SCT_ISSUBTITLE(type);
+    break;
+
+  case MC_MPEGTS:
+    ret |= (type == SCT_MPEG2VIDEO);
+    ret |= (type == SCT_H264);
+
+    ret |= (type == SCT_MPEG2AUDIO);
+    ret |= (type == SCT_AC3);
+    ret |= (type == SCT_AAC);
+    ret |= (type == SCT_MP4A);
+    ret |= (type == SCT_EAC3);
+
+    //Some pids lack pts, disable for now
+    //ret |= (type == SCT_TELETEXT);
+    ret |= (type == SCT_DVBSUB);
+    break;
+
+  case MC_MPEGPS:
+    ret |= (type == SCT_MPEG2VIDEO);
+    ret |= (type == SCT_MPEG2AUDIO);
+    ret |= (type == SCT_AC3);
+
+  default:
+    break;
+  }
+
+  return ret;
+}
+
+
+/**
+ * Figure out the mime-type for the muxed data stream
+ */
+static const char*
+lav_muxer_mime(muxer_t* m, const struct streaming_start *ss)
+{
+  int i;
+  int has_audio;
+  int has_video;
+  const streaming_start_component_t *ssc;
+  
+  has_audio = 0;
+  has_video = 0;
+
+  for(i=0; i < ss->ss_num_components; i++) {
+    ssc = &ss->ss_components[i];
+
+    if(ssc->ssc_disabled)
+      continue;
+
+    if(!lav_muxer_support_stream(m->m_container, ssc->ssc_type))
+      continue;
+
+    has_video |= SCT_ISVIDEO(ssc->ssc_type);
+    has_audio |= SCT_ISAUDIO(ssc->ssc_type);
+  }
+
+  if(has_video)
+    return muxer_container_type2mime(m->m_container, 1);
+  else if(has_audio)
+    return muxer_container_type2mime(m->m_container, 0);
+  else
+    return muxer_container_type2mime(MC_UNKNOWN, 0);
+}
+
+
+/**
+ * Init the muxer with streams
+ */
+static int
+lav_muxer_init(muxer_t* m, const struct streaming_start *ss, const char *name)
+{
+  int i;
+  const streaming_start_component_t *ssc;
+  AVFormatContext *oc;
+  lav_muxer_t *lm = (lav_muxer_t*)m;
+  char app[128];
+
+  snprintf(app, sizeof(app), "Tvheadend %s", tvheadend_version);
+
+  oc = lm->lm_oc;
+
+  av_dict_set(&oc->metadata, "title", name, 0);
+  av_dict_set(&oc->metadata, "service_name", name, 0);
+  av_dict_set(&oc->metadata, "service_provider", app, 0);
+
+  if(lm->m_container == MC_MPEGTS)
+    lm->lm_h264_filter = av_bitstream_filter_init("h264_mp4toannexb");
+
+  oc->max_delay = 0.7 * AV_TIME_BASE;
+
+  for(i=0; i < ss->ss_num_components; i++) {
+    ssc = &ss->ss_components[i];
+
+    if(ssc->ssc_disabled)
+      continue;
+
+    if(!lav_muxer_support_stream(lm->m_container, ssc->ssc_type)) {
+      tvhlog(LOG_WARNING, "libav",  "%s is not supported in %s", 
+            streaming_component_type2txt(ssc->ssc_type), 
+            muxer_container_type2txt(lm->m_container));
+      continue;
+    }
+
+    if(lav_muxer_add_stream(lm, ssc)) {
+      tvhlog(LOG_ERR, "libav",  "Failed to add %s stream", 
+            streaming_component_type2txt(ssc->ssc_type));
+      continue;
+    }
+  }
+
+  if(!lm->lm_oc->nb_streams) {
+    tvhlog(LOG_ERR, "libav",  "No supported streams available");
+    lm->m_errors++;
+    return -1;
+  } else if(avformat_write_header(lm->lm_oc, NULL) < 0) {
+    tvhlog(LOG_ERR, "libav",  "Failed to write %s header", 
+          muxer_container_type2txt(lm->m_container));
+    lm->m_errors++;
+    return -1;
+  }
+
+  return 0;
+}
+
+
+/**
+ * Handle changes to the streams (usually PMT updates)
+ */
+static int
+lav_muxer_reconfigure(muxer_t* m, const struct streaming_start *ss)
+{
+  lav_muxer_t *lm = (lav_muxer_t*)m;
+
+  lm->m_errors++;
+
+  return -1;
+}
+
+
+/**
+ * Open the muxer and write the header
+ */
+static int
+lav_muxer_open_stream(muxer_t *m, int fd)
+{
+  uint8_t *buf;
+  AVIOContext *pb;
+  lav_muxer_t *lm = (lav_muxer_t*)m;
+
+  buf = av_malloc(MUX_BUF_SIZE);
+  pb = avio_alloc_context(buf, MUX_BUF_SIZE, 1, lm, NULL, 
+                         lav_muxer_write, NULL);
+  pb->seekable = 0;
+  lm->lm_oc->pb = pb;
+  lm->lm_fd = fd;
+
+  return 0;
+}
+
+
+static int
+lav_muxer_open_file(muxer_t *m, const char *filename)
+{
+  AVFormatContext *oc;
+  lav_muxer_t *lm = (lav_muxer_t*)m;
+
+  oc = lm->lm_oc;
+  snprintf(oc->filename, sizeof(oc->filename), "%s", filename);
+
+  if(avio_open(&oc->pb, filename, AVIO_FLAG_WRITE) < 0) {
+    tvhlog(LOG_ERR, "libav",  "Could not open %s", filename);
+    lm->m_errors++;
+    return -1;
+  }
+
+  return 0;
+}
+
+
+/**
+ * Write a packet to the muxer
+ */
+static int
+lav_muxer_write_pkt(muxer_t *m, streaming_message_type_t smt, void *data)
+{
+  int i;
+  AVFormatContext *oc;
+  AVStream *st;
+  AVPacket packet;
+  th_pkt_t *pkt = (th_pkt_t*)data;
+  lav_muxer_t *lm = (lav_muxer_t*)m;
+
+  assert(smt == SMT_PACKET);
+
+  oc = lm->lm_oc;
+
+  if(!oc->nb_streams) {
+    tvhlog(LOG_ERR, "libav",  "No streams to mux");
+    lm->m_errors++;
+    return -1;
+  }
+
+  for(i=0; i<oc->nb_streams; i++) {
+    st = oc->streams[i];
+
+    if(st->id != pkt->pkt_componentindex)
+      continue;
+
+    av_init_packet(&packet);
+
+    if(st->codec->codec_id == CODEC_ID_MPEG2VIDEO)
+      pkt = pkt_merge_header(pkt);
+
+    if(lm->lm_h264_filter && st->codec->codec_id == CODEC_ID_H264) {
+         av_bitstream_filter_filter(lm->lm_h264_filter, 
+                                   st->codec, 
+                                   NULL, 
+                                   &packet.data, 
+                                   &packet.size, 
+                                   pktbuf_ptr(pkt->pkt_payload), 
+                                   pktbuf_len(pkt->pkt_payload), 
+                                   pkt->pkt_frametype < PKT_P_FRAME);
+    } else {
+      packet.data = pktbuf_ptr(pkt->pkt_payload);
+      packet.size = pktbuf_len(pkt->pkt_payload);
+    }
+
+    packet.stream_index = st->index;
+    packet.pts      = av_rescale_q(pkt->pkt_pts     , mpeg_tc, st->time_base);
+    packet.dts      = av_rescale_q(pkt->pkt_dts     , mpeg_tc, st->time_base);
+    packet.duration = av_rescale_q(pkt->pkt_duration, mpeg_tc, st->time_base);
+
+    if(pkt->pkt_frametype < PKT_P_FRAME)
+      packet.flags |= AV_PKT_FLAG_KEY;
+
+    if (av_interleaved_write_frame(oc, &packet) != 0) {
+        tvhlog(LOG_WARNING, "libav",  "Failed to write frame");
+       lm->m_errors++;
+       return -1;
+    }
+
+    break;
+  }
+
+  pkt_ref_dec(pkt);
+
+  return 0;
+}
+
+
+/**
+ * NOP
+ */
+static int
+lav_muxer_write_meta(muxer_t *m, struct epg_broadcast *eb)
+{
+  return 0;
+}
+
+
+/**
+ * Close the muxer and append trailer to output
+ */
+static int
+lav_muxer_close(muxer_t *m)
+{
+  int i;
+  int ret = 0;
+  lav_muxer_t *lm = (lav_muxer_t*)m;
+
+  if(lm->lm_oc->nb_streams && av_write_trailer(lm->lm_oc) < 0) {
+    tvhlog(LOG_WARNING, "libav",  "Failed to write %s trailer", 
+          muxer_container_type2txt(lm->m_container));
+    lm->m_errors++;
+    ret = -1;
+  }
+
+  if(lm->lm_h264_filter)
+    av_bitstream_filter_close(lm->lm_h264_filter);
+
+  for(i=0; i<lm->lm_oc->nb_streams; i++)
+    av_freep(&lm->lm_oc->streams[i]->codec->extradata);
+  lm->lm_oc->nb_streams = 0;
+
+  return ret;
+}
+
+
+/**
+ * Free all memory associated with the muxer
+ */
+static void
+lav_muxer_destroy(muxer_t *m)
+{
+  lav_muxer_t *lm = (lav_muxer_t*)m;
+
+  if(lm->lm_oc && lm->lm_oc->pb)
+    av_free(lm->lm_oc->pb);
+
+  if(lm->lm_oc)
+    av_free(lm->lm_oc);
+
+  free(lm);
+}
+
+
+/**
+ * Create a new libavformat based muxer
+ */
+muxer_t*
+lav_muxer_create(muxer_container_type_t mc)
+{
+  const char *mux_name;
+  lav_muxer_t *lm;
+  AVOutputFormat *fmt;
+
+  switch(mc) {
+  case MC_MPEGPS:
+    mux_name = "dvd";
+    break;
+  default:
+    mux_name = muxer_container_type2txt(mc);
+    break;
+  }
+
+  fmt = av_guess_format(mux_name, NULL, NULL);
+  if(!fmt) {
+    tvhlog(LOG_ERR, "libav",  "Can't find the '%s' muxer", mux_name);
+    return NULL;
+  }
+
+  lm = calloc(1, sizeof(lav_muxer_t));
+  lm->m_open_stream  = lav_muxer_open_stream;
+  lm->m_open_file    = lav_muxer_open_file;
+  lm->m_init         = lav_muxer_init;
+  lm->m_reconfigure  = lav_muxer_reconfigure;
+  lm->m_mime         = lav_muxer_mime;
+  lm->m_write_meta   = lav_muxer_write_meta;
+  lm->m_write_pkt    = lav_muxer_write_pkt;
+  lm->m_close        = lav_muxer_close;
+  lm->m_destroy      = lav_muxer_destroy;
+  lm->m_container    = mc;
+  lm->lm_oc          = avformat_alloc_context();
+  lm->lm_oc->oformat = fmt;
+  lm->lm_fd          = -1;
+
+  return (muxer_t*)lm;
+}
+
diff --git a/src/muxer/muxer_libav.h b/src/muxer/muxer_libav.h
new file mode 100644 (file)
index 0000000..a8325b0
--- /dev/null
@@ -0,0 +1,26 @@
+/*
+ *  tvheadend, muxing of packets with libavformat
+ *  Copyright (C) 2012 John Törnblom
+ *
+ *  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 <htmlui://www.gnu.org/licenses/>.
+ */
+
+#ifndef LAV_MUXER_H_
+#define LAV_MUXER_H_
+
+#include "muxer.h"
+
+muxer_t* lav_muxer_create(muxer_container_type_t mc);
+
+#endif
index 88496e6f19cb8f8ea71ca63e780398c97673f46b..b7f98f436339beb4641336290e5511c308792e92 100644 (file)
@@ -736,6 +736,47 @@ skip:
 
 }
 
+
+/**
+ *
+ */
+static int
+extjs_dvr_containers(http_connection_t *hc, const char *remain, void *opaque)
+{
+  htsbuf_queue_t *hq = &hc->hc_reply;
+  const char *op = http_arg_get(&hc->hc_req_args, "op");
+  htsmsg_t *out, *array;
+
+  pthread_mutex_lock(&global_lock);
+
+  if(op != NULL && !strcmp(op, "list")) {
+
+    out = htsmsg_create_map();
+    array = htsmsg_create_list();
+
+    if (http_access_verify(hc, ACCESS_RECORDER_ALL))
+      goto skip;
+
+    muxer_container_list(array);
+
+skip:
+    htsmsg_add_msg(out, "entries", array);
+
+  } else {
+    pthread_mutex_unlock(&global_lock);
+    return HTTP_STATUS_BAD_REQUEST;
+  }
+
+  pthread_mutex_unlock(&global_lock);
+
+  htsmsg_json_serialize(out, hq, 0);
+  htsmsg_destroy(out);
+  http_output_content(hc, "text/x-json; charset=UTF-8");
+  return 0;
+
+}
+
+
 /**
  *
  */
@@ -2018,6 +2059,7 @@ extjs_start(void)
   http_path_add("/dvrlist_upcoming", NULL, extjs_dvrlist_upcoming, ACCESS_WEB_INTERFACE);
   http_path_add("/dvrlist_finished", NULL, extjs_dvrlist_finished, ACCESS_WEB_INTERFACE);
   http_path_add("/dvrlist_failed",   NULL, extjs_dvrlist_failed,   ACCESS_WEB_INTERFACE);
+  http_path_add("/dvr_containers",   NULL, extjs_dvr_containers,   ACCESS_WEB_INTERFACE);
   http_path_add("/subscriptions",    NULL, extjs_subscriptions,    ACCESS_WEB_INTERFACE);
   http_path_add("/ecglist",          NULL, extjs_ecglist,          ACCESS_WEB_INTERFACE);
   http_path_add("/config",           NULL, extjs_config,           ACCESS_WEB_INTERFACE);
index fa7d40416624cc7b3160a30b7a547958fb2234dc..215fdf6685ab988bb29dea0ec25b1ad023f2d422 100644 (file)
@@ -14,13 +14,20 @@ tvheadend.dvrprio = new Ext.data.SimpleStore({
                [ 'unimportant', 'Unimportant' ] ]
 });
 
+
 //For the container configuration
-tvheadend.containers = new Ext.data.SimpleStore({
-       fields : [ 'identifier', 'name' ],
-       id : 0,
-       data : [ [ 'matroska', 'Matroska' ], [ 'pass', 'TS (Pass-through)' ] ]
+tvheadend.containers = new Ext.data.JsonStore({
+       autoLoad : true,
+       root : 'entries',
+       fields : [ 'name', 'description' ],
+       id : 'name',
+       url : 'dvr_containers',
+       baseParams : {
+               op : 'list'
+       }
 });
 
+
 /**
  * Configuration names
  */
@@ -743,11 +750,11 @@ tvheadend.dvrsettings = function() {
                }, new Ext.form.ComboBox({
                        store : tvheadend.containers,
                        fieldLabel : 'Media container',
-                       mode : 'local',
                        triggerAction : 'all',
-                       displayField : 'name',
-                       valueField : 'identifier',
+                       displayField : 'description',
+                       valueField : 'name',
                        editable : false,
+                       width : 200,
                        hiddenName : 'container'
                }), new Ext.form.NumberField({
                        allowNegative : false,