diff --git a/Makefile b/Makefile index 00ee4147..b3a6a7ae 100644 --- 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 \ diff --git a/configure b/configure index 8933d92f..d7f5ec0e 100755 --- a/configure +++ b/configure @@ -142,6 +142,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 diff --git a/src/dvb/dvb_tables.c b/src/dvb/dvb_tables.c index 1a7ea185..937583f1 100644 --- a/src/dvb/dvb_tables.c +++ b/src/dvb/dvb_tables.c @@ -893,13 +893,13 @@ dvb_nit_callback(th_dvb_mux_instance_t *tdmi, uint8_t *ptr, int len, tvhtrace("nit", "tableid 0x%02x", tableid); tvhlog_hexdump("nit", ptr, len); - /* Ignore other network */ - if(tableid != 0x40) return -1; - - /* Check NID */ - if(tdmi->tdmi_adapter->tda_nitoid && - tdmi->tdmi_adapter->tda_nitoid != network_id) - return -1; + /* Specific NID requested */ + if(tdmi->tdmi_adapter->tda_nitoid) { + if (tdmi->tdmi_adapter->tda_nitoid != network_id) + return -1; + } else if (tableid != 0x40) { + return -1; + } /* Ignore non-current */ if((ptr[2] & 1) == 0) diff --git a/src/epg.c b/src/epg.c index e031ae07..280b8108 100644 --- a/src/epg.c +++ b/src/epg.c @@ -1396,12 +1396,16 @@ static void _epg_channel_timer_callback ( void *p ) break; } - /* Change */ - if (cur != ch->ch_epg_now || nxt != ch->ch_epg_next) + /* Change (update HTSP) */ + if (cur != ch->ch_epg_now || nxt != ch->ch_epg_next) { tvhlog(LOG_DEBUG, "epg", "now/next %u/%u set on %s", ch->ch_epg_now ? ch->ch_epg_now->id : 0, ch->ch_epg_next ? ch->ch_epg_next->id : 0, ch->ch_name); + tvhlog(LOG_DEBUG, "epg", "inform HTSP of now event change on %s", + ch->ch_name); + htsp_channel_update_nownext(ch); + } /* re-arm */ if ( next ) { @@ -1410,13 +1414,6 @@ static void _epg_channel_timer_callback ( void *p ) gtimer_arm_abs(&ch->ch_epg_timer, _epg_channel_timer_callback, ch, next); } - /* Update HTSP */ - if ( cur != ch->ch_epg_now ) { - tvhlog(LOG_DEBUG, "epg", "inform HTSP of now event change on %s", - ch->ch_name); - htsp_channel_update_current(ch); - } - /* Remove refs */ if (cur) cur->putref(cur); if (nxt) nxt->putref(nxt); diff --git a/src/htsp_server.c b/src/htsp_server.c index 4aef88be..1931fb83 100644 --- a/src/htsp_server.c +++ b/src/htsp_server.c @@ -48,7 +48,9 @@ #if ENABLE_TIMESHIFT #include "timeshift.h" #endif - +#if ENABLE_LIBAV +#include "plumbing/transcoding.h" +#endif #include #include "settings.h" #include @@ -59,7 +61,7 @@ static void *htsp_server, *htsp_server_2; -#define HTSP_PROTO_VERSION 10 +#define HTSP_PROTO_VERSION 11 #define HTSP_ASYNC_OFF 0x00 #define HTSP_ASYNC_ON 0x01 @@ -179,6 +181,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 */ @@ -189,6 +195,10 @@ typedef struct htsp_subscription { int hs_queue_depth; +#define NUM_FILTERED_STREAMS (32*16) + + uint32_t hs_filtered_streams[16]; // one bit per stream + } htsp_subscription_t; @@ -210,6 +220,31 @@ typedef struct htsp_file { * Support routines * *************************************************************************/ +static void +htsp_disable_stream(htsp_subscription_t *hs, unsigned int id) +{ + if(id < NUM_FILTERED_STREAMS) + hs->hs_filtered_streams[id / 32] |= 1 << (id & 31); +} + + +static void +htsp_enable_stream(htsp_subscription_t *hs, unsigned int id) +{ + if(id < NUM_FILTERED_STREAMS) + hs->hs_filtered_streams[id / 32] &= ~(1 << (id & 31)); +} + + +static inline int +htsp_is_stream_enabled(htsp_subscription_t *hs, unsigned int id) +{ + if(id < NUM_FILTERED_STREAMS) + return !(hs->hs_filtered_streams[id / 32] & (1 << (id & 31))); + return 1; +} + + /** * */ @@ -284,13 +319,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); } @@ -656,11 +701,16 @@ htsp_build_event htsmsg_add_str(out, "summary", str); } else if((str = epg_broadcast_get_summary(e, lang))) htsmsg_add_str(out, "description", str); - if (e->serieslink) + if (e->serieslink) { htsmsg_add_u32(out, "serieslinkId", e->serieslink->id); + if (e->serieslink->uri) + htsmsg_add_str(out, "serieslinkUri", e->serieslink->uri); + } if (ee) { htsmsg_add_u32(out, "episodeId", ee->id); + if (ee->uri && strncasecmp(ee->uri,"tvh://",6)) /* tvh:// uris are internal */ + htsmsg_add_str(out, "episodeUri", ee->uri); if (ee->brand) htsmsg_add_u32(out, "brandId", ee->brand->id); if (ee->season) @@ -1331,6 +1381,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); @@ -1493,6 +1569,44 @@ htsp_method_live(htsp_connection_t *htsp, htsmsg_t *in) return NULL; } +/** + * Change filters for a subscription + */ +static htsmsg_t * +htsp_method_filter_stream(htsp_connection_t *htsp, htsmsg_t *in) +{ + htsp_subscription_t *hs; + uint32_t sid; + htsmsg_t *l; + if(htsmsg_get_u32(in, "subscriptionId", &sid)) + return htsp_error("Missing argument 'subscriptionId'"); + + LIST_FOREACH(hs, &htsp->htsp_subscriptions, hs_link) + if(hs->hs_sid == sid) + break; + + if(hs == NULL) + return htsp_error("Requested subscription does not exist"); + + if((l = htsmsg_get_list(in, "enable")) != NULL) { + htsmsg_field_t *f; + HTSMSG_FOREACH(f, l) { + if(f->hmf_type == HMF_S64) + htsp_enable_stream(hs, f->hmf_s64); + } + } + + if((l = htsmsg_get_list(in, "disable")) != NULL) { + htsmsg_field_t *f; + HTSMSG_FOREACH(f, l) { + if(f->hmf_type == HMF_S64) + htsp_disable_stream(hs, f->hmf_s64); + } + } + return htsmsg_create_map(); +} + + /** * Open file */ @@ -1643,6 +1757,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 */ @@ -1672,6 +1808,10 @@ struct { { "subscriptionSkip", htsp_method_skip, ACCESS_STREAMING}, { "subscriptionSpeed", htsp_method_speed, ACCESS_STREAMING}, { "subscriptionLive", htsp_method_live, ACCESS_STREAMING}, + { "subscriptionFilterStream", htsp_method_filter_stream, 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}, @@ -2029,13 +2169,13 @@ htsp_async_send(htsmsg_t *m, int mode) /** - * EPG subsystem calls this function when the current event + * EPG subsystem calls this function when the current/next event * changes for a channel, e may be NULL if there is no current event. * * global_lock is held */ void -htsp_channel_update_current(channel_t *ch) +htsp_channel_update_nownext(channel_t *ch) { epg_broadcast_t *now, *next; htsmsg_t *m; @@ -2216,6 +2356,11 @@ htsp_stream_deliver(htsp_subscription_t *hs, th_pkt_t *pkt) int64_t ts; int qlen = hs->hs_q.hmq_payload; + if(!htsp_is_stream_enabled(hs, pkt->pkt_componentindex)) { + pkt_ref_dec(pkt); + return; + } + if((qlen > hs->hs_queue_depth && pkt->pkt_frametype == PKT_B_FRAME) || (qlen > hs->hs_queue_depth * 2 && pkt->pkt_frametype == PKT_P_FRAME) || (qlen > hs->hs_queue_depth * 3)) { @@ -2357,6 +2502,7 @@ htsp_subscription_start(htsp_subscription_t *hs, const streaming_start_t *ss) if (SCT_ISAUDIO(ssc->ssc_type)) { + htsmsg_add_u32(c, "audio_type", ssc->ssc_audio_type); if (ssc->ssc_channels) htsmsg_add_u32(c, "channels", ssc->ssc_channels); if (ssc->ssc_sri) diff --git a/src/htsp_server.h b/src/htsp_server.h index 77002cac..f551ab89 100644 --- a/src/htsp_server.h +++ b/src/htsp_server.h @@ -24,7 +24,7 @@ void htsp_init(const char *bindaddr); -void htsp_channel_update_current(channel_t *ch); +void htsp_channel_update_nownext(channel_t *ch); void htsp_channel_add(channel_t *ch); void htsp_channel_update(channel_t *ch); diff --git a/src/libav.c b/src/libav.c index e867eea4..7934c52e 100644 --- a/src/libav.c +++ b/src/libav.c @@ -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; +} + + /** * */ diff --git a/src/libav.h b/src/libav.h index c8a6ed7a..98856f90 100644 --- a/src/libav.h +++ b/src/libav.h @@ -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 diff --git a/src/main.c b/src/main.c index 8e2d7ec8..8a2323f8 100644 --- a/src/main.c +++ b/src/main.c @@ -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 index 00000000..4cb48f67 --- /dev/null +++ b/src/plumbing/transcoding.c @@ -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 . + */ + +#include +#include +#include +#include +#include + +#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 index 00000000..a08bea01 --- /dev/null +++ b/src/plumbing/transcoding.h @@ -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 . + */ +#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); + diff --git a/src/psi.c b/src/psi.c index b191037b..378cc4c4 100644 --- a/src/psi.c +++ b/src/psi.c @@ -172,16 +172,17 @@ psi_build_pat(service_t *t, uint8_t *buf, int maxlen, int pmtpid) #define PMT_UPDATE_PCR 0x1 #define PMT_UPDATE_NEW_STREAM 0x2 #define PMT_UPDATE_LANGUAGE 0x4 -#define PMT_UPDATE_FRAME_DURATION 0x8 -#define PMT_UPDATE_COMPOSITION_ID 0x10 -#define PMT_UPDATE_ANCILLARY_ID 0x20 -#define PMT_UPDATE_STREAM_DELETED 0x40 -#define PMT_UPDATE_NEW_CA_STREAM 0x80 -#define PMT_UPDATE_NEW_CAID 0x100 -#define PMT_UPDATE_CA_PROVIDER_CHANGE 0x200 -#define PMT_UPDATE_PARENT_PID 0x400 -#define PMT_UPDATE_CAID_DELETED 0x800 -#define PMT_REORDERED 0x1000 +#define PMT_UPDATE_AUDIO_TYPE 0x8 +#define PMT_UPDATE_FRAME_DURATION 0x10 +#define PMT_UPDATE_COMPOSITION_ID 0x20 +#define PMT_UPDATE_ANCILLARY_ID 0x40 +#define PMT_UPDATE_STREAM_DELETED 0x80 +#define PMT_UPDATE_NEW_CA_STREAM 0x100 +#define PMT_UPDATE_NEW_CAID 0x200 +#define PMT_UPDATE_CA_PROVIDER_CHANGE 0x400 +#define PMT_UPDATE_PARENT_PID 0x800 +#define PMT_UPDATE_CAID_DELETED 0x1000 +#define PMT_REORDERED 0x2000 /** * Add a CA descriptor @@ -397,6 +398,7 @@ psi_parse_pmt(service_t *t, const uint8_t *ptr, int len, int chksvcid, int position = 0; int tt_position = 1000; const char *lang = NULL; + uint8_t audio_type = 0; caid_t *c, *cn; @@ -529,6 +531,7 @@ psi_parse_pmt(service_t *t, const uint8_t *ptr, int len, int chksvcid, case DVB_DESC_LANGUAGE: lang = lang_code_get2((const char*)ptr, 3); + audio_type = ptr[3]; break; case DVB_DESC_TELETEXT: @@ -601,6 +604,11 @@ psi_parse_pmt(service_t *t, const uint8_t *ptr, int len, int chksvcid, memcpy(st->es_lang, lang, 4); } + if(st->es_audio_type != audio_type) { + update |= PMT_UPDATE_AUDIO_TYPE; + st->es_audio_type = audio_type; + } + if(composition_id != -1 && st->es_composition_id != composition_id) { st->es_composition_id = composition_id; update |= PMT_UPDATE_COMPOSITION_ID; @@ -756,7 +764,7 @@ psi_build_pmt(const streaming_start_t *ss, uint8_t *buf0, int maxlen, buf[0] = DVB_DESC_LANGUAGE; buf[1] = 4; memcpy(&buf[2],ssc->ssc_lang,3); - buf[5] = 0; /* Main audio */ + buf[5] = ssc->ssc_audio_type; dlen = 6; break; case SCT_DVBSUB: @@ -904,10 +912,26 @@ psi_caid2name(uint16_t caid) return buf; } +const char * +psi_audio_type2desc(uint8_t audio_type) +{ + /* From ISO 13818-1 - ISO 639 language descriptor */ + switch(audio_type) { + case 0: return ""; /* "Undefined" in the standard, but used for normal audio */ + case 1: return "Clean effects"; + case 2: return "Hearing impaired"; + case 3: return "Visually impaired commentary"; + } + + return "Reserved"; +} + /** * */ static struct strtab streamtypetab[] = { + { "NONE", SCT_NONE }, + { "UNKNOWN", SCT_UNKNOWN }, { "MPEG2VIDEO", SCT_MPEG2VIDEO }, { "MPEG2AUDIO", SCT_MPEG2AUDIO }, { "H264", SCT_H264 }, @@ -920,7 +944,7 @@ static struct strtab streamtypetab[] = { { "MPEGTS", SCT_MPEGTS }, { "TEXTSUB", SCT_TEXTSUB }, { "EAC3", SCT_EAC3 }, - { "AAC", SCT_MP4A }, + { "AAC", SCT_MP4A }, }; @@ -933,6 +957,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 @@ -959,6 +991,9 @@ psi_save_service_settings(htsmsg_t *m, service_t *t) if(st->es_lang[0]) htsmsg_add_str(sub, "language", st->es_lang); + if (SCT_ISAUDIO(st->es_type)) + htsmsg_add_u32(sub, "audio_type", st->es_audio_type); + if(st->es_type == SCT_CA) { caid_t *c; @@ -1109,6 +1144,11 @@ psi_load_service_settings(htsmsg_t *m, service_t *t) if((v = htsmsg_get_str(c, "language")) != NULL) strncpy(st->es_lang, lang_code_get(v), 3); + if (SCT_ISAUDIO(type)) { + if(!htsmsg_get_u32(c, "audio_type", &u32)) + st->es_audio_type = u32; + } + if(!htsmsg_get_u32(c, "position", &u32)) st->es_position = u32; diff --git a/src/psi.h b/src/psi.h index 34c9736b..5dfc98b5 100644 --- a/src/psi.h +++ b/src/psi.h @@ -46,6 +46,7 @@ int psi_build_pmt(const streaming_start_t *ss, uint8_t *buf, int maxlen, int version, int pcrpid); const char *psi_caid2name(uint16_t caid); +const char *psi_audio_type2desc(uint8_t audio_type); void psi_load_service_settings(htsmsg_t *m, struct service *t); void psi_save_service_settings(htsmsg_t *m, struct service *t); diff --git a/src/service.c b/src/service.c index 7851f5c2..c6666b5f 100644 --- a/src/service.c +++ b/src/service.c @@ -908,6 +908,7 @@ service_build_stream_start(service_t *t) ssc->ssc_type = st->es_type; memcpy(ssc->ssc_lang, st->es_lang, 4); + ssc->ssc_audio_type = st->es_audio_type; ssc->ssc_composition_id = st->es_composition_id; ssc->ssc_ancillary_id = st->es_ancillary_id; ssc->ssc_pid = st->es_pid; diff --git a/src/service.h b/src/service.h index d2f8b03b..8b96156e 100644 --- a/src/service.h +++ b/src/service.h @@ -82,6 +82,8 @@ typedef struct elementary_stream { uint16_t es_aspect_den; char es_lang[4]; /* ISO 639 2B 3-letter language code */ + uint8_t es_audio_type; /* Audio type */ + uint16_t es_composition_id; uint16_t es_ancillary_id; diff --git a/src/streaming.h b/src/streaming.h index 75dc7857..855be286 100644 --- a/src/streaming.h +++ b/src/streaming.h @@ -27,6 +27,7 @@ typedef struct streaming_start_component { int ssc_index; int ssc_type; char ssc_lang[4]; + uint8_t ssc_audio_type; uint16_t ssc_composition_id; uint16_t ssc_ancillary_id; uint16_t ssc_pid; diff --git a/src/tvheadend.h b/src/tvheadend.h index 55cdd517..4bc951dc 100644 --- a/src/tvheadend.h +++ b/src/tvheadend.h @@ -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) diff --git a/src/webui/extjs.c b/src/webui/extjs.c index 64aeb236..9478725d 100644 --- a/src/webui/extjs.c +++ b/src/webui/extjs.c @@ -1635,7 +1635,13 @@ extjs_servicedetails(http_connection_t *hc, case SCT_MP4A: case SCT_AAC: case SCT_MPEG2AUDIO: - htsmsg_add_str(c, "details", st->es_lang); + if (st->es_audio_type) { + snprintf(buf, sizeof(buf), "%s (%s)", st->es_lang, + psi_audio_type2desc(st->es_audio_type)); + htsmsg_add_str(c, "details", buf); + } else { + htsmsg_add_str(c, "details", st->es_lang); + } break; case SCT_DVBSUB: diff --git a/src/webui/static/app/tvadapters.js b/src/webui/static/app/tvadapters.js index 3661fabd..8da843f1 100644 --- a/src/webui/static/app/tvadapters.js +++ b/src/webui/static/app/tvadapters.js @@ -99,7 +99,7 @@ tvheadend.showTransportDetails = function(data) { win = new Ext.Window({ title : 'Service details for ' + data.title, layout : 'fit', - width : 400, + width : 450, height : 400, plain : true, bodyStyle : 'padding: 5px', diff --git a/src/webui/webui.c b/src/webui/webui.c index 1996de6f..8628fe6b 100644 --- a/src/webui/webui.c +++ b/src/webui/webui.c @@ -39,6 +39,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" @@ -555,6 +556,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 */ @@ -667,6 +709,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; @@ -696,6 +741,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; @@ -717,6 +770,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);