From 60bdb16c9d3a84ac2eed29aef3ae38000447775c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?John=20T=C3=B6rblom?= Date: Thu, 9 May 2013 21:51:27 +0200 Subject: [PATCH] Added initial support for transcoding. --- Makefile | 3 +- configure | 4 + src/htsp_server.c | 69 +- src/libav.c | 49 ++ src/libav.h | 2 +- src/main.c | 4 + src/plumbing/transcoding.c | 1336 ++++++++++++++++++++++++++++++++++++ src/plumbing/transcoding.h | 42 ++ src/psi.c | 12 +- src/tvheadend.h | 3 +- src/webui/webui.c | 59 ++ 11 files changed, 1578 insertions(+), 5 deletions(-) create mode 100644 src/plumbing/transcoding.c create mode 100644 src/plumbing/transcoding.h 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 198e4d1b..fcec9de4 100755 --- 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 diff --git a/src/htsp_server.c b/src/htsp_server.c index f68d016d..2a670a69 100644 --- a/src/htsp_server.c +++ b/src/htsp_server.c @@ -46,7 +46,9 @@ #if ENABLE_TIMESHIFT #include "timeshift.h" #endif - +#if ENABLE_LIBAV +#include "plumbing/transcoding.h" +#endif #include #include "settings.h" #include @@ -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}, 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..d86519c8 100644 --- 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 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/webui.c b/src/webui/webui.c index bc7d5989..997b8829 100644 --- a/src/webui/webui.c +++ b/src/webui/webui.c @@ -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);