diff --git a/Makefile b/Makefile index cc452563..500afcb8 100644 --- a/Makefile +++ b/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)\" diff --git a/src/avc.c b/src/avc.c new file mode 100644 index 00000000..4418de77 --- /dev/null +++ b/src/avc.c @@ -0,0 +1,208 @@ +/* + * AVC helper functions for muxers + * Copyright (c) 2006 Baptiste Coudurier + * + * 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; +} + diff --git a/src/avc.h b/src/avc.h new file mode 100644 index 00000000..5b8c1ea2 --- /dev/null +++ b/src/avc.h @@ -0,0 +1,37 @@ +/* + * AVC helper functions for muxers + * Copyright (c) 2006 Baptiste Coudurier + * + * 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 +#include +#include +#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 diff --git a/src/dvr/dvr.h b/src/dvr/dvr.h index aec427d3..60df7840 100644 --- a/src/dvr/dvr.h +++ b/src/dvr/dvr.h @@ -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; diff --git a/src/dvr/dvr_db.c b/src/dvr/dvr_db.c index 44892b5a..2ffa3c34 100644 --- a/src/dvr/dvr_db.c +++ b/src/dvr/dvr_db.c @@ -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(); diff --git a/src/dvr/dvr_rec.c b/src/dvr/dvr_rec.c index 1362e08f..1a35e2e3 100644 --- a/src/dvr/dvr_rec.c +++ b/src/dvr/dvr_rec.c @@ -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 ?: "", si->si_provider ?: "", si->si_service ?: ""); + + + 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); diff --git a/src/dvr/ebml.c b/src/dvr/ebml.c new file mode 100644 index 00000000..da2ee9a6 --- /dev/null +++ b/src/dvr/ebml.c @@ -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 +#include +#include +#include +#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); +} diff --git a/src/dvr/ebml.h b/src/dvr/ebml.h new file mode 100644 index 00000000..22b0b708 --- /dev/null +++ b/src/dvr/ebml.h @@ -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__ diff --git a/src/dvr/mkmux.c b/src/dvr/mkmux.c new file mode 100644 index 00000000..223fca56 --- /dev/null +++ b/src/dvr/mkmux.c @@ -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 . + */ + + +#include +#include +#include +#include +#include + +#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); +} diff --git a/src/dvr/mkmux.h b/src/dvr/mkmux.h new file mode 100644 index 00000000..bb75576b --- /dev/null +++ b/src/dvr/mkmux.h @@ -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 . + */ + +#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__ diff --git a/src/plumbing/globalheaders.c b/src/plumbing/globalheaders.c new file mode 100644 index 00000000..2677d195 --- /dev/null +++ b/src/plumbing/globalheaders.c @@ -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 . + */ + +#include +#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); +} + diff --git a/src/plumbing/globalheaders.h b/src/plumbing/globalheaders.h new file mode 100644 index 00000000..160ee12e --- /dev/null +++ b/src/plumbing/globalheaders.h @@ -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 . + */ + +#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__