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:
parent
1ccfa89afe
commit
1308d3c68b
12 changed files with 1552 additions and 382 deletions
8
Makefile
8
Makefile
|
@ -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
208
src/avc.c
Normal 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
37
src/avc.h
Normal 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
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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
155
src/dvr/ebml.c
Normal 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
27
src/dvr/ebml.h
Normal 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
725
src/dvr/mkmux.c
Normal 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
36
src/dvr/mkmux.h
Normal 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__
|
283
src/plumbing/globalheaders.c
Normal file
283
src/plumbing/globalheaders.c
Normal 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);
|
||||
}
|
||||
|
29
src/plumbing/globalheaders.h
Normal file
29
src/plumbing/globalheaders.h
Normal 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__
|
Loading…
Add table
Reference in a new issue