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