Add a native matroska muxer used for recording.

- Correctly writes global headers. Ticket #61
- Add support for recording AAC audio. Ticket #160
This commit is contained in:
Andreas Öman 2010-06-21 20:52:36 +00:00
parent 1ccfa89afe
commit 1308d3c68b
12 changed files with 1552 additions and 382 deletions

View file

@ -69,12 +69,16 @@ SRCS = src/main.c \
src/htsstr.c \
src/rawtsinput.c \
src/iptv_input.c \
src/avc.c \
SRCS += src/plumbing/tsfix.c \
src/plumbing/globalheaders.c \
SRCS += src/dvr/dvr_db.c \
src/dvr/dvr_rec.c \
src/dvr/dvr_autorec.c
src/dvr/dvr_autorec.c \
src/dvr/ebml.c \
src/dvr/mkmux.c \
SRCS-${CONFIG_LINUXDVB} += \
src/dvb/dvb.c \
@ -155,7 +159,7 @@ CURVERSION=$(shell cat ${BUILDDIR}/ver || echo "0")
# Common CFLAGS for all files
CFLAGS_com = -g -funsigned-char -O2
CFLAGS_com += -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64
CFLAGS_com += -D_FILE_OFFSET_BITS=64
CFLAGS_com += -I${BUILDDIR} -I${CURDIR}/src -I${CURDIR}
CFLAGS_com += -DHTS_VERSION=\"$(VERSION)\"

208
src/avc.c Normal file
View file

@ -0,0 +1,208 @@
/*
* AVC helper functions for muxers
* Copyright (c) 2006 Baptiste Coudurier <baptiste.coudurier@smartjog.com>
*
* This file is part of FFmpeg.
*
* FFmpeg is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* FFmpeg 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with FFmpeg; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "avc.h"
const uint8_t *avc_find_startcode(const uint8_t *p, const uint8_t *end)
{
const uint8_t *a = p + 4 - ((long)p & 3);
for( end -= 3; p < a && p < end; p++ ) {
if( p[0] == 0 && p[1] == 0 && p[2] == 1 )
return p;
}
for( end -= 3; p < end; p += 4 ) {
uint32_t x = *(const uint32_t*)p;
// if( (x - 0x01000100) & (~x) & 0x80008000 ) // little endian
// if( (x - 0x00010001) & (~x) & 0x00800080 ) // big endian
if( (x - 0x01010101) & (~x) & 0x80808080 ) { // generic
if( p[1] == 0 ) {
if( p[0] == 0 && p[2] == 1 )
return p-1;
if( p[2] == 0 && p[3] == 1 )
return p;
}
if( p[3] == 0 ) {
if( p[2] == 0 && p[4] == 1 )
return p+1;
if( p[4] == 0 && p[5] == 1 )
return p+2;
}
}
}
for( end += 3; p < end; p++ ) {
if( p[0] == 0 && p[1] == 0 && p[2] == 1 )
return p;
}
return end + 3;
}
int avc_parse_nal_units(ByteIOContext *pb, const uint8_t *buf_in, int size)
{
const uint8_t *p = buf_in;
const uint8_t *end = p + size;
const uint8_t *nal_start, *nal_end, *b;
printf("CONVERT SIZE %d\n", size);
size = 0;
nal_start = avc_find_startcode(p, end);
while (nal_start < end) {
while(!*(nal_start++));
nal_end = avc_find_startcode(nal_start, end);
printf("%4d bytes %5d : %d\n", nal_end - nal_start,
nal_start - buf_in,
nal_end - buf_in);
int l = nal_end - nal_start;
b = nal_start;
while(l > 3 && !(b[l-3] | b[l-2] | b[l-1]))
l--;
put_be32(pb, l);
put_buffer(pb, nal_start, l);
size += 4 + l;
nal_start = nal_end;
}
printf("size=%d\n", size);
return size;
}
int avc_parse_nal_units_buf(const uint8_t *buf_in, uint8_t **buf, int *size)
{
ByteIOContext *pb;
int ret = url_open_dyn_buf(&pb);
if(ret < 0)
return ret;
avc_parse_nal_units(pb, buf_in, *size);
av_freep(buf);
*size = url_close_dyn_buf(pb, buf);
return 0;
}
static uint32_t
RB32(const uint8_t *d)
{
return (d[0] << 24) | (d[1] << 16) | (d[2] << 8) | d[3];
}
static uint32_t
RB24(const uint8_t *d)
{
return (d[0] << 16) | (d[1] << 8) | d[2];
}
static int isom_write_avcc(ByteIOContext *pb, const uint8_t *data, int len)
{
if (len > 6) {
/* check for h264 start code */
if (RB32(data) == 0x00000001 ||
RB24(data) == 0x000001) {
uint8_t *buf=NULL, *end, *start;
uint32_t sps_size=0, pps_size=0;
uint8_t *sps=0, *pps=0;
int ret = avc_parse_nal_units_buf(data, &buf, &len);
if (ret < 0)
return ret;
start = buf;
end = buf + len;
/* look for sps and pps */
while (buf < end) {
unsigned int size;
uint8_t nal_type;
size = RB32(buf);
nal_type = buf[4] & 0x1f;
if (nal_type == 7) { /* SPS */
sps = buf + 4;
sps_size = size;
} else if (nal_type == 8) { /* PPS */
pps = buf + 4;
pps_size = size;
}
buf += size + 4;
}
if(!sps || !pps) {
av_free(start);
return -1;
}
put_byte(pb, 1); /* version */
put_byte(pb, sps[1]); /* profile */
put_byte(pb, sps[2]); /* profile compat */
put_byte(pb, sps[3]); /* level */
put_byte(pb, 0xff); /* 6 bits reserved (111111) + 2 bits nal size length - 1 (11) */
put_byte(pb, 0xe1); /* 3 bits reserved (111) + 5 bits number of sps (00001) */
put_be16(pb, sps_size);
put_buffer(pb, sps, sps_size);
put_byte(pb, 1); /* number of pps */
put_be16(pb, pps_size);
put_buffer(pb, pps, pps_size);
av_free(start);
} else {
put_buffer(pb, data, len);
}
}
return 0;
}
th_pkt_t *
avc_convert_pkt(th_pkt_t *src)
{
ByteIOContext *payload, *headers;
th_pkt_t *pkt = malloc(sizeof(th_pkt_t));
*pkt = *src;
pkt->pkt_refcount = 1;
url_open_dyn_buf(&payload);
avc_parse_nal_units(payload, pkt->pkt_payload, pkt->pkt_payloadlen);
pkt->pkt_payloadlen = url_close_dyn_buf(payload, &pkt->pkt_payload);
if(pkt->pkt_globaldata) {
url_open_dyn_buf(&headers);
isom_write_avcc(headers, pkt->pkt_globaldata, pkt->pkt_globaldata_len);
pkt->pkt_globaldata_len = url_close_dyn_buf(headers, &pkt->pkt_globaldata);
hexdump("annexB: ", src->pkt_globaldata, src->pkt_globaldata_len);
hexdump(" AVC: ", pkt->pkt_globaldata, pkt->pkt_globaldata_len);
}
pkt_ref_dec(src);
return pkt;
}

37
src/avc.h Normal file
View file

@ -0,0 +1,37 @@
/*
* AVC helper functions for muxers
* Copyright (c) 2006 Baptiste Coudurier <baptiste.coudurier@smartjog.com>
*
* This file is part of FFmpeg.
*
* FFmpeg is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* FFmpeg 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with FFmpeg; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef AVC_H__
#define AVC_H__
#include <stdint.h>
#include <libavformat/avformat.h>
#include <libavformat/avio.h>
#include "tvhead.h"
#include "packet.h"
const uint8_t *avc_find_startcode(const uint8_t *p, const uint8_t *end);
int avc_parse_nal_units(ByteIOContext *pb, const uint8_t *buf_in, int size);
int avc_parse_nal_units_buf(const uint8_t *buf_in, uint8_t **buf, int *size);
th_pkt_t *avc_convert_pkt(th_pkt_t *src);
#endif

View file

@ -68,8 +68,7 @@ typedef enum {
typedef enum {
DVR_RS_PENDING,
DVR_RS_WAIT_AUDIO_LOCK,
DVR_RS_WAIT_VIDEO_LOCK,
DVR_RS_WAIT_PROGRAM_START,
DVR_RS_RUNNING,
DVR_RS_COMMERCIAL,
DVR_RS_ERROR,
@ -154,21 +153,13 @@ typedef struct dvr_entry {
th_subscription_t *de_s;
streaming_queue_t de_sq;
streaming_target_t *de_tsfix;
streaming_target_t *de_gh;
/**
* Initialized upon SUBSCRIPTION_TRANSPORT_RUN
*/
int64_t de_ts_offset; /* Offset to compensate for PTS/DTS not beeing
0 at start of recording */
int64_t de_ts_com_start; /* Starttime for last/current commercial break */
int64_t de_ts_com_offset; /* Offset to subtract to PTS/DTS to skip over
all commercial breaks so far */
struct dvr_rec_stream_list de_streams;
AVFormatContext *de_fctx;
int de_header_written;
struct mk_mux *de_mkmux;
} dvr_entry_t;

View file

@ -63,11 +63,9 @@ dvr_entry_status(dvr_entry_t *de)
switch(de->de_rec_state) {
case DVR_RS_PENDING:
return "Pending start";
case DVR_RS_WAIT_AUDIO_LOCK:
return "Waiting for audio lock";
case DVR_RS_WAIT_VIDEO_LOCK:
return "Waiting for video lock";
return "Waiting for stream";
case DVR_RS_WAIT_PROGRAM_START:
return "Waiting for program start";
case DVR_RS_RUNNING:
return "Running";
case DVR_RS_COMMERCIAL:
@ -373,6 +371,7 @@ dvr_entry_remove(dvr_entry_t *de)
LIST_REMOVE(de, de_channel_link);
LIST_REMOVE(de, de_global_link);
de->de_channel = NULL;
dvrdb_changed();

View file

@ -34,25 +34,14 @@
#include "transports.h"
#include "plumbing/tsfix.h"
#include "plumbing/globalheaders.h"
static const AVRational mpeg_tc = {1, 90000};
typedef struct dvr_rec_stream {
LIST_ENTRY(dvr_rec_stream) drs_link;
int drs_source_index;
AVStream *drs_lavf_stream;
int drs_decoded;
} dvr_rec_stream_t;
#include "mkmux.h"
/**
*
*/
static void *dvr_thread(void *aux);
static void dvr_thread_new_pkt(dvr_entry_t *de, th_pkt_t *pkt);
static void dvr_spawn_postproc(dvr_entry_t *de);
static void dvr_thread_epilog(dvr_entry_t *de);
@ -87,7 +76,9 @@ dvr_rec_subscribe(dvr_entry_t *de)
else
weight = 300;
de->de_tsfix = tsfix_create(&de->de_sq.sq_st);
de->de_gh = globalheaders_create(&de->de_sq.sq_st);
de->de_tsfix = tsfix_create(de->de_gh);
de->de_s = subscription_create_from_channel(de->de_channel, weight,
buf, de->de_tsfix, 0);
@ -109,6 +100,7 @@ dvr_rec_unsubscribe(dvr_entry_t *de, int stopcode)
de->de_s = NULL;
tsfix_destroy(de->de_tsfix);
globalheaders_destroy(de->de_gh);
de->de_last_error = stopcode;
}
@ -247,12 +239,12 @@ pvr_generate_filename(dvr_entry_t *de)
while(1) {
if(stat(fullname, &st) == -1) {
tvhlog(LOG_DEBUG, "pvr", "File \"%s\" -- %s -- Using for recording",
tvhlog(LOG_DEBUG, "dvr", "File \"%s\" -- %s -- Using for recording",
fullname, strerror(errno));
break;
}
tvhlog(LOG_DEBUG, "pvr", "Overwrite protection, file \"%s\" exists",
tvhlog(LOG_DEBUG, "dvr", "Overwrite protection, file \"%s\" exists",
fullname);
tally++;
@ -281,7 +273,7 @@ dvr_rec_fatal_error(dvr_entry_t *de, const char *fmt, ...)
vsnprintf(msgbuf, sizeof(msgbuf), fmt, ap);
va_end(ap);
tvhlog(LOG_ERR, "pvr",
tvhlog(LOG_ERR, "dvr",
"Recording error: \"%s\": %s",
de->de_filename ?: de->de_title, msgbuf);
}
@ -293,6 +285,8 @@ dvr_rec_fatal_error(dvr_entry_t *de, const char *fmt, ...)
static void
dvr_rec_set_state(dvr_entry_t *de, dvr_rs_state_t newstate, int error)
{
if(de->de_rec_state == newstate)
return;
de->de_rec_state = newstate;
de->de_last_error = error;
if(error)
@ -307,16 +301,7 @@ static void
dvr_rec_start(dvr_entry_t *de, const streaming_start_t *ss)
{
const source_info_t *si = &ss->ss_si;
dvr_rec_stream_t *drs;
AVOutputFormat *fmt;
AVFormatContext *fctx;
AVCodecContext *ctx;
AVCodec *codec;
enum CodecID codec_id;
enum CodecType codec_type;
const char *codec_name;
char urlname[512];
int err, r;
const streaming_start_component_t *ssc;
int i;
if(pvr_generate_filename(de) != 0) {
@ -324,113 +309,13 @@ dvr_rec_start(dvr_entry_t *de, const streaming_start_t *ss)
return;
}
/* Find lavf format */
de->de_mkmux = mk_mux_create(de->de_filename, ss, de);
fmt = guess_format(dvr_format, NULL, NULL);
if(fmt == NULL) {
dvr_rec_fatal_error(de, "Unable to open file format \"%s\" for output",
dvr_format);
if(de->de_mkmux == NULL) {
dvr_rec_fatal_error(de, "Unable to open file");
return;
}
/* Init format context */
fctx = avformat_alloc_context();
av_strlcpy(fctx->title, de->de_ititle ?: "",
sizeof(fctx->title));
av_strlcpy(fctx->comment, de->de_desc ?: "",
sizeof(fctx->comment));
av_strlcpy(fctx->copyright, de->de_channel->ch_name,
sizeof(fctx->copyright));
fctx->oformat = fmt;
/* Open output file */
snprintf(urlname, sizeof(urlname), "file:%s", de->de_filename);
if((err = url_fopen(&fctx->pb, urlname, URL_WRONLY)) < 0) {
av_free(fctx);
dvr_rec_fatal_error(de, "Unable to create output file \"%s\". "
"FFmpeg error %d", urlname, err);
return;
}
av_set_parameters(fctx, NULL);
/**
* Setup each stream
*/
for(i = 0; i < ss->ss_num_components; i++) {
const streaming_start_component_t *ssc = &ss->ss_components[i];
switch(ssc->ssc_type) {
case SCT_AC3:
codec_id = CODEC_ID_AC3;
codec_type = CODEC_TYPE_AUDIO;
codec_name = "AC-3";
break;
case SCT_MPEG2AUDIO:
codec_id = CODEC_ID_MP2;
codec_type = CODEC_TYPE_AUDIO;
codec_name = "MPEG";
break;
case SCT_MPEG2VIDEO:
codec_id = CODEC_ID_MPEG2VIDEO;
codec_type = CODEC_TYPE_VIDEO;
codec_name = "MPEG-2";
break;
case SCT_H264:
codec_id = CODEC_ID_H264;
codec_type = CODEC_TYPE_VIDEO;
codec_name = "H264";
break;
default:
continue;
}
codec = avcodec_find_decoder(codec_id);
if(codec == NULL) {
tvhlog(LOG_ERR, "dvr",
"%s - Cannot find codec for %s, ignoring stream",
de->de_ititle, codec_name);
continue;
}
drs = calloc(1, sizeof(dvr_rec_stream_t));
drs->drs_source_index = ssc->ssc_index;
drs->drs_lavf_stream = av_new_stream(fctx, fctx->nb_streams);
ctx = drs->drs_lavf_stream->codec;
ctx->codec_id = codec_id;
ctx->codec_type = codec_type;
pthread_mutex_lock(&ffmpeg_lock);
r = avcodec_open(ctx, codec);
pthread_mutex_unlock(&ffmpeg_lock);
if(r < 0) {
tvhlog(LOG_ERR, "dvr",
"%s - Cannot open codec for %s, ignoring stream",
de->de_ititle, codec_name);
free(ctx);
continue;
}
memcpy(drs->drs_lavf_stream->language, ssc->ssc_lang, 4);
LIST_INSERT_HEAD(&de->de_streams, drs, drs_link);
}
de->de_fctx = fctx;
de->de_ts_offset = AV_NOPTS_VALUE;
tvhlog(LOG_INFO, "dvr", "%s from "
"adapter: \"%s\", "
"network: \"%s\", mux: \"%s\", provider: \"%s\", "
@ -441,6 +326,20 @@ dvr_rec_start(dvr_entry_t *de, const streaming_start_t *ss)
si->si_mux ?: "<N/A>",
si->si_provider ?: "<N/A>",
si->si_service ?: "<N/A>");
for(i = 0; i < ss->ss_num_components; i++) {
ssc = &ss->ss_components[i];
tvhlog(LOG_INFO, "dvr",
"%2d %-20s %-4s %5d x %-5d %-5d",
ssc->ssc_index,
streaming_component_type2txt(ssc->ssc_type),
ssc->ssc_lang,
ssc->ssc_width,
ssc->ssc_height,
ssc->ssc_frameduration);
}
}
@ -454,7 +353,6 @@ dvr_thread(void *aux)
streaming_queue_t *sq = &de->de_sq;
streaming_message_t *sm;
int run = 1;
th_pkt_t *pkt;
pthread_mutex_lock(&sq->sq_mutex);
@ -471,22 +369,17 @@ dvr_thread(void *aux)
switch(sm->sm_type) {
case SMT_PACKET:
pkt = sm->sm_data;
sm->sm_data = NULL;
if(dispatch_clock > de->de_start - (60 * de->de_start_extra)) {
pkt = pkt_merge_global(pkt);
dvr_thread_new_pkt(de, pkt);
dvr_rec_set_state(de, DVR_RS_RUNNING, 0);
mk_mux_write_pkt(de->de_mkmux, sm->sm_data);
sm->sm_data = NULL;
}
pkt_ref_dec(pkt);
break;
case SMT_START:
assert(de->de_fctx == NULL);
pthread_mutex_lock(&global_lock);
dvr_rec_set_state(de, DVR_RS_WAIT_PROGRAM_START, 0);
dvr_rec_start(de, sm->sm_data);
de->de_header_written = 0;
dvr_rec_set_state(de, DVR_RS_WAIT_AUDIO_LOCK, 0);
pthread_mutex_unlock(&global_lock);
break;
@ -498,7 +391,7 @@ dvr_thread(void *aux)
de->de_last_error = 0;
tvhlog(LOG_INFO,
"pvr", "Recording completed: \"%s\"",
"dvr", "Recording completed: \"%s\"",
de->de_filename ?: de->de_title);
} else {
@ -507,7 +400,7 @@ dvr_thread(void *aux)
dvr_rec_set_state(de, DVR_RS_ERROR, sm->sm_code);
tvhlog(LOG_ERR,
"pvr", "Recording stopped: \"%s\": %s",
"dvr", "Recording stopped: \"%s\": %s",
de->de_filename ?: de->de_title,
streaming_code2txt(sm->sm_code));
}
@ -533,7 +426,7 @@ dvr_thread(void *aux)
if(de->de_last_error != code) {
dvr_rec_set_state(de, DVR_RS_ERROR, code);
tvhlog(LOG_ERR,
"pvr", "Streaming error: \"%s\": %s",
"dvr", "Streaming error: \"%s\": %s",
de->de_filename ?: de->de_title,
streaming_code2txt(code));
}
@ -546,7 +439,7 @@ dvr_thread(void *aux)
dvr_rec_set_state(de, DVR_RS_ERROR, sm->sm_code);
tvhlog(LOG_ERR,
"pvr", "Recording unable to start: \"%s\": %s",
"dvr", "Recording unable to start: \"%s\": %s",
de->de_filename ?: de->de_title,
streaming_code2txt(sm->sm_code));
}
@ -568,192 +461,6 @@ dvr_thread(void *aux)
}
/**
* Check if all streams of the given type has been decoded
*/
static int
is_all_decoded(dvr_entry_t *de, enum CodecType type)
{
dvr_rec_stream_t *drs;
AVStream *st;
LIST_FOREACH(drs, &de->de_streams, drs_link) {
st = drs->drs_lavf_stream;
if(st->codec->codec->type == type && drs->drs_decoded == 0)
return 0;
}
return 1;
}
/**
*
*/
static void
dvr_thread_new_pkt(dvr_entry_t *de, th_pkt_t *pkt)
{
AVFormatContext *fctx = de->de_fctx;
dvr_rec_stream_t *drs;
AVStream *st, *stx;
AVCodecContext *ctx;
AVPacket avpkt;
void *abuf;
AVFrame pic;
int r, data_size, i;
void *buf;
size_t bufsize;
char txt[100];
int64_t pts, dts;
buf = pkt->pkt_payload;
bufsize = pkt->pkt_payloadlen;
LIST_FOREACH(drs, &de->de_streams, drs_link)
if(drs->drs_source_index == pkt->pkt_componentindex)
break;
if(drs == NULL)
return;
st = drs->drs_lavf_stream;
ctx = st->codec;
if(de->de_ts_offset == AV_NOPTS_VALUE)
de->de_ts_offset = pkt->pkt_dts;
switch(de->de_rec_state) {
default:
break;
case DVR_RS_WAIT_AUDIO_LOCK:
if(ctx->codec_type != CODEC_TYPE_AUDIO || drs->drs_decoded)
break;
data_size = AVCODEC_MAX_AUDIO_FRAME_SIZE;
abuf = av_malloc(data_size);
r = avcodec_decode_audio2(ctx, abuf, &data_size, buf, bufsize);
av_free(abuf);
if(r != 0 && data_size) {
tvhlog(LOG_DEBUG, "dvr", "%s - "
"Stream #%d: \"%s\" decoded a complete audio frame: "
"%d channels in %d Hz",
de->de_ititle, st->index, ctx->codec->name,
ctx->channels, ctx->sample_rate);
drs->drs_decoded = 1;
}
if(is_all_decoded(de, CODEC_TYPE_AUDIO))
dvr_rec_set_state(de, DVR_RS_WAIT_VIDEO_LOCK, 0);
break;
case DVR_RS_WAIT_VIDEO_LOCK:
if(ctx->codec_type != CODEC_TYPE_VIDEO || drs->drs_decoded)
break;
r = avcodec_decode_video(st->codec, &pic, &data_size, buf, bufsize);
if(r != 0 && data_size) {
tvhlog(LOG_DEBUG, "dvr", "%s - "
"Stream #%d: \"%s\" decoded a complete video frame: "
"%d x %d at %.2fHz",
de->de_ititle, st->index, ctx->codec->name,
ctx->width, st->codec->height,
(float)ctx->time_base.den / (float)ctx->time_base.num);
drs->drs_decoded = 1;
st->sample_aspect_ratio = st->codec->sample_aspect_ratio;
}
if(!is_all_decoded(de, CODEC_TYPE_VIDEO))
break;
/* All Audio & Video decoded, start recording */
dvr_rec_set_state(de, DVR_RS_RUNNING, 0);
if(!de->de_header_written) {
if(av_write_header(fctx)) {
tvhlog(LOG_ERR, "dvr",
"%s - Unable to write header",
de->de_ititle);
break;
}
de->de_header_written = 1;
tvhlog(LOG_INFO, "dvr",
"%s - Header written to file, stream dump:",
de->de_ititle);
for(i = 0; i < fctx->nb_streams; i++) {
stx = fctx->streams[i];
avcodec_string(txt, sizeof(txt), stx->codec, 1);
tvhlog(LOG_INFO, "dvr", "%s - Stream #%d: %s [%d/%d]",
de->de_ititle, i, txt,
stx->time_base.num, stx->time_base.den);
}
}
/* FALLTHRU */
case DVR_RS_RUNNING:
if(de->de_header_written == 0)
break;
if(pkt->pkt_commercial == COMMERCIAL_YES &&
st->codec->codec->type == CODEC_TYPE_VIDEO &&
pkt->pkt_frametype == PKT_I_FRAME) {
de->de_ts_com_start = pkt->pkt_dts;
dvr_rec_set_state(de, DVR_RS_COMMERCIAL, 0);
break;
}
outputpacket:
dts = pkt->pkt_dts - de->de_ts_offset - de->de_ts_com_offset;
pts = pkt->pkt_pts - de->de_ts_offset - de->de_ts_com_offset;
if(dts < 0 || pts < 0)
break;
av_init_packet(&avpkt);
avpkt.stream_index = st->index;
avpkt.dts = av_rescale_q(dts, mpeg_tc, st->time_base);
avpkt.pts = av_rescale_q(pts, mpeg_tc, st->time_base);
avpkt.data = buf;
avpkt.size = bufsize;
avpkt.duration = av_rescale_q(pkt->pkt_duration, mpeg_tc, st->time_base);
avpkt.flags = pkt->pkt_frametype >= PKT_P_FRAME ? 0 : PKT_FLAG_KEY;
r = av_interleaved_write_frame(fctx, &avpkt);
break;
case DVR_RS_COMMERCIAL:
if(pkt->pkt_commercial != COMMERCIAL_YES &&
st->codec->codec->type == CODEC_TYPE_VIDEO &&
pkt->pkt_frametype == PKT_I_FRAME) {
/* Switch out of commercial mode */
de->de_ts_com_offset += (pkt->pkt_dts - de->de_ts_com_start);
dvr_rec_set_state(de, DVR_RS_RUNNING, 0);
tvhlog(LOG_INFO, "dvr",
"%s - Skipped %" PRId64 " seconds of commercials",
de->de_ititle, (pkt->pkt_dts - de->de_ts_com_start) / 90000);
goto outputpacket;
}
break;
}
}
/**
*
*/
@ -811,39 +518,8 @@ dvr_spawn_postproc(dvr_entry_t *de)
static void
dvr_thread_epilog(dvr_entry_t *de)
{
AVFormatContext *fctx = de->de_fctx;
dvr_rec_stream_t *drs;
AVStream *st;
int i;
if(fctx == NULL)
return;
/* Write trailer if we've written anything at all */
if(de->de_header_written)
av_write_trailer(fctx);
/* Close lavf streams and format */
for(i = 0; i < fctx->nb_streams; i++) {
st = fctx->streams[i];
pthread_mutex_lock(&ffmpeg_lock);
avcodec_close(st->codec);
pthread_mutex_unlock(&ffmpeg_lock);
free(st->codec);
free(st);
}
url_fclose(fctx->pb);
free(fctx);
de->de_fctx = NULL;
while((drs = LIST_FIRST(&de->de_streams)) != NULL) {
LIST_REMOVE(drs, drs_link);
free(drs);
}
mk_mux_close(de->de_mkmux);
de->de_mkmux = NULL;
if(dvr_postproc)
dvr_spawn_postproc(de);

155
src/dvr/ebml.c Normal file
View file

@ -0,0 +1,155 @@
/*****************************************************************************
* matroska_ebml.c:
*****************************************************************************
* Copyright (C) 2005 Mike Matsnev
* Copyright (C) 2010 Andreas Öman
*
* 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 2 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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111, USA.
*****************************************************************************/
#include <assert.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include "ebml.h"
void
ebml_append_id(htsbuf_queue_t *q, uint32_t id)
{
uint8_t u8[4] = {id >> 24, id >> 16, id >> 8, id};
if(u8[0])
return htsbuf_append(q, u8, 4);
if(u8[1])
return htsbuf_append(q, u8+1, 3);
if(u8[2])
return htsbuf_append(q, u8+2, 2);
return htsbuf_append(q, u8+3, 1);
}
void
ebml_append_size(htsbuf_queue_t *q, uint32_t size)
{
uint8_t u8[5] = { 0x08, size >> 24, size >> 16, size >> 8, size };
if(size < 0x7f) {
u8[4] |= 0x80;
return htsbuf_append(q, u8+4, 1);
}
if(size < 0x3fff) {
u8[3] |= 0x40;
return htsbuf_append(q, u8+3, 2);
}
if(size < 0x1fffff) {
u8[2] |= 0x20;
return htsbuf_append(q, u8+2, 3);
}
if(size < 0x0fffffff) {
u8[1] |= 0x10;
return htsbuf_append(q, u8+1, 4);
}
return htsbuf_append(q, u8, 5);
}
void
ebml_append_bin(htsbuf_queue_t *q, unsigned id, const void *data, size_t len)
{
ebml_append_id(q, id);
ebml_append_size(q, len);
return htsbuf_append(q, data, len);
}
void
ebml_append_string(htsbuf_queue_t *q, unsigned id, const char *str)
{
return ebml_append_bin(q, id, str, strlen(str));
}
void
ebml_append_uint(htsbuf_queue_t *q, unsigned id, int64_t ui)
{
uint8_t u8[8] = {ui >> 56, ui >> 48, ui >> 40, ui >> 32,
ui >> 24, ui >> 16, ui >> 8, ui };
int i = 0;
while( i < 7 && !u8[i] )
++i;
return ebml_append_bin(q, id, u8 + i, 8 - i);
}
void
ebml_append_float(htsbuf_queue_t *q, unsigned id, float f)
{
union
{
float f;
unsigned u;
} u;
unsigned char c_f[4];
u.f = f;
c_f[0] = u.u >> 24;
c_f[1] = u.u >> 16;
c_f[2] = u.u >> 8;
c_f[3] = u.u;
return ebml_append_bin(q, id, c_f, 4);
}
void
ebml_append_master(htsbuf_queue_t *q, uint32_t id, htsbuf_queue_t *p)
{
ebml_append_id(q, id);
ebml_append_size(q, p->hq_size);
htsbuf_appendq(q, p);
free(p);
}
void
ebml_append_void(htsbuf_queue_t *q)
{
char n[1] = {0};
ebml_append_bin(q, 0xec, n, 1);
}
void
ebml_append_pad(htsbuf_queue_t *q, size_t pad)
{
assert(pad > 2);
pad -= 2;
assert(pad < 0x3fff);
pad -= pad > 0x7e;
void *data = alloca(pad);
memset(data, 0, pad);
ebml_append_bin(q, 0xec, data, pad);
}
void
ebml_append_idid(htsbuf_queue_t *q, uint32_t id0, uint32_t id)
{
uint8_t u8[4] = {id >> 24, id >> 16, id >> 8, id};
if(u8[0])
return ebml_append_bin(q, id0, u8, 4);
if(u8[1])
return ebml_append_bin(q, id0, u8+1, 3);
if(u8[2])
return ebml_append_bin(q, id0, u8+2, 2);
return ebml_append_bin(q, id0, u8+3, 1);
}

27
src/dvr/ebml.h Normal file
View file

@ -0,0 +1,27 @@
#ifndef EBML_H__
#define EBML_H__
#include "htsbuf.h"
void ebml_append_id(htsbuf_queue_t *q, uint32_t id);
void ebml_append_size(htsbuf_queue_t *q, uint32_t size);
void ebml_append_bin(htsbuf_queue_t *q, unsigned id,
const void *data, size_t len);
void ebml_append_string(htsbuf_queue_t *q, unsigned id, const char *str);
void ebml_append_uint(htsbuf_queue_t *q, unsigned id, int64_t ui);
void ebml_append_float(htsbuf_queue_t *q, unsigned id, float f);
void ebml_append_master(htsbuf_queue_t *q, uint32_t id, htsbuf_queue_t *p);
void ebml_append_void(htsbuf_queue_t *q);
void ebml_append_pad(htsbuf_queue_t *q, size_t pad);
void ebml_append_idid(htsbuf_queue_t *q, uint32_t id0, uint32_t id);
#endif // EBML_H__

725
src/dvr/mkmux.c Normal file
View file

@ -0,0 +1,725 @@
/*
* Matroska muxer
* Copyright (C) 2005 Mike Matsnev
* Copyright (C) 2010 Andreas Öman
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <assert.h>
#include "tvhead.h"
#include "streaming.h"
#include "dvr.h"
#include "mkmux.h"
#include "ebml.h"
TAILQ_HEAD(mk_cue_queue, mk_cue);
#define MATROSKA_TIMESCALE 1000000 // in nS
static const AVRational mpeg_tc = {1, 90000};
static const AVRational mkv_tc = {1, 1000};
static const AVRational ns_tc = {1, 1000000000};
/**
*
*/
typedef struct mk_track {
int index;
int enabled;
int merge;
int type;
int tracknum;
int64_t nextpts;
} mk_track;
/**
*
*/
struct mk_cue {
TAILQ_ENTRY(mk_cue) link;
int64_t ts;
int tracknum;
off_t cluster_pos;
};
/**
*
*/
struct mk_mux {
int fd;
int error;
off_t fdpos; // Current position in file
mk_track *tracks;
int ntracks;
int64_t totduration;
htsbuf_queue_t *cluster;
int64_t cluster_tc;
off_t cluster_pos;
off_t segment_pos;
off_t segmentinfo_pos;
off_t trackinfo_pos;
off_t metadata_pos;
off_t cue_pos;
int addcue;
struct mk_cue_queue cues;
char uuid[16];
char *title;
};
/**
*
*/
static htsbuf_queue_t *
mk_build_ebmlheader(void)
{
htsbuf_queue_t *q = htsbuf_queue_alloc(0);
ebml_append_uint(q, 0x4286, 1);
ebml_append_uint(q, 0x42f7, 1);
ebml_append_uint(q, 0x42f2, 4);
ebml_append_uint(q, 0x42f3, 8);
ebml_append_string(q, 0x4282, "matroska");
ebml_append_uint(q, 0x4287, 2);
ebml_append_uint(q, 0x4285, 2);
return q;
}
/**
*
*/
static void
getuuid(char *id)
{
int r, fd = open("/dev/urandom", O_RDONLY);
if(fd != -1) {
r = read(fd, id, 16);
close(fd);
if(r == 16)
return;
}
memset(id, 0x55, 16);
}
/**
*
*/
static htsbuf_queue_t *
mk_build_segment_info(mk_mux_t *mkm)
{
htsbuf_queue_t *q = htsbuf_queue_alloc(0);
extern char *htsversion_full;
char app[128];
snprintf(app, sizeof(app), "HTS Tvheadend %s", htsversion_full);
ebml_append_bin(q, 0x73a4, mkm->uuid, sizeof(mkm->uuid));
ebml_append_string(q, 0x7ba9, mkm->title);
ebml_append_string(q, 0x4d80, "HTS Tvheadend Matroska muxer");
ebml_append_string(q, 0x5741, app);
ebml_append_uint(q, 0x2ad7b1, MATROSKA_TIMESCALE);
if(mkm->totduration)
ebml_append_float(q, 0x4489, (float)mkm->totduration);
else
ebml_append_pad(q, 7); // Must be equal to floatingpoint duration
return q;
}
/**
*
*/
static htsbuf_queue_t *
mk_build_tracks(mk_mux_t *mkm, const struct streaming_start *ss)
{
const streaming_start_component_t *ssc;
const char *codec_id;
int i, tracktype;
htsbuf_queue_t *q = htsbuf_queue_alloc(0), *t;
int tracknum = 0;
mkm->tracks = calloc(1, sizeof(mk_track) * ss->ss_num_components);
mkm->ntracks = ss->ss_num_components;
for(i = 0; i < ss->ss_num_components; i++) {
ssc = &ss->ss_components[i];
mkm->tracks[i].index = ssc->ssc_index;
mkm->tracks[i].type = ssc->ssc_type;
mkm->tracks[i].nextpts = AV_NOPTS_VALUE;
switch(ssc->ssc_type) {
case SCT_MPEG2VIDEO:
tracktype = 1;
codec_id = "V_MPEG2";
mkm->tracks[i].merge = 1;
break;
case SCT_H264:
tracktype = 1;
codec_id = "V_MPEG4/ISO/AVC";
break;
case SCT_MPEG2AUDIO:
tracktype = 2;
codec_id = "A_MPEG/L2";
break;
case SCT_AC3:
tracktype = 2;
codec_id = "A_AC3";
break;
case SCT_AAC:
tracktype = 2;
codec_id = "A_AAC";
break;
default:
continue;
}
mkm->tracks[i].enabled = 1;
mkm->tracks[i].tracknum = tracknum;
tracknum++;
t = htsbuf_queue_alloc(0);
ebml_append_uint(t, 0xd7, mkm->tracks[i].tracknum);
ebml_append_uint(t, 0x73c5, mkm->tracks[i].tracknum);
ebml_append_uint(t, 0x83, tracktype);
ebml_append_uint(t, 0x9c, 0); // Lacing
ebml_append_string(t, 0x86, codec_id);
if(ssc->ssc_lang[0])
ebml_append_string(t, 0x22b59c, ssc->ssc_lang);
if(ssc->ssc_global_header_len) {
switch(ssc->ssc_type) {
case SCT_H264:
case SCT_MPEG2VIDEO:
case SCT_AAC:
ebml_append_bin(t, 0x63a2, ssc->ssc_global_header,
ssc->ssc_global_header_len);
break;
}
}
if(ssc->ssc_frameduration) {
int d = av_rescale_q(ssc->ssc_frameduration, mpeg_tc, ns_tc);
ebml_append_uint(t, 0x23e383, d);
}
if(SCT_ISVIDEO(ssc->ssc_type)) {
htsbuf_queue_t *vi = htsbuf_queue_alloc(0);
ebml_append_uint(vi, 0xb0, ssc->ssc_width);
ebml_append_uint(vi, 0xba, ssc->ssc_height);
ebml_append_master(t, 0xe0, vi);
}
ebml_append_master(q, 0xae, t);
}
return q;
}
/**
*
*/
static void
mk_write_to_fd(mk_mux_t *mkm, htsbuf_queue_t *hq)
{
htsbuf_data_t *hd;
int i = 0;
TAILQ_FOREACH(hd, &hq->hq_q, hd_link)
i++;
struct iovec *iov = alloca(sizeof(struct iovec) * i);
i = 0;
TAILQ_FOREACH(hd, &hq->hq_q, hd_link) {
iov[i ].iov_base = hd->hd_data + hd->hd_data_off;
iov[i++].iov_len = hd->hd_data_len - hd->hd_data_off;
}
if(writev(mkm->fd, iov, i) != hq->hq_size) {
mkm->error = errno;
} else {
mkm->fdpos += hq->hq_size;
}
}
/**
*
*/
static void
mk_write_queue(mk_mux_t *mkm, htsbuf_queue_t *q)
{
if(!mkm->error)
mk_write_to_fd(mkm, q);
htsbuf_queue_flush(q);
}
/**
*
*/
static void
mk_write_master(mk_mux_t *mkm, uint32_t id, htsbuf_queue_t *p)
{
htsbuf_queue_t q;
htsbuf_queue_init(&q, 0);
ebml_append_master(&q, id, p);
mk_write_queue(mkm, &q);
}
/**
*
*/
static void
mk_write_segment(mk_mux_t *mkm)
{
htsbuf_queue_t q;
char ff = 0xff;
htsbuf_queue_init(&q, 0);
ebml_append_id(&q, 0x18538067);
htsbuf_append(&q, &ff, 1);
mk_write_queue(mkm, &q);
}
/**
*
*/
static htsbuf_queue_t *
build_tag_string(const char *name, const char *value,
int targettype, const char *targettypename)
{
htsbuf_queue_t *q = htsbuf_queue_alloc(0);
htsbuf_queue_t *t = htsbuf_queue_alloc(0);
htsbuf_queue_t *st = htsbuf_queue_alloc(0);
if(targettype == 0 && targettypename == NULL) {
ebml_append_void(t);
} else {
if(targettype)
ebml_append_uint(t, 0x68ca, targettype);
if(targettypename)
ebml_append_string(t, 0x63ca, targettypename);
}
ebml_append_master(q, 0x63c0, t);
ebml_append_string(st, 0x45a3, name);
ebml_append_string(st, 0x4487, value);
ebml_append_master(q, 0x67c8, st);
return q;
}
/**
*
*/
static htsbuf_queue_t *
build_tag_int(const char *name, int value,
int targettype, const char *targettypename)
{
char str[64];
snprintf(str, sizeof(str), "%d", value);
return build_tag_string(name, str, targettype, targettypename);
}
/**
*
*/
static void
addtag(htsbuf_queue_t *q, htsbuf_queue_t *t)
{
ebml_append_master(q, 0x7373, t);
}
/**
*
*/
static htsbuf_queue_t *
mk_build_metadata(const dvr_entry_t *de)
{
htsbuf_queue_t *q = htsbuf_queue_alloc(0);
if(de->de_channel != NULL)
addtag(q, build_tag_string("TVCHANNEL", de->de_channel->ch_name, 0, NULL));
if(de->de_episode.ee_onscreen)
addtag(q, build_tag_string("SYNOPSIS",
de->de_episode.ee_onscreen, 0, NULL));
if(de->de_title != NULL)
addtag(q, build_tag_string("SUMMARY", de->de_title, 0, NULL));
if(de->de_episode.ee_season)
addtag(q, build_tag_int("PART_NUMBER", de->de_episode.ee_season,
60, "SEASON"));
if(de->de_episode.ee_episode)
addtag(q, build_tag_int("PART_NUMBER", de->de_episode.ee_episode,
0, NULL));
if(de->de_episode.ee_part)
addtag(q, build_tag_int("PART_NUMBER", de->de_episode.ee_part,
40, "PART"));
return q;
}
/**
*
*/
static htsbuf_queue_t *
mk_build_one_metaseek(mk_mux_t *mkm, uint32_t id, off_t pos)
{
htsbuf_queue_t *q = htsbuf_queue_alloc(0);
ebml_append_idid(q, 0x53ab, id);
ebml_append_uint(q, 0x53ac, pos - mkm->segment_pos);
return q;
}
/**
*
*/
static htsbuf_queue_t *
mk_build_metaseek(mk_mux_t *mkm)
{
htsbuf_queue_t *q = htsbuf_queue_alloc(0);
if(mkm->segmentinfo_pos)
ebml_append_master(q, 0x4dbb,
mk_build_one_metaseek(mkm, 0x1549a966,
mkm->segmentinfo_pos));
if(mkm->trackinfo_pos)
ebml_append_master(q, 0x4dbb,
mk_build_one_metaseek(mkm, 0x1654ae6b,
mkm->trackinfo_pos));
if(mkm->metadata_pos)
ebml_append_master(q, 0x4dbb,
mk_build_one_metaseek(mkm, 0x1254c367,
mkm->metadata_pos));
if(mkm->cue_pos)
ebml_append_master(q, 0x4dbb,
mk_build_one_metaseek(mkm, 0x1c53bb6b,
mkm->cue_pos));
return q;
}
/**
*
*/
static void
mk_write_metaseek(mk_mux_t *mkm, int first)
{
htsbuf_queue_t q;
htsbuf_queue_init(&q, 0);
ebml_append_master(&q, 0x114d9b74, mk_build_metaseek(mkm));
assert(q.hq_size < 498);
ebml_append_pad(&q, 500 - q.hq_size);
if(first) {
mk_write_to_fd(mkm, &q);
} else {
off_t prev = mkm->fdpos;
if(lseek(mkm->fd, mkm->segment_pos, SEEK_SET) == (off_t) -1)
mkm->error = errno;
mk_write_queue(mkm, &q);
mkm->fdpos = prev;
if(lseek(mkm->fd, mkm->fdpos, SEEK_SET) == (off_t) -1)
mkm->error = errno;
}
htsbuf_queue_flush(&q);
}
/**
*
*/
mk_mux_t *
mk_mux_create(const char *filename,
const struct streaming_start *ss,
const struct dvr_entry *de)
{
mk_mux_t *mkm;
int fd;
fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0777);
if(fd == -1)
return NULL;
mkm = calloc(1, sizeof(struct mk_mux));
getuuid(mkm->uuid);
mkm->fd = fd;
mkm->title = strdup(de->de_title);
TAILQ_INIT(&mkm->cues);
mk_write_master(mkm, 0x1a45dfa3, mk_build_ebmlheader());
mk_write_segment(mkm);
mkm->segment_pos = mkm->fdpos;
mk_write_metaseek(mkm, 1); // Must be first in segment
mkm->segmentinfo_pos = mkm->fdpos;
mk_write_master(mkm, 0x1549a966, mk_build_segment_info(mkm));
mkm->trackinfo_pos = mkm->fdpos;
mk_write_master(mkm, 0x1654ae6b, mk_build_tracks(mkm, ss));
mkm->metadata_pos = mkm->fdpos;
mk_write_master(mkm, 0x1254c367, mk_build_metadata(de));
mk_write_metaseek(mkm, 0);
return mkm;
}
/**
*
*/
static void
addcue(mk_mux_t *mkm, int64_t pts, int tracknum)
{
struct mk_cue *mc = malloc(sizeof(struct mk_cue));
mc->ts = pts;
mc->tracknum = tracknum;
mc->cluster_pos = mkm->cluster_pos;
TAILQ_INSERT_TAIL(&mkm->cues, mc, link);
}
/**
*
*/
static void
mk_close_cluster(mk_mux_t *mkm)
{
if(mkm->cluster != NULL)
mk_write_master(mkm, 0x1f43b675, mkm->cluster);
mkm->cluster = NULL;
}
/**
*
*/
static void
mk_write_frame_i(mk_mux_t *mkm, mk_track *t, th_pkt_t *pkt)
{
int64_t pts = pkt->pkt_pts, delta, nxt;
unsigned char c_delta_flags[3];
int keyframe = pkt->pkt_frametype < PKT_P_FRAME;
int skippable = pkt->pkt_frametype == PKT_B_FRAME;
int vkeyframe = SCT_ISVIDEO(t->type) && keyframe;
uint8_t *data;
size_t len;
const int clusersizemax = 2000000;
if(pts == AV_NOPTS_VALUE)
// This is our best guess, it might be wrong but... oh well
pts = t->nextpts;
if(pts != AV_NOPTS_VALUE) {
t->nextpts = pts + (pkt->pkt_duration >> pkt->pkt_field);
nxt = av_rescale_q(t->nextpts, mpeg_tc, mkv_tc);
pts = av_rescale_q(pts, mpeg_tc, mkv_tc);
if(mkm->totduration < nxt)
mkm->totduration = nxt;
delta = pts - mkm->cluster_tc;
if(delta > 32767ll || delta < -32768ll)
mk_close_cluster(mkm);
else if(vkeyframe && (delta > 30000ll || delta < -30000ll))
mk_close_cluster(mkm);
} else {
return;
}
if(vkeyframe && mkm->cluster && mkm->cluster->hq_size > clusersizemax/4)
mk_close_cluster(mkm);
else if(mkm->cluster && mkm->cluster->hq_size > clusersizemax)
mk_close_cluster(mkm);
if(mkm->cluster == NULL) {
mkm->cluster_tc = pts;
mkm->cluster = htsbuf_queue_alloc(0);
mkm->cluster_pos = mkm->fdpos;
mkm->addcue = 1;
ebml_append_uint(mkm->cluster, 0xe7, mkm->cluster_tc);
delta = 0;
}
if(mkm->addcue && vkeyframe) {
mkm->addcue = 0;
addcue(mkm, pts, t->tracknum);
}
data = pkt->pkt_payload;
len = pkt->pkt_payloadlen;
if(t->type == SCT_AAC) {
// Skip ADTS header
if(len < 7)
return;
len -= 7;
data += 7;
}
ebml_append_id(mkm->cluster, 0xa3 ); // SimpleBlock
ebml_append_size(mkm->cluster, len + 4);
ebml_append_size(mkm->cluster, t->tracknum);
c_delta_flags[0] = delta >> 8;
c_delta_flags[1] = delta;
c_delta_flags[2] = (keyframe << 7) | skippable;
htsbuf_append(mkm->cluster, c_delta_flags, 3);
htsbuf_append(mkm->cluster, data, len);
}
/**
*
*/
void
mk_mux_write_pkt(mk_mux_t *mkm, struct th_pkt *pkt)
{
int i;
mk_track *t = NULL;
for(i = 0; i < mkm->ntracks;i++) {
if(mkm->tracks[i].index == pkt->pkt_componentindex &&
mkm->tracks[i].enabled) {
t = &mkm->tracks[i];
break;
}
}
if(t != NULL) {
if(t->merge)
pkt = pkt_merge_global(pkt);
mk_write_frame_i(mkm, t, pkt);
}
pkt_ref_dec(pkt);
}
/**
*
*/
static void
mk_write_cues(mk_mux_t *mkm)
{
struct mk_cue *mc;
htsbuf_queue_t *q, *c, *p;
if(TAILQ_FIRST(&mkm->cues) == NULL)
return;
q = htsbuf_queue_alloc(0);
while((mc = TAILQ_FIRST(&mkm->cues)) != NULL) {
TAILQ_REMOVE(&mkm->cues, mc, link);
c = htsbuf_queue_alloc(0);
ebml_append_uint(c, 0xb3, mc->ts);
p = htsbuf_queue_alloc(0);
ebml_append_uint(p, 0xf7, mc->tracknum);
ebml_append_uint(p, 0xf1, mc->cluster_pos - mkm->segment_pos);
ebml_append_master(c, 0xb7, p);
ebml_append_master(q, 0xbb, c);
free(mc);
}
mkm->cue_pos = mkm->fdpos;
mk_write_master(mkm, 0x1c53bb6b, q);
}
/**
*
*/
void
mk_mux_close(mk_mux_t *mkm)
{
mk_close_cluster(mkm);
mk_write_cues(mkm);
mk_write_metaseek(mkm, 0);
// Rewrite segment info to update duration
if(lseek(mkm->fd, mkm->segmentinfo_pos, SEEK_SET) == mkm->segmentinfo_pos)
mk_write_master(mkm, 0x1549a966, mk_build_segment_info(mkm));
close(mkm->fd);
free(mkm->tracks);
free(mkm->title);
free(mkm);
}

36
src/dvr/mkmux.h Normal file
View file

@ -0,0 +1,36 @@
/*
* Matroska muxer
* Copyright (C) 2010 Andreas Öman
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MKMUX_H__
#define MKMUX_H__
typedef struct mk_mux mk_mux_t;
struct streaming_start;
struct dvr_entry;
struct th_pkt;
mk_mux_t *mk_mux_create(const char *filename,
const struct streaming_start *ss,
const struct dvr_entry *de);
void mk_mux_write_pkt(mk_mux_t *mkm, struct th_pkt *pkt);
void mk_mux_close(mk_mux_t *mk_mux);
#endif // MKMUX_H__

View file

@ -0,0 +1,283 @@
/**
* Global header modification
* Copyright (C) 2010 Andreas Öman
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <assert.h>
#include "tvhead.h"
#include "streaming.h"
#include "globalheaders.h"
#include "avc.h"
typedef struct globalheaders {
streaming_target_t gh_input;
streaming_target_t *gh_output;
struct th_pktref_queue gh_holdq;
streaming_start_t *gh_ss;
int gh_passthru;
} globalheaders_t;
/**
*
*/
static void
gh_flush(globalheaders_t *gh)
{
if(gh->gh_ss != NULL)
streaming_start_unref(gh->gh_ss);
gh->gh_ss = NULL;
pktref_clear_queue(&gh->gh_holdq);
}
/**
*
*/
static void
apply_header(streaming_start_component_t *ssc, th_pkt_t *pkt)
{
uint8_t *d;
if(ssc->ssc_frameduration == 0 && pkt->pkt_duration != 0)
ssc->ssc_frameduration = pkt->pkt_duration;
if(ssc->ssc_global_header != NULL)
return;
switch(ssc->ssc_type) {
case SCT_AAC:
d = ssc->ssc_global_header = malloc(2);
ssc->ssc_global_header_len = 2;
const int profile = 2;
d[0] = (profile << 3) | ((pkt->pkt_sri & 0xe) >> 1);
d[1] = ((pkt->pkt_sri & 0x1) << 7) | (pkt->pkt_channels << 3);
break;
case SCT_H264:
case SCT_MPEG2VIDEO:
if(pkt->pkt_globaldata != NULL) {
ssc->ssc_global_header = malloc(pkt->pkt_globaldata_len +
FF_INPUT_BUFFER_PADDING_SIZE);
memcpy(ssc->ssc_global_header, pkt->pkt_globaldata,
pkt->pkt_globaldata_len);
ssc->ssc_global_header_len = pkt->pkt_globaldata_len;
}
break;
}
}
/**
*
*/
static int
headers_complete(globalheaders_t *gh)
{
streaming_start_t *ss = gh->gh_ss;
streaming_start_component_t *ssc;
int i;
assert(ss != NULL);
for(i = 0; i < ss->ss_num_components; i++) {
ssc = &ss->ss_components[i];
if((SCT_ISAUDIO(ssc->ssc_type) || SCT_ISVIDEO(ssc->ssc_type)) &&
ssc->ssc_frameduration == 0)
return 0;
if(ssc->ssc_global_header == NULL &&
(ssc->ssc_type == SCT_H264 ||
ssc->ssc_type == SCT_MPEG2VIDEO ||
ssc->ssc_type == SCT_AAC))
return 0;
}
return 1;
}
/**
*
*/
static th_pkt_t *
convertpkt(streaming_start_component_t *ssc, th_pkt_t *pkt)
{
switch(ssc->ssc_type) {
case SCT_H264:
// return avc_convert_pkt(pkt);
default:
return pkt;
}
}
/**
*
*/
static void
gh_hold(globalheaders_t *gh, streaming_message_t *sm)
{
th_pkt_t *pkt;
th_pktref_t *pr;
streaming_start_component_t *ssc;
switch(sm->sm_type) {
case SMT_PACKET:
pkt = sm->sm_data;
ssc = streaming_start_component_find_by_index(gh->gh_ss,
pkt->pkt_componentindex);
assert(ssc != NULL);
pkt = convertpkt(ssc, pkt);
apply_header(ssc, pkt);
pr = pktref_create(pkt);
TAILQ_INSERT_TAIL(&gh->gh_holdq, pr, pr_link);
free(sm);
if(!headers_complete(gh))
break;
// Send our modified start
sm = streaming_msg_create_data(SMT_START,
streaming_start_copy(gh->gh_ss));
streaming_target_deliver2(gh->gh_output, sm);
// Send all pending packets
while((pr = TAILQ_FIRST(&gh->gh_holdq)) != NULL) {
TAILQ_REMOVE(&gh->gh_holdq, pr, pr_link);
sm = streaming_msg_create_pkt(pr->pr_pkt);
streaming_target_deliver2(gh->gh_output, sm);
pkt_ref_dec(pr->pr_pkt);
free(pr);
}
gh->gh_passthru = 1;
break;
case SMT_START:
assert(gh->gh_ss == NULL);
gh->gh_ss = streaming_start_copy(sm->sm_data);
streaming_msg_free(sm);
break;
case SMT_STOP:
gh_flush(gh);
streaming_msg_free(sm);
break;
case SMT_EXIT:
case SMT_TRANSPORT_STATUS:
case SMT_NOSTART:
case SMT_MPEGTS:
streaming_target_deliver2(gh->gh_output, sm);
break;
}
}
/**
*
*/
static void
gh_pass(globalheaders_t *gh, streaming_message_t *sm)
{
th_pkt_t *pkt;
streaming_start_component_t *ssc;
switch(sm->sm_type) {
case SMT_START:
abort(); // Should not happen
case SMT_STOP:
gh->gh_passthru = 0;
gh_flush(gh);
// FALLTHRU
case SMT_EXIT:
case SMT_TRANSPORT_STATUS:
case SMT_NOSTART:
case SMT_MPEGTS:
streaming_target_deliver2(gh->gh_output, sm);
break;
case SMT_PACKET:
pkt = sm->sm_data;
ssc = streaming_start_component_find_by_index(gh->gh_ss,
pkt->pkt_componentindex);
sm->sm_data = convertpkt(ssc, pkt);
streaming_target_deliver2(gh->gh_output, sm);
break;
}
}
/**
*
*/
static void
globalheaders_input(void *opaque, streaming_message_t *sm)
{
globalheaders_t *gh = opaque;
if(gh->gh_passthru)
gh_pass(gh, sm);
else
gh_hold(gh, sm);
}
/**
*
*/
streaming_target_t *
globalheaders_create(streaming_target_t *output)
{
globalheaders_t *gh = calloc(1, sizeof(globalheaders_t));
TAILQ_INIT(&gh->gh_holdq);
gh->gh_output = output;
streaming_target_init(&gh->gh_input, globalheaders_input, gh, 0);
return &gh->gh_input;
}
/**
*
*/
void
globalheaders_destroy(streaming_target_t *pad)
{
globalheaders_t *gh = (globalheaders_t *)pad;
gh_flush(gh);
free(gh);
}

View file

@ -0,0 +1,29 @@
/**
* Global header modification
* Copyright (C) 2010 Andreas Öman
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef GLOBALHEADERS_H__
#define GLOBALHEADERS_H__
#include "tvhead.h"
streaming_target_t *globalheaders_create(streaming_target_t *output);
void globalheaders_destroy(streaming_target_t *gh);
#endif // GLOBALHEADERS_H__