]> git.ipfire.org Git - thirdparty/tvheadend.git/commitdiff
Added initial support for transcoding.
authorJohn Törblom <john.tornblom@gmail.com>
Thu, 9 May 2013 19:51:27 +0000 (21:51 +0200)
committerJohn Törblom <john.tornblom@gmail.com>
Sun, 12 May 2013 12:47:05 +0000 (14:47 +0200)
Makefile
configure
src/htsp_server.c
src/libav.c
src/libav.h
src/main.c
src/plumbing/transcoding.c [new file with mode: 0644]
src/plumbing/transcoding.h [new file with mode: 0644]
src/psi.c
src/tvheadend.h
src/webui/webui.c

index 00ee41470c2c2e80eaf9350bc769f823e3dbf3c3..b3a6a7ae080791751faab2ebb84a40a5ca7241f1 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -189,7 +189,8 @@ SRCS-$(CONFIG_AVAHI) += src/avahi.c
 
 # libav
 SRCS-$(CONFIG_LIBAV) += src/libav.c \
-       src/muxer/muxer_libav.c
+       src/muxer/muxer_libav.c \
+       src/plumbing/transcoding.c \
 
 # CWC
 SRCS-${CONFIG_CWC} += src/cwc.c \
index 198e4d1bd0fa2d833b549531a245098ad035fcbf..fcec9de43eca4ef505128df166efac068268b78a 100755 (executable)
--- a/configure
+++ b/configure
@@ -137,6 +137,10 @@ if enabled_or_auto libav; then
     has_libav=false
   fi
 
+  if $has_libav && ! check_pkg libswscale ">=0.13.0"; then
+    has_libav=false
+  fi
+
   if $has_libav; then
     enable libav
   elif enabled libav; then
index f68d016dc7ebb47b5dcc6c3f3e2d4ecd026cacd0..2a670a6950db88de7c493d82d36b739fe4d1dde2 100644 (file)
@@ -46,7 +46,9 @@
 #if ENABLE_TIMESHIFT
 #include "timeshift.h"
 #endif
-
+#if ENABLE_LIBAV
+#include "plumbing/transcoding.h"
+#endif
 #include <sys/statvfs.h>
 #include "settings.h"
 #include <sys/time.h>
@@ -177,6 +179,10 @@ typedef struct htsp_subscription {
   streaming_target_t *hs_tshift;
 #endif
 
+#if ENABLE_LIBAV
+streaming_target_t *hs_transcoder;
+#endif
+
   htsp_msg_q_t hs_q;
 
   time_t hs_last_report; /* Last queue status report sent */
@@ -282,13 +288,23 @@ htsp_subscription_destroy(htsp_connection_t *htsp, htsp_subscription_t *hs)
 {
   LIST_REMOVE(hs, hs_link);
   subscription_unsubscribe(hs->hs_s);
+
   if(hs->hs_tsfix != NULL)
     tsfix_destroy(hs->hs_tsfix);
+
+#if ENABLE_LIBAV
+  if(hs->hs_transcoder != NULL)
+    transcoder_destroy(hs->hs_transcoder);
+#endif
+
   htsp_flush_queue(htsp, &hs->hs_q);
+
 #if ENABLE_TIMESHIFT
   if(hs->hs_tshift)
     timeshift_destroy(hs->hs_tshift);
 #endif
+
+
   free(hs);
 }
 
@@ -1328,6 +1344,32 @@ htsp_method_subscribe(htsp_connection_t *htsp, htsmsg_t *in)
     normts = 1;
   }
 #endif
+
+#if ENABLE_LIBAV
+  if (transcoding_enabled) {
+    transcoder_props_t props;
+
+    props.tp_vcodec = streaming_component_txt2type(htsmsg_get_str(in, "videoCodec"));
+    props.tp_acodec = streaming_component_txt2type(htsmsg_get_str(in, "audioCodec"));
+    props.tp_scodec = streaming_component_txt2type(htsmsg_get_str(in, "subtitleCodec"));
+
+    props.tp_resolution = htsmsg_get_u32_or_default(in, "maxResolution", 0);
+    props.tp_channels   = htsmsg_get_u32_or_default(in, "channels", 0);
+    props.tp_bandwidth  = htsmsg_get_u32_or_default(in, "bandwidth", 0);
+
+    if ((str = htsmsg_get_str(in, "language")))
+      strncpy(props.tp_language, str, 3);
+
+    if(props.tp_vcodec != SCT_UNKNOWN ||
+       props.tp_acodec != SCT_UNKNOWN ||
+       props.tp_scodec != SCT_UNKNOWN) {
+      st = hs->hs_transcoder = transcoder_create(st);
+      transcoder_set_properties(st, &props);
+      normts = 1;
+    }
+  }
+#endif
+
   if(normts)
     st = hs->hs_tsfix = tsfix_create(st);
 
@@ -1640,6 +1682,28 @@ htsp_method_file_seek(htsp_connection_t *htsp, htsmsg_t *in)
   return rep;
 }
 
+
+#if ENABLE_LIBAV
+/**
+ *
+ */
+static htsmsg_t *
+htsp_method_getCodecs(htsp_connection_t *htsp, htsmsg_t *in)
+{
+  htsmsg_t *out, *l;
+
+  l = htsmsg_create_list();
+  transcoder_get_capabilities(l);
+
+  out = htsmsg_create_map();
+
+  htsmsg_add_msg(out, "encoders", l);
+
+  return out;
+}
+#endif
+
+
 /**
  * HTSP methods
  */
@@ -1669,6 +1733,9 @@ struct {
   { "subscriptionSkip",         htsp_method_skip,           ACCESS_STREAMING},
   { "subscriptionSpeed",        htsp_method_speed,          ACCESS_STREAMING},
   { "subscriptionLive",         htsp_method_live,           ACCESS_STREAMING},
+#if ENABLE_LIBAV
+  { "getCodecs",                htsp_method_getCodecs,      ACCESS_STREAMING},
+#endif
   { "fileOpen",                 htsp_method_file_open,      ACCESS_RECORDER},
   { "fileRead",                 htsp_method_file_read,      ACCESS_RECORDER},
   { "fileClose",                htsp_method_file_close,     ACCESS_RECORDER},
index e867eea46d79cf3b49f76f216a0fbc4a4808d8cb..7934c52e5140b9058d34966797e488f516a01649 100644 (file)
@@ -91,6 +91,55 @@ streaming_component_type2codec_id(streaming_component_type_t type)
   return codec_id;
 }
 
+
+/**
+ * Translate a libavcodec id to a component type
+ */
+streaming_component_type_t
+codec_id2streaming_component_type(enum CodecID id)
+{
+  streaming_component_type_t type = CODEC_ID_NONE;
+
+  switch(id) {
+  case CODEC_ID_H264:
+    type = SCT_H264;
+    break;
+  case CODEC_ID_MPEG2VIDEO:
+    type = SCT_MPEG2VIDEO;
+    break;
+  case CODEC_ID_AC3:
+    type = SCT_AC3;
+    break;
+  case CODEC_ID_EAC3:
+    type = SCT_EAC3;
+    break;
+  case CODEC_ID_AAC:
+    type = SCT_AAC;
+    break;
+  case CODEC_ID_MP2:
+    type = SCT_MPEG2AUDIO;
+    break;
+  case CODEC_ID_DVB_SUBTITLE:
+    type = SCT_DVBSUB;
+    break;
+  case CODEC_ID_TEXT:
+    type = SCT_TEXTSUB;
+    break;
+  case CODEC_ID_DVB_TELETEXT:
+    type = SCT_TELETEXT;
+    break;
+  case CODEC_ID_NONE:
+    type = SCT_NONE;
+    break;
+  default:
+    type = SCT_UNKNOWN;
+    break;
+  }
+
+  return type;
+}
+
+
 /**
  * 
  */ 
index c8a6ed7af487b098a9b287f3b7afb3ee518864ac..98856f902f20013699cb5db4bec493aecc7071f1 100644 (file)
@@ -24,7 +24,7 @@
 #include "tvheadend.h"
 
 enum CodecID streaming_component_type2codec_id(streaming_component_type_t type);
-
+streaming_component_type_t codec_id2streaming_component_type(enum CodecID id);
 void libav_init(void);
 
 #endif
index 8e2d7ec8bfed36cf1edc4f4cf4546a01177c5235..8a2323f8b9d0647613feaf6cdbe23c7d5cdae45b 100644 (file)
@@ -64,6 +64,7 @@
 #include "timeshift.h"
 #if ENABLE_LIBAV
 #include "libav.h"
+#include "plumbing/transcoding.h"
 #endif
 
 /* Command line option struct */
@@ -126,6 +127,9 @@ const tvh_caps_t tvheadend_capabilities[] = {
 #if ENABLE_LINUXDVB
   { "linuxdvb", NULL },
 #endif
+#if ENABLE_LIBAV
+  { "transcoding", &transcoding_enabled },
+#endif
 #if ENABLE_IMAGECACHE
   { "imagecache", &imagecache_enabled },
 #endif
diff --git a/src/plumbing/transcoding.c b/src/plumbing/transcoding.c
new file mode 100644 (file)
index 0000000..4cb48f6
--- /dev/null
@@ -0,0 +1,1336 @@
+/**
+ *  Transcoding
+ *  Copyright (C) 2013 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <unistd.h>
+#include <libavformat/avformat.h>
+#include <libavcodec/avcodec.h>
+#include <libswscale/swscale.h>
+#include <libavutil/dict.h>
+
+#include "tvheadend.h"
+#include "streaming.h"
+#include "service.h"
+#include "packet.h"
+#include "transcoding.h"
+#include "libav.h"
+
+LIST_HEAD(transcoder_stream_list, transcoder_stream);
+
+typedef struct transcoder_stream {
+  int                           ts_index;
+  streaming_component_type_t    ts_type;
+  streaming_target_t           *ts_target;
+  LIST_ENTRY(transcoder_stream) ts_link;
+
+  void (*ts_handle_pkt) (struct transcoder_stream *, th_pkt_t *);
+  void (*ts_destroy)    (struct transcoder_stream *);
+} transcoder_stream_t;
+
+
+typedef struct audio_stream {
+  transcoder_stream_t;
+
+  AVCodecContext *aud_ictx;
+  AVCodec        *aud_icodec;
+
+  AVCodecContext *aud_octx;
+  AVCodec        *aud_ocodec;
+
+  uint8_t        *aud_dec_sample;
+  uint32_t        aud_dec_size;
+  uint32_t        aud_dec_offset;
+
+  uint8_t        *aud_enc_sample; 
+  uint32_t        aud_enc_size;
+
+  uint64_t        aud_dec_pts;
+  uint64_t        aud_enc_pts;
+
+  int8_t          aud_channels;
+  int32_t         aud_bitrate;
+} audio_stream_t;
+
+
+typedef struct video_stream {
+  transcoder_stream_t;
+
+  AVCodecContext            *vid_ictx;
+  AVCodec                   *vid_icodec;
+
+  AVCodecContext            *vid_octx;
+  AVCodec                   *vid_ocodec;
+
+  AVFrame                   *vid_dec_frame;
+  struct SwsContext         *vid_scaler;
+  AVFrame                   *vid_enc_frame;
+
+  int16_t                    vid_width;
+  int16_t                    vid_height;
+} video_stream_t;
+
+
+typedef struct subtitle_stream {
+  transcoder_stream_t;
+
+  AVCodecContext            *sub_ictx;
+  AVCodec                   *sub_icodec;
+
+  AVCodecContext            *sub_octx;
+  AVCodec                   *sub_ocodec;
+} subtitle_stream_t;
+
+
+
+typedef struct transcoder {
+  streaming_target_t  t_input;  // must be first
+  streaming_target_t *t_output;
+
+  transcoder_props_t            t_props;
+  struct transcoder_stream_list t_stream_list;
+} transcoder_t;
+
+
+
+#define WORKING_ENCODER(x) (x == CODEC_ID_H264 || x == CODEC_ID_MPEG2VIDEO || \
+                           x == CODEC_ID_AAC || x == CODEC_ID_MP2)
+
+
+uint32_t transcoding_enabled = 0;
+
+/**
+ * 
+ */
+static AVCodec *
+transcoder_get_decoder(streaming_component_type_t ty)
+{
+  enum CodecID codec_id;
+  AVCodec *codec;
+
+  codec_id = streaming_component_type2codec_id(ty);
+  if (codec_id == CODEC_ID_NONE) {
+    tvhlog(LOG_ERR, "transcode", "Unsupported input codec %s", 
+          streaming_component_type2txt(ty));
+    return NULL;
+  }
+
+  codec = avcodec_find_decoder(codec_id);
+  if (!codec) {
+    tvhlog(LOG_ERR, "transcode", "Unable to find %s decoder", 
+          streaming_component_type2txt(ty));
+    return NULL;
+  }
+
+  tvhlog(LOG_DEBUG, "transcode", "Using decoder %s", codec->name);
+
+  return codec;
+}
+
+
+/**
+ * 
+ */
+static AVCodec *
+transcoder_get_encoder(streaming_component_type_t ty)
+{
+  enum CodecID codec_id;
+  AVCodec *codec;
+
+  codec_id = streaming_component_type2codec_id(ty);
+  if (codec_id == CODEC_ID_NONE) {
+    tvhlog(LOG_ERR, "transcode", "Unable to find %s codec", 
+          streaming_component_type2txt(ty));
+    return NULL;
+  }
+
+  if (!WORKING_ENCODER(codec_id)) {
+    tvhlog(LOG_WARNING, "transcode", "Unsupported output codec %s", 
+          streaming_component_type2txt(ty));
+    return NULL;
+  }
+
+  codec = avcodec_find_encoder(codec_id);
+  if (!codec) {
+    tvhlog(LOG_ERR, "transcode", "Unable to find %s encoder", 
+          streaming_component_type2txt(ty));
+    return NULL;
+  }
+
+  tvhlog(LOG_DEBUG, "transcode", "Using encoder %s", codec->name);
+
+  return codec;
+}
+
+
+/**
+ *
+ */
+static void
+transcoder_stream_packet(transcoder_stream_t *ts, th_pkt_t *pkt)
+{
+  streaming_message_t *sm;
+
+  sm = streaming_msg_create_pkt(pkt);
+  streaming_target_deliver2(ts->ts_target, sm);
+}
+
+
+/**
+ *
+ */
+static void
+transcoder_stream_subtitle(transcoder_stream_t *ts, th_pkt_t *pkt)
+{
+  //streaming_message_t *sm;
+  AVCodec *icodec;
+  AVCodecContext *ictx;
+  AVPacket packet;
+  AVSubtitle sub;
+  int length,  got_subtitle;
+
+  subtitle_stream_t *ss = (subtitle_stream_t*)ts;
+
+  ictx = ss->sub_ictx;
+  //octx = ss->sub_octx;
+
+  icodec = ss->sub_icodec;
+  //ocodec = ss->sub_ocodec;
+
+  if (ictx->codec_id == CODEC_ID_NONE) {
+    ictx->codec_id = icodec->id;
+
+    if (avcodec_open(ictx, icodec) < 0) {
+      tvhlog(LOG_ERR, "transcode", "Unable to open %s decoder", icodec->name);
+      ts->ts_index = 0;
+      goto cleanup;
+    }
+  }
+
+  av_init_packet(&packet);
+  packet.data     = pktbuf_ptr(pkt->pkt_payload);
+  packet.size     = pktbuf_len(pkt->pkt_payload);
+  packet.pts      = pkt->pkt_pts;
+  packet.dts      = pkt->pkt_dts;
+  packet.duration = pkt->pkt_duration;
+
+  length = avcodec_decode_subtitle2(ictx,  &sub, &got_subtitle, &packet);
+  if (length <= 0) {
+    tvhlog(LOG_ERR, "transcode", "Unable to decode subtitle (%d)", length);
+    ts->ts_index = 0;
+    goto cleanup;
+  }
+
+  if (!got_subtitle)
+    goto cleanup;
+
+  //TODO: encoding
+
+ cleanup:
+  av_free_packet(&packet);
+  avsubtitle_free(&sub);
+}
+
+
+/**
+ *
+ */
+static void
+transcoder_stream_audio(transcoder_stream_t *ts, th_pkt_t *pkt)
+{
+  AVCodec *icodec, *ocodec;
+  AVCodecContext *ictx, *octx;
+  AVPacket packet;
+  int length, len, i;
+  uint32_t frame_bytes;
+  short *samples;
+  streaming_message_t *sm;
+  th_pkt_t *n;
+  audio_stream_t *as = (audio_stream_t*)ts;
+
+  ictx = as->aud_ictx;
+  octx = as->aud_octx;
+
+  icodec = as->aud_icodec;
+  ocodec = as->aud_ocodec;
+
+  if (ictx->codec_id == CODEC_ID_NONE) {
+    ictx->codec_id = icodec->id;
+
+    if (avcodec_open(ictx, icodec) < 0) {
+      tvhlog(LOG_ERR, "transcode", "Unable to open %s decoder", icodec->name);
+      ts->ts_index = 0;
+      goto cleanup;
+    }
+
+    as->aud_dec_pts = pkt->pkt_pts;
+  }
+
+  if (pkt->pkt_pts > as->aud_dec_pts) {
+    tvhlog(LOG_WARNING, "transcode", "Detected framedrop in audio");
+    as->aud_enc_pts += (pkt->pkt_pts - as->aud_dec_pts);
+  }
+
+  pkt = pkt_merge_header(pkt);
+
+  av_init_packet(&packet);
+  packet.data     = pktbuf_ptr(pkt->pkt_payload);
+  packet.size     = pktbuf_len(pkt->pkt_payload);
+  packet.pts      = pkt->pkt_pts;
+  packet.dts      = pkt->pkt_dts;
+  packet.duration = pkt->pkt_duration;
+
+  if ((len = as->aud_dec_size - as->aud_dec_offset) <= 0) {
+    tvhlog(LOG_ERR, "transcode", "Decoder buffer overflow");
+    ts->ts_index = 0;
+    goto cleanup;
+  }
+
+  samples = (short*)(as->aud_dec_sample + as->aud_dec_offset);
+  if ((length = avcodec_decode_audio3(ictx, samples, &len, &packet)) <= 0) {
+    tvhlog(LOG_ERR, "transcode", "Unable to decode audio (%d)", length);
+    ts->ts_index = 0;
+    goto cleanup;
+  }
+
+  as->aud_dec_pts    += pkt->pkt_duration;
+  as->aud_dec_offset += len;
+
+
+  octx->sample_rate     = ictx->sample_rate;
+  octx->sample_fmt      = ictx->sample_fmt;
+
+  octx->time_base.den   = 90000;
+  octx->time_base.num   = 1;
+
+  octx->channels        = as->aud_channels ? as->aud_channels : ictx->channels;
+  octx->bit_rate        = as->aud_bitrate  ? as->aud_bitrate  : ictx->bit_rate;
+
+  octx->channels        = MIN(octx->channels, ictx->channels);
+  octx->bit_rate        = MIN(octx->bit_rate,  ictx->bit_rate);
+
+  switch (octx->channels) {
+  case 1:
+    octx->channel_layout = AV_CH_LAYOUT_MONO; 
+    break;
+
+  case 2:
+    octx->channel_layout = AV_CH_LAYOUT_STEREO;
+    break;
+
+  case 3:
+    octx->channel_layout = AV_CH_LAYOUT_SURROUND;
+    break;
+
+  case 4:
+    octx->channel_layout = AV_CH_LAYOUT_QUAD;
+    break;
+
+  case 5:
+    octx->channel_layout = AV_CH_LAYOUT_5POINT0;
+    break;
+
+  case 6:
+    octx->channel_layout = AV_CH_LAYOUT_5POINT1;
+    break;
+
+  case 7:
+    octx->channel_layout = AV_CH_LAYOUT_6POINT1;
+    break;
+
+  case 8:
+    octx->channel_layout = AV_CH_LAYOUT_7POINT1;
+    break;
+
+  default: 
+    break;
+  }
+
+  switch (ts->ts_type) {
+  case SCT_MPEG2AUDIO:
+    octx->channels = MIN(octx->channels, 2);
+    if (octx->channels == 1)
+      octx->channel_layout = AV_CH_LAYOUT_MONO;
+    else
+      octx->channel_layout = AV_CH_LAYOUT_STEREO;
+
+    break;
+
+  case SCT_AAC:
+    octx->global_quality = 4*FF_QP2LAMBDA;
+    octx->flags         |= CODEC_FLAG_QSCALE;
+    break;
+
+  default:
+    break;
+  }
+
+  if (octx->codec_id == CODEC_ID_NONE) {
+    octx->codec_id = ocodec->id;
+
+    if (avcodec_open(octx, ocodec) < 0) {
+      tvhlog(LOG_ERR, "transcode", "Unable to open %s encoder", ocodec->name);
+      ts->ts_index = 0;
+      goto cleanup;
+    }
+  }
+
+  frame_bytes = av_get_bytes_per_sample(octx->sample_fmt) *
+    octx->frame_size *
+    octx->channels;
+
+  len = as->aud_dec_offset;
+
+  for (i = 0; i <= (len - frame_bytes); i += frame_bytes) {
+    length = avcodec_encode_audio(octx,
+                                 as->aud_enc_sample,
+                                 as->aud_enc_size,
+                                 (short *)(as->aud_dec_sample + i));
+    if (length < 0) {
+      tvhlog(LOG_ERR, "transcode", "Unable to encode audio (%d)", length);
+      ts->ts_index = 0;
+      goto cleanup;
+
+    } else if (length) {
+      n = pkt_alloc(as->aud_enc_sample, length, as->aud_enc_pts, as->aud_enc_pts);
+      n->pkt_componentindex = ts->ts_index;
+      n->pkt_frametype      = pkt->pkt_frametype;
+      n->pkt_channels       = octx->channels;
+      n->pkt_sri            = pkt->pkt_sri;
+
+      if (octx->coded_frame && octx->coded_frame->pts != AV_NOPTS_VALUE)
+       n->pkt_duration = octx->coded_frame->pts - as->aud_enc_pts;
+      else
+       n->pkt_duration = frame_bytes*90000 / (2 * octx->channels * octx->sample_rate);
+
+      as->aud_enc_pts += n->pkt_duration;
+
+      if (octx->extradata_size)
+       n->pkt_header = pktbuf_alloc(octx->extradata, octx->extradata_size);
+
+      sm = streaming_msg_create_pkt(n);
+      streaming_target_deliver2(ts->ts_target, sm);
+      pkt_ref_dec(n);
+    }
+
+    as->aud_dec_offset -= frame_bytes;
+  }
+
+  if (as->aud_dec_offset)
+    memmove(as->aud_dec_sample, as->aud_dec_sample + len - as->aud_dec_offset, 
+           as->aud_dec_offset);
+
+ cleanup:
+  av_free_packet(&packet);
+}
+
+
+/**
+ *
+ */
+static void
+transcoder_stream_video(transcoder_stream_t *ts, th_pkt_t *pkt)
+{
+  AVCodec *icodec, *ocodec;
+  AVCodecContext *ictx, *octx;
+  AVDictionary *opts;
+  AVPacket packet;
+  AVPicture deint_pic;
+  uint8_t *buf, *out, *deint;
+  int length, len, got_picture;
+  streaming_message_t *sm;
+  th_pkt_t *n;
+  video_stream_t *vs = (video_stream_t*)ts;
+
+  ictx = vs->vid_ictx;
+  octx = vs->vid_octx;
+
+  icodec = vs->vid_icodec;
+  ocodec = vs->vid_ocodec;
+
+  buf = out = deint = NULL;
+  opts = NULL;
+
+  if (ictx->codec_id == CODEC_ID_NONE) {
+    ictx->codec_id = icodec->id;
+
+    if (avcodec_open(ictx, icodec) < 0) {
+      tvhlog(LOG_ERR, "transcode", "Unable to open %s decoder", icodec->name);
+      ts->ts_index = 0;
+      goto cleanup;
+    }
+  }
+
+  pkt = pkt_merge_header(pkt);
+
+  av_init_packet(&packet);
+  packet.data     = pktbuf_ptr(pkt->pkt_payload);
+  packet.size     = pktbuf_len(pkt->pkt_payload);
+  packet.pts      = pkt->pkt_pts;
+  packet.dts      = pkt->pkt_dts;
+  packet.duration = pkt->pkt_duration;
+
+  vs->vid_enc_frame->pts = packet.pts;
+  vs->vid_enc_frame->pkt_dts = packet.dts;
+  vs->vid_enc_frame->pkt_pts = packet.pts;
+
+  vs->vid_dec_frame->pts = packet.pts;
+  vs->vid_dec_frame->pkt_dts = packet.dts;
+  vs->vid_dec_frame->pkt_pts = packet.pts;
+
+  ictx->reordered_opaque = packet.pts;
+
+  length = avcodec_decode_video2(ictx, vs->vid_dec_frame, &got_picture, &packet);
+  if (length <= 0) {
+    tvhlog(LOG_ERR, "transcode", "Unable to decode video (%d)", length);
+    ts->ts_index = 0;
+    goto cleanup;
+  }
+
+  if (!got_picture)
+    goto cleanup;
+
+  octx->sample_aspect_ratio.num = ictx->sample_aspect_ratio.num;
+  octx->sample_aspect_ratio.den = ictx->sample_aspect_ratio.den;
+
+  vs->vid_enc_frame->sample_aspect_ratio.num = vs->vid_dec_frame->sample_aspect_ratio.num;
+  vs->vid_enc_frame->sample_aspect_ratio.den = vs->vid_dec_frame->sample_aspect_ratio.den;
+
+  if(octx->codec_id == CODEC_ID_NONE) {
+    // Common settings
+    octx->width           = vs->vid_width  ? vs->vid_width  : ictx->width;
+    octx->height          = vs->vid_height ? vs->vid_height : ictx->height;
+    octx->gop_size        = 25;
+    octx->time_base.den   = 25;
+    octx->time_base.num   = 1;
+    octx->has_b_frames    = ictx->has_b_frames;
+
+    switch (ts->ts_type) {
+    case SCT_MPEG2VIDEO:
+      octx->codec_id       = CODEC_ID_MPEG2VIDEO;
+      octx->pix_fmt        = PIX_FMT_YUV420P;
+      octx->flags         |= CODEC_FLAG_GLOBAL_HEADER;
+
+      octx->qmin           = 1;
+      octx->qmax           = FF_LAMBDA_MAX;
+
+      octx->bit_rate       = 2 * octx->width * octx->height;
+      octx->rc_max_rate    = 4 * octx->bit_rate;
+      octx->rc_buffer_size = 2 * octx->rc_max_rate;
+      break;
+    case SCT_H264:
+      octx->codec_id       = CODEC_ID_H264;
+      octx->pix_fmt        = PIX_FMT_YUV420P;
+      octx->flags          |= CODEC_FLAG_GLOBAL_HEADER;
+
+      // Qscale difference between I-frames and P-frames. 
+      // Note: -i_qfactor is handled a little differently than --ipratio. 
+      // Recommended: -i_qfactor 0.71
+      octx->i_quant_factor = 0.71;
+
+      // QP curve compression: 0.0 => CBR, 1.0 => CQP.
+      // Recommended default: -qcomp 0.60
+      octx->qcompress = 0.6;
+
+      // Minimum quantizer. Doesn't need to be changed.
+      // Recommended default: -qmin 10
+      octx->qmin = 10;
+
+      // Maximum quantizer. Doesn't need to be changed.
+      // Recommended default: -qmax 51
+      octx->qmax = 30;
+
+      av_dict_set(&opts, "preset",  "medium", 0);
+      av_dict_set(&opts, "profile", "baseline", 0);
+
+      octx->bit_rate       = 2 * octx->width * octx->height;
+      octx->rc_buffer_size = 8 * 1024 * 224;
+      octx->rc_max_rate    = 2 * octx->rc_buffer_size;
+      break;
+
+    default:
+      break;
+    }
+
+    octx->codec_id = ocodec->id;
+
+    if (avcodec_open2(octx, ocodec, &opts) < 0) {
+      tvhlog(LOG_ERR, "transcode", "Unable to open %s encoder", ocodec->name);
+      ts->ts_index = 0;
+      goto cleanup;
+    }
+  }
+
+  len = avpicture_get_size(ictx->pix_fmt, ictx->width, ictx->height);
+  deint = av_malloc(len);
+
+  avpicture_fill(&deint_pic,
+                deint, 
+                ictx->pix_fmt, 
+                ictx->width, 
+                ictx->height);
+
+  if (avpicture_deinterlace(&deint_pic,
+                           (AVPicture *)vs->vid_dec_frame,
+                           ictx->pix_fmt,
+                           ictx->width,
+                           ictx->height) < 0) {
+    tvhlog(LOG_ERR, "transcode", "Cannot deinterlace frame");
+    ts->ts_index = 0;
+    goto cleanup;
+  }
+
+  len = avpicture_get_size(octx->pix_fmt, octx->width, octx->height);
+  buf = av_malloc(len + FF_INPUT_BUFFER_PADDING_SIZE);
+  memset(buf, 0, len);
+
+  avpicture_fill((AVPicture *)vs->vid_enc_frame, 
+                 buf, 
+                 octx->pix_fmt,
+                 octx->width, 
+                 octx->height);
+  vs->vid_scaler = sws_getCachedContext(vs->vid_scaler,
+                                   ictx->width,
+                                   ictx->height,
+                                   ictx->pix_fmt,
+                                   octx->width,
+                                   octx->height,
+                                   octx->pix_fmt,
+                                   1,
+                                   NULL,
+                                   NULL,
+                                   NULL);
+  if (sws_scale(vs->vid_scaler, 
+               (const uint8_t * const*)deint_pic.data, 
+               deint_pic.linesize, 
+               0, 
+               ictx->height, 
+               vs->vid_enc_frame->data, 
+               vs->vid_enc_frame->linesize) < 0) {
+    tvhlog(LOG_ERR, "transcode", "Cannot scale frame");
+    ts->ts_index = 0;
+    goto cleanup;
+  }
+      
+  len = avpicture_get_size(octx->pix_fmt, ictx->width, ictx->height);
+  out = av_malloc(len + FF_INPUT_BUFFER_PADDING_SIZE);
+  memset(out, 0, len);
+
+  vs->vid_enc_frame->pkt_pts = vs->vid_dec_frame->pkt_pts;
+  vs->vid_enc_frame->pkt_dts = vs->vid_dec_frame->pkt_dts;
+
+  if (vs->vid_dec_frame->reordered_opaque != AV_NOPTS_VALUE)
+    vs->vid_enc_frame->pts = vs->vid_dec_frame->reordered_opaque;
+
+  else if (ictx->coded_frame && ictx->coded_frame->pts != AV_NOPTS_VALUE)
+    vs->vid_enc_frame->pts = vs->vid_dec_frame->pts;
+  length = avcodec_encode_video(octx, out, len, vs->vid_enc_frame);
+  if (length <= 0) {
+    if (length) {
+      tvhlog(LOG_ERR, "transcode", "Unable to encode video (%d)", length);
+      ts->ts_index = 0;
+    }
+
+    goto cleanup;
+  }
+
+  if (!octx->coded_frame)
+    goto cleanup;
+
+  n = pkt_alloc(out, length, octx->coded_frame->pkt_pts, octx->coded_frame->pkt_dts);
+
+  switch (octx->coded_frame->pict_type) {
+  case AV_PICTURE_TYPE_I:
+    n->pkt_frametype = PKT_I_FRAME;
+    break;
+
+  case AV_PICTURE_TYPE_P:
+    n->pkt_frametype = PKT_P_FRAME;
+    break;
+
+  case AV_PICTURE_TYPE_B:
+    n->pkt_frametype = PKT_B_FRAME;
+    break;
+
+  default:
+    break;
+  }
+
+  n->pkt_duration       = pkt->pkt_duration;
+  n->pkt_commercial     = pkt->pkt_commercial;
+  n->pkt_componentindex = pkt->pkt_componentindex;
+  n->pkt_field          = pkt->pkt_field;
+  n->pkt_aspect_num     = pkt->pkt_aspect_num;
+  n->pkt_aspect_den     = pkt->pkt_aspect_den;
+  
+  if(octx->coded_frame && octx->coded_frame->pts != AV_NOPTS_VALUE) {
+    if(n->pkt_dts != PTS_UNSET)
+      n->pkt_dts -= n->pkt_pts;
+
+    n->pkt_pts = octx->coded_frame->pts;
+
+    if(n->pkt_dts != PTS_UNSET)
+      n->pkt_dts += n->pkt_pts;
+  }
+
+  if (octx->extradata_size)
+    n->pkt_header = pktbuf_alloc(octx->extradata, octx->extradata_size);
+
+  sm = streaming_msg_create_pkt(n);
+  streaming_target_deliver2(ts->ts_target, sm);
+  pkt_ref_dec(n);
+
+ cleanup:
+  av_free_packet(&packet);
+
+  if(buf)
+    av_free(buf);
+
+  if(out)
+    av_free(out);
+
+  if(deint)
+    av_free(deint);
+
+  if(opts)
+    av_dict_free(&opts);
+}
+
+
+/**
+ * 
+ */
+static void
+transcoder_packet(transcoder_t *t, th_pkt_t *pkt)
+{
+  transcoder_stream_t *ts;
+
+  LIST_FOREACH(ts, &t->t_stream_list, ts_link) {
+    if (pkt->pkt_componentindex != ts->ts_index)
+      continue;
+
+    ts->ts_handle_pkt(ts, pkt);
+    break;
+  }
+}
+
+
+/**
+ * 
+ */
+static void
+transcoder_destroy_stream(transcoder_stream_t *ts)
+{
+  free(ts);
+}
+
+
+/**
+ * 
+ */
+static int
+transcoder_init_stream(transcoder_t *t, streaming_start_component_t *ssc)
+{
+  transcoder_stream_t *ts = calloc(1, sizeof(transcoder_stream_t));
+
+  ts->ts_index      = ssc->ssc_index;
+  ts->ts_type       = ssc->ssc_type;
+  ts->ts_target     = t->t_output;
+  ts->ts_handle_pkt = transcoder_stream_packet;
+  ts->ts_destroy    = transcoder_destroy_stream;
+
+  LIST_INSERT_HEAD(&t->t_stream_list, ts, ts_link);
+
+  if(ssc->ssc_gh)
+    pktbuf_ref_inc(ssc->ssc_gh);
+
+  tvhlog(LOG_INFO, "transcode", "%d:%s ==> Passthrough", 
+        ssc->ssc_index,
+        streaming_component_type2txt(ssc->ssc_type));
+
+  return 1;
+}
+
+
+/**
+ * 
+ */
+static void
+transcoder_destroy_subtitle(transcoder_stream_t *ts)
+{
+  subtitle_stream_t *ss = (subtitle_stream_t*)ts;
+
+  if(ss->sub_ictx) {
+    avcodec_close(ss->sub_ictx);
+    av_free(ss->sub_ictx);
+  }
+
+  if(ss->sub_octx) {
+    avcodec_close(ss->sub_octx);
+    av_free(ss->sub_octx);
+  }
+
+  free(ts);
+}
+
+
+/**
+ * 
+ */
+static int
+transcoder_init_subtitle(transcoder_t *t, streaming_start_component_t *ssc)
+{
+  subtitle_stream_t *ss;
+  AVCodec *icodec, *ocodec;
+  transcoder_props_t *tp = &t->t_props;
+
+  if (tp->tp_scodec == SCT_NONE)
+    return 0;
+
+  else if (tp->tp_scodec == SCT_UNKNOWN)
+    return transcoder_init_stream(t, ssc);
+
+  else if (!(icodec = transcoder_get_decoder(ssc->ssc_type)))
+    return transcoder_init_stream(t, ssc);
+
+  else if (!(ocodec = transcoder_get_encoder(tp->tp_scodec)))
+    return transcoder_init_stream(t, ssc);
+
+  if (tp->tp_scodec == ssc->ssc_type)
+    return transcoder_init_stream(t, ssc);
+
+  ss = calloc(1, sizeof(subtitle_stream_t));
+
+  ss->ts_index      = ssc->ssc_index;
+  ss->ts_type       = tp->tp_scodec;
+  ss->ts_target     = t->t_output;
+  ss->ts_handle_pkt = transcoder_stream_subtitle;
+  ss->ts_destroy    = transcoder_destroy_subtitle;
+
+  ss->sub_icodec = icodec;
+  ss->sub_ocodec = ocodec;
+
+  ss->sub_ictx = avcodec_alloc_context();
+  ss->sub_octx = avcodec_alloc_context();
+
+  ss->sub_ictx->codec_type = AVMEDIA_TYPE_SUBTITLE;
+  ss->sub_octx->codec_type = AVMEDIA_TYPE_SUBTITLE;
+
+  avcodec_get_context_defaults3(ss->sub_ictx, icodec);
+  avcodec_get_context_defaults3(ss->sub_octx, ocodec);
+
+  LIST_INSERT_HEAD(&t->t_stream_list, (transcoder_stream_t*)ss, ts_link);
+
+  tvhlog(LOG_INFO, "transcode", "%d:%s ==> %s", 
+        ssc->ssc_index,
+        streaming_component_type2txt(ssc->ssc_type),
+        streaming_component_type2txt(ss->ts_type));
+
+  ssc->ssc_type = tp->tp_scodec;
+  ssc->ssc_gh = NULL;
+
+  return 1;
+}
+
+
+/**
+ * 
+ */
+static void
+transcoder_destroy_audio(transcoder_stream_t *ts)
+{
+  audio_stream_t *as = (audio_stream_t*)ts;
+
+  if(as->aud_ictx) {
+    avcodec_close(as->aud_ictx);
+    av_free(as->aud_ictx);
+  }
+
+  if(as->aud_octx) {
+    avcodec_close(as->aud_octx);
+    av_free(as->aud_octx);
+  }
+
+  if(as->aud_dec_sample)
+    av_free(as->aud_dec_sample);
+
+  if(as->aud_enc_sample)
+    av_free(as->aud_enc_sample);
+
+  free(ts);
+}
+
+
+/**
+ * 
+ */
+static int
+transcoder_init_audio(transcoder_t *t, streaming_start_component_t *ssc)
+{
+  audio_stream_t *as;
+  transcoder_stream_t *ts;
+  AVCodec *icodec, *ocodec;
+  transcoder_props_t *tp = &t->t_props;
+
+  if (tp->tp_acodec == SCT_NONE)
+    return 0;
+
+  else if (tp->tp_acodec == SCT_UNKNOWN)
+    return transcoder_init_stream(t, ssc);
+
+  else if (!(icodec = transcoder_get_decoder(ssc->ssc_type)))
+    return transcoder_init_stream(t, ssc);
+
+  else if (!(ocodec = transcoder_get_encoder(tp->tp_acodec)))
+    return transcoder_init_stream(t, ssc);
+
+  LIST_FOREACH(ts, &t->t_stream_list, ts_link)
+    if (SCT_ISAUDIO(ts->ts_type))
+       return 0;
+
+  if (tp->tp_acodec == ssc->ssc_type)
+    return transcoder_init_stream(t, ssc);
+
+  as = calloc(1, sizeof(audio_stream_t));
+
+  as->ts_index      = ssc->ssc_index;
+  as->ts_type       = tp->tp_acodec;
+  as->ts_target     = t->t_output;
+  as->ts_handle_pkt = transcoder_stream_audio;
+  as->ts_destroy    = transcoder_destroy_audio;
+
+  as->aud_icodec = icodec;
+  as->aud_ocodec = ocodec;
+
+  as->aud_ictx = avcodec_alloc_context();
+  as->aud_octx = avcodec_alloc_context();
+
+  as->aud_ictx->codec_type = AVMEDIA_TYPE_AUDIO;
+  as->aud_octx->codec_type = AVMEDIA_TYPE_AUDIO;
+
+  as->aud_ictx->thread_count = sysconf(_SC_NPROCESSORS_ONLN);
+  as->aud_octx->thread_count = sysconf(_SC_NPROCESSORS_ONLN);
+
+  avcodec_get_context_defaults3(as->aud_ictx, icodec);
+  avcodec_get_context_defaults3(as->aud_octx, ocodec);
+
+  as->aud_ictx->codec_type = AVMEDIA_TYPE_AUDIO;
+  as->aud_octx->codec_type = AVMEDIA_TYPE_AUDIO;
+
+  as->aud_dec_size = AVCODEC_MAX_AUDIO_FRAME_SIZE*2;
+  as->aud_enc_size = AVCODEC_MAX_AUDIO_FRAME_SIZE*2;
+
+  as->aud_dec_sample = av_malloc(as->aud_dec_size + FF_INPUT_BUFFER_PADDING_SIZE);
+  as->aud_enc_sample = av_malloc(as->aud_enc_size + FF_INPUT_BUFFER_PADDING_SIZE);
+
+  memset(as->aud_dec_sample, 0, as->aud_dec_size + FF_INPUT_BUFFER_PADDING_SIZE);
+  memset(as->aud_enc_sample, 0, as->aud_enc_size + FF_INPUT_BUFFER_PADDING_SIZE);
+
+  LIST_INSERT_HEAD(&t->t_stream_list, (transcoder_stream_t*)as, ts_link);
+
+  tvhlog(LOG_INFO, "transcode", "%d:%s ==> %s", 
+        ssc->ssc_index,
+        streaming_component_type2txt(ssc->ssc_type),
+        streaming_component_type2txt(as->ts_type));
+
+  ssc->ssc_type     = tp->tp_acodec;
+  ssc->ssc_gh       = NULL;
+
+  // resampling not implemented yet
+  if(tp->tp_channels > 0)
+    as->aud_channels = 0; //tp->tp_channels; 
+  else
+    as->aud_channels = 0;
+
+  as->aud_bitrate = as->aud_channels * 64000;
+  return 1;
+}
+
+
+/**
+ * 
+ */
+static void
+transcoder_destroy_video(transcoder_stream_t *ts)
+{
+  video_stream_t *vs = (video_stream_t*)ts;
+
+  if(vs->vid_ictx) {
+    avcodec_close(vs->vid_ictx);
+    av_free(vs->vid_ictx);
+  }
+
+  if(vs->vid_octx) {
+    avcodec_close(vs->vid_octx);
+    av_free(vs->vid_octx);
+  }
+
+  if(vs->vid_dec_frame)
+    av_free(vs->vid_dec_frame);
+
+  if(vs->vid_scaler)
+    sws_freeContext(vs->vid_scaler);
+
+  if(vs->vid_enc_frame)
+    av_free(vs->vid_enc_frame);
+
+  free(ts);
+}
+
+
+/**
+ * 
+ */
+static int
+transcoder_init_video(transcoder_t *t, streaming_start_component_t *ssc)
+{
+  video_stream_t *vs;
+  AVCodec *icodec, *ocodec;
+  double aspect;
+  transcoder_props_t *tp = &t->t_props;
+
+  if (tp->tp_vcodec == SCT_NONE)
+    return 0;
+
+  else if (tp->tp_vcodec == SCT_UNKNOWN)
+    return transcoder_init_stream(t, ssc);
+
+  else if (!(icodec = transcoder_get_decoder(ssc->ssc_type)))
+    return transcoder_init_stream(t, ssc);
+
+  else if (!(ocodec = transcoder_get_encoder(tp->tp_vcodec)))
+    return transcoder_init_stream(t, ssc);
+
+  vs = calloc(1, sizeof(video_stream_t));
+
+  vs->ts_index      = ssc->ssc_index;
+  vs->ts_type       = tp->tp_vcodec;
+  vs->ts_target     = t->t_output;
+  vs->ts_handle_pkt = transcoder_stream_video;
+  vs->ts_destroy    = transcoder_destroy_video;
+
+  vs->vid_icodec = icodec;
+  vs->vid_ocodec = ocodec;
+
+  vs->vid_ictx = avcodec_alloc_context();
+  vs->vid_octx = avcodec_alloc_context();
+
+  vs->vid_ictx->thread_count = sysconf(_SC_NPROCESSORS_ONLN);
+  vs->vid_octx->thread_count = sysconf(_SC_NPROCESSORS_ONLN);
+  avcodec_get_context_defaults3(vs->vid_ictx, icodec);
+  avcodec_get_context_defaults3(vs->vid_octx, ocodec);
+
+  vs->vid_dec_frame = avcodec_alloc_frame();
+  vs->vid_enc_frame = avcodec_alloc_frame();
+
+  avcodec_get_frame_defaults(vs->vid_dec_frame);
+  avcodec_get_frame_defaults(vs->vid_enc_frame);
+
+  vs->vid_ictx->codec_type = AVMEDIA_TYPE_VIDEO;
+  vs->vid_octx->codec_type = AVMEDIA_TYPE_VIDEO;
+
+  LIST_INSERT_HEAD(&t->t_stream_list, (transcoder_stream_t*)vs, ts_link);
+
+  aspect = (double)ssc->ssc_width / ssc->ssc_height;
+
+  vs->vid_height = MIN(tp->tp_resolution, ssc->ssc_height);
+  if (vs->vid_height&1) // Must be even
+    vs->vid_height++;
+
+  vs->vid_width = vs->vid_height * aspect;
+  if (vs->vid_width&1) // Must be even
+    vs->vid_width++;
+
+  tvhlog(LOG_INFO, "transcode", "%d:%s %dx%d ==> %s %dx%d", 
+        ssc->ssc_index,
+        streaming_component_type2txt(ssc->ssc_type),
+        ssc->ssc_width,
+        ssc->ssc_height,
+        streaming_component_type2txt(vs->ts_type),
+        vs->vid_width,
+        vs->vid_height);
+
+  ssc->ssc_type   = tp->tp_vcodec;
+  ssc->ssc_width  = vs->vid_width;
+  ssc->ssc_height = vs->vid_height;
+  ssc->ssc_gh     = NULL;
+
+  return 1;
+}
+
+
+/**
+ * Figure out how many streams we will use.
+ */
+static int
+transcoder_calc_stream_count(transcoder_t *t, streaming_start_t *ss) {
+  int i = 0;
+  int video = 0;
+  int audio = 0;
+  int subtitle = 0;
+  streaming_start_component_t *ssc = NULL;
+
+  for (i = 0; i < ss->ss_num_components; i++) {
+    ssc = &ss->ss_components[i];
+
+    if (ssc->ssc_disabled)
+      continue;
+
+    if (SCT_ISVIDEO(ssc->ssc_type)) {
+      if (t->t_props.tp_vcodec == SCT_NONE)
+       video = 0;
+      else if (t->t_props.tp_vcodec == SCT_UNKNOWN)
+       video++;
+      else
+       video = 1;
+
+    } else if (SCT_ISAUDIO(ssc->ssc_type)) {
+      if (t->t_props.tp_acodec == SCT_NONE)
+       audio = 0;
+      else if (t->t_props.tp_acodec == SCT_UNKNOWN)
+       audio++;
+      else
+       audio = 1;
+
+    } else if (SCT_ISSUBTITLE(ssc->ssc_type)) {
+      if (t->t_props.tp_scodec == SCT_NONE)
+       subtitle = 0;
+      else if (t->t_props.tp_scodec == SCT_UNKNOWN)
+       subtitle++;
+      else
+       subtitle = 1;
+    }
+  }
+
+  return (video + audio + subtitle);
+}
+
+
+/**
+ * 
+ */
+static streaming_start_t *
+transcoder_start(transcoder_t *t, streaming_start_t *src)
+{
+  int i, j, n, rc;
+  streaming_start_t *ss;
+
+
+  n = transcoder_calc_stream_count(t, src);
+  ss = calloc(1, (sizeof(streaming_start_t) +
+                 sizeof(streaming_start_component_t) * n));
+
+  ss->ss_refcount       = 1;
+  ss->ss_num_components = n;
+  ss->ss_pcr_pid        = src->ss_pcr_pid;
+  ss->ss_pmt_pid        = src->ss_pmt_pid;
+  service_source_info_copy(&ss->ss_si, &src->ss_si);
+
+
+  for (i = j = 0; i < src->ss_num_components && j < n; i++) {
+    streaming_start_component_t *ssc_src = &src->ss_components[i];
+    streaming_start_component_t *ssc = &ss->ss_components[j];
+    
+    if (ssc_src->ssc_disabled)
+      continue;
+
+    ssc->ssc_index          = ssc_src->ssc_index;
+    ssc->ssc_type           = ssc_src->ssc_type;
+    ssc->ssc_composition_id = ssc_src->ssc_composition_id;
+    ssc->ssc_ancillary_id   = ssc_src->ssc_ancillary_id;
+    ssc->ssc_pid            = ssc_src->ssc_pid;
+    ssc->ssc_width          = ssc_src->ssc_width;
+    ssc->ssc_height         = ssc_src->ssc_height;
+    ssc->ssc_aspect_num     = ssc_src->ssc_aspect_num;
+    ssc->ssc_aspect_den     = ssc_src->ssc_aspect_den;
+    ssc->ssc_sri            = ssc_src->ssc_sri;
+    ssc->ssc_channels       = ssc_src->ssc_channels;
+    ssc->ssc_disabled       = ssc_src->ssc_disabled;
+    ssc->ssc_frameduration  = ssc_src->ssc_frameduration;
+    ssc->ssc_gh             = ssc_src->ssc_gh;
+
+    memcpy(ssc->ssc_lang, ssc_src->ssc_lang, 4);
+
+    if (SCT_ISVIDEO(ssc->ssc_type)) 
+      rc = transcoder_init_video(t, ssc);
+
+    else if (SCT_ISAUDIO(ssc->ssc_type))
+      rc = transcoder_init_audio(t, ssc);
+
+    else if (SCT_ISSUBTITLE(ssc->ssc_type))
+      rc = transcoder_init_subtitle(t, ssc);
+    else
+      rc = 0;
+
+    if(!rc)
+      tvhlog(LOG_INFO, "transcode", "%d:%s ==> Filtered", 
+            ssc->ssc_index,
+            streaming_component_type2txt(ssc->ssc_type));
+    else
+      j++;
+  }
+
+  return ss;
+}
+
+
+/**
+ * 
+ */
+static void
+transcoder_stop(transcoder_t *t)
+{
+  transcoder_stream_t *ts;
+  
+  while ((ts = LIST_FIRST(&t->t_stream_list))) {
+    LIST_REMOVE(ts, ts_link);
+
+    if (ts->ts_destroy)
+      ts->ts_destroy(ts);
+  }
+}
+
+
+/**
+ * 
+ */
+static void
+transcoder_input(void *opaque, streaming_message_t *sm)
+{
+  transcoder_t *t;
+  streaming_start_t *ss;
+  th_pkt_t *pkt;
+
+  t = opaque;
+
+  switch (sm->sm_type) {
+  case SMT_PACKET:
+    pkt = sm->sm_data;
+    transcoder_packet(t, pkt);
+    pkt_ref_dec(pkt);
+    break;
+
+  case SMT_START:
+    ss = transcoder_start(t, sm->sm_data);
+    streaming_start_unref(sm->sm_data);
+    sm->sm_data = ss;
+
+    streaming_target_deliver2(t->t_output, sm);
+    break;
+
+  case SMT_STOP:
+    transcoder_stop(t);
+    // Fallthrough
+
+  case SMT_SPEED:
+  case SMT_SKIP:
+  case SMT_TIMESHIFT_STATUS:
+  case SMT_EXIT:
+  case SMT_SERVICE_STATUS:
+  case SMT_SIGNAL_STATUS:
+  case SMT_NOSTART:
+  case SMT_MPEGTS:
+    streaming_target_deliver2(t->t_output, sm);
+    break;
+  }
+}
+
+
+/**
+ *
+ */
+streaming_target_t *
+transcoder_create(streaming_target_t *output)
+{
+  transcoder_t *t = calloc(1, sizeof(transcoder_t));
+
+  t->t_output = output;
+
+  streaming_target_init(&t->t_input, transcoder_input, t, 0);
+
+  return &t->t_input;
+}
+
+
+/**
+ * 
+ */
+void
+transcoder_set_properties(streaming_target_t *st, 
+                         transcoder_props_t *props)
+{
+  transcoder_t *t = (transcoder_t *)st;
+  transcoder_props_t *tp = &t->t_props;
+
+  tp->tp_vcodec     = props->tp_vcodec;
+  tp->tp_acodec     = props->tp_acodec;
+  tp->tp_scodec     = props->tp_scodec;
+  tp->tp_channels   = props->tp_channels;
+  tp->tp_bandwidth  = props->tp_bandwidth;
+  tp->tp_resolution = props->tp_resolution;
+
+  memcpy(tp->tp_language, props->tp_language, 4);
+}
+
+
+/**
+ * 
+ */
+void
+transcoder_destroy(streaming_target_t *st)
+{
+  transcoder_t *t = (transcoder_t *)st;
+
+  transcoder_stop(t);
+  free(t);
+}
+
+
+/**
+ * 
+ */ 
+void
+transcoder_get_capabilities(htsmsg_t *array)
+{
+  AVCodec *p = NULL;
+  const char *name;
+  streaming_component_type_t sct;
+
+  while ((p = av_codec_next(p))) {
+
+    if (!p->encode && !p->encode2)
+      continue;
+
+    if (!WORKING_ENCODER(p->id))
+      continue;
+
+    sct = codec_id2streaming_component_type(p->id);
+    if (sct == SCT_NONE)
+      continue;
+
+    name = streaming_component_type2txt(sct);
+    htsmsg_add_str(array, NULL, name);
+  }
+}
+
+
+
diff --git a/src/plumbing/transcoding.h b/src/plumbing/transcoding.h
new file mode 100644 (file)
index 0000000..a08bea0
--- /dev/null
@@ -0,0 +1,42 @@
+/**
+ *  Transcoding
+ *  Copyright (C) 2013 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 <http://www.gnu.org/licenses/>.
+ */
+#pragma once
+
+#include "tvheadend.h"
+#include "htsmsg.h"
+
+typedef struct transcoder_prop {
+  streaming_component_type_t tp_vcodec;
+  streaming_component_type_t tp_acodec;
+  streaming_component_type_t tp_scodec;
+
+  int8_t   tp_channels;
+  int32_t  tp_bandwidth;
+  char     tp_language[4];
+  int32_t  tp_resolution;
+} transcoder_props_t;
+
+extern uint32_t transcoding_enabled;
+
+streaming_target_t *transcoder_create (streaming_target_t *output);
+void                transcoder_destroy(streaming_target_t *tr);
+
+void transcoder_get_capabilities(htsmsg_t *array);
+void transcoder_set_properties  (streaming_target_t *tr, 
+                                transcoder_props_t *prop);
+
index b191037b23367c0d1c3963cc407d23f827e6a31a..d86519c89895ccfa582c7b5d0f04b25ab123b5e1 100644 (file)
--- a/src/psi.c
+++ b/src/psi.c
@@ -908,6 +908,8 @@ psi_caid2name(uint16_t caid)
  *
  */
 static struct strtab streamtypetab[] = {
+  { "NONE",       SCT_NONE },
+  { "UNKNOWN",    SCT_UNKNOWN },
   { "MPEG2VIDEO", SCT_MPEG2VIDEO },
   { "MPEG2AUDIO", SCT_MPEG2AUDIO },
   { "H264",       SCT_H264 },
@@ -920,7 +922,7 @@ static struct strtab streamtypetab[] = {
   { "MPEGTS",     SCT_MPEGTS },
   { "TEXTSUB",    SCT_TEXTSUB },
   { "EAC3",       SCT_EAC3 },
-  { "AAC",       SCT_MP4A },
+  { "AAC",        SCT_MP4A },
 };
 
 
@@ -933,6 +935,14 @@ streaming_component_type2txt(streaming_component_type_t s)
   return val2str(s, streamtypetab) ?: "INVALID";
 }
 
+/**
+ *
+ */
+streaming_component_type_t
+streaming_component_txt2type(const char *str)
+{
+  return str ? str2val(str, streamtypetab) : SCT_UNKNOWN;
+}
 
 /**
  * Store service settings into message
index 55cdd517ccd014b19d9a32cb9d0172758475fb50..4bc951dc0ae4deb007fed1d2153df1e7e260e23e 100644 (file)
@@ -184,6 +184,7 @@ int get_device_connection(const char *dev);
  * Stream component types
  */
 typedef enum {
+  SCT_NONE = -1,
   SCT_UNKNOWN = 0,
   SCT_MPEG2VIDEO = 1,
   SCT_MPEG2AUDIO,
@@ -423,7 +424,7 @@ typedef struct sbuf {
 } sbuf_t;
 
 
-
+streaming_component_type_t streaming_component_txt2type(const char *str);
 const char *streaming_component_type2txt(streaming_component_type_t s);
 
 static inline unsigned int tvh_strhash(const char *s, unsigned int mod)
index bc7d5989ee630b47b7c4af26f348d0335b94628a..997b8829ec84a415e08be90660c03d0406f119e9 100644 (file)
@@ -40,6 +40,7 @@
 #include "psi.h"
 #include "plumbing/tsfix.h"
 #include "plumbing/globalheaders.h"
+#include "plumbing/transcoding.h"
 #include "epg.h"
 #include "muxer.h"
 #include "dvb/dvb.h"
@@ -548,6 +549,47 @@ page_http_playlist(http_connection_t *hc, const char *remain, void *opaque)
 }
 
 
+#if ENABLE_LIBAV
+static int
+http_get_transcoder_properties(struct http_arg_list *args, 
+                              transcoder_props_t *props)
+{
+  int transcode;
+  const char *s;
+
+  memset(props, 0, sizeof(transcoder_props_t));
+
+  if ((s = http_arg_get(args, "transcode")))
+    transcode = atoi(s);
+  else
+    transcode = 0;
+
+  if ((s = http_arg_get(args, "resolution")))
+    props->tp_resolution = atoi(s);
+  if ((s = http_arg_get(args, "channels")))
+    props->tp_channels = atoi(s);
+  if ((s = http_arg_get(args, "bandwidth")))
+    props->tp_bandwidth = atoi(s);
+
+  if ((s = http_arg_get(args, "language")))
+    strncpy(props->tp_language, s, 3);
+
+  if ((s = http_arg_get(args, "vcodec")))
+    props->tp_vcodec = streaming_component_txt2type(s);
+
+  if ((s = http_arg_get(args, "acodec")))
+    props->tp_acodec = streaming_component_txt2type(s);
+
+  if ((s = http_arg_get(args, "scodec")))
+    props->tp_scodec = streaming_component_txt2type(s);
+
+  return transcode && transcoding_enabled;
+}
+#endif
+
+
 /**
  * Subscribes to a service and starts the streaming loop
  */
@@ -660,6 +702,9 @@ http_stream_channel(http_connection_t *hc, channel_t *ch)
   streaming_target_t *gh;
   streaming_target_t *tsfix;
   streaming_target_t *st;
+#if ENABLE_LIBAV
+  streaming_target_t *tr = NULL;
+#endif
   dvr_config_t *cfg;
   int priority = 100;
   int flags;
@@ -689,6 +734,14 @@ http_stream_channel(http_connection_t *hc, channel_t *ch)
   } else {
     streaming_queue_init2(&sq, 0, qsize);
     gh = globalheaders_create(&sq.sq_st);
+#if ENABLE_LIBAV
+    transcoder_props_t props;
+    if(http_get_transcoder_properties(&hc->hc_req_args, &props)) {
+      tr = transcoder_create(gh);
+      transcoder_set_properties(tr, &props);
+      tsfix = tsfix_create(tr);
+    } else
+#endif
     tsfix = tsfix_create(gh);
     st = tsfix;
     flags = 0;
@@ -710,6 +763,12 @@ http_stream_channel(http_connection_t *hc, channel_t *ch)
 
   if(gh)
     globalheaders_destroy(gh);
+
+#if ENABLE_LIBAV
+  if(tr)
+    transcoder_destroy(tr);
+#endif
+
   if(tsfix)
     tsfix_destroy(tsfix);