tvheadend/pvr.c

1149 lines
25 KiB
C
Raw Normal View History

2007-08-09 15:42:01 +00:00
/*
* Private Video Recorder
* Copyright (C) 2007 Andreas <EFBFBD>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 <pthread.h>
#include <assert.h>
2007-08-09 15:42:01 +00:00
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <dirent.h>
2007-08-09 15:42:01 +00:00
#include <ffmpeg/avcodec.h>
#include <ffmpeg/avformat.h>
#include <ffmpeg/avstring.h>
#include <libhts/htscfg.h>
#include "tvhead.h"
#include "channels.h"
#include "subscriptions.h"
2007-08-16 11:19:18 +00:00
#include "htsclient.h"
2007-08-09 15:42:01 +00:00
#include "pvr.h"
#include "epg.h"
#include "dispatch.h"
#include "buffer.h"
2007-10-31 16:10:19 +00:00
#include "strtab.h"
2007-08-09 15:42:01 +00:00
static int pvr_id_ceil; /* number generator for database entries */
2007-08-09 15:42:01 +00:00
struct pvr_rec_list pvrr_global_list;
static void pvr_database_save(pvr_rec_t *pvrr);
static void pvr_database_erase(pvr_rec_t *pvrr);
2007-08-09 15:42:01 +00:00
static void pvr_database_load(void);
static void pvrr_fsm(pvr_rec_t *pvrr);
static void pvrr_subscription_callback(struct th_subscription *s,
subscription_event_t event,
void *opaque);
static void *pvr_recorder_thread(void *aux);
static void pvrr_record_packet(pvr_rec_t *pvrr, th_pkt_t *pkt);
2007-08-09 15:42:01 +00:00
/****************************************************************************
*
* Externally visible functions
*
*/
void
pvr_init(void)
{
pvr_database_load();
}
2007-08-09 15:42:01 +00:00
char
pvr_prog_status(event_t *e)
2007-08-09 15:42:01 +00:00
{
pvr_rec_t *pvrr;
LIST_FOREACH(pvrr, &pvrr_global_list, pvrr_global_link) {
if(pvrr->pvrr_start >= e->e_start &&
pvrr->pvrr_stop <= e->e_start + e->e_duration &&
pvrr->pvrr_channel == e->e_ch) {
2007-08-09 15:42:01 +00:00
return pvrr->pvrr_status;
}
}
return HTSTV_PVR_STATUS_NONE;
}
pvr_rec_t *
pvr_get_log_entry(int e)
{
pvr_rec_t *pvrr = LIST_FIRST(&pvrr_global_list);
while(pvrr) {
if(e == 0)
return pvrr;
e--;
pvrr = LIST_NEXT(pvrr, pvrr_global_link);
}
return NULL;
}
pvr_rec_t *
pvr_get_tag_entry(int e)
{
pvr_rec_t *pvrr;
LIST_FOREACH(pvrr, &pvrr_global_list, pvrr_global_link)
if(pvrr->pvrr_ref == e)
return pvrr;
return NULL;
}
/****************************************************************************
*
* work queue
*
*/
void
pvr_inform_status_change(pvr_rec_t *pvrr)
{
event_t *e;
2007-08-09 15:42:01 +00:00
clients_send_ref(pvrr->pvrr_ref);
2007-08-09 15:42:01 +00:00
e = epg_event_find_by_time(pvrr->pvrr_channel, pvrr->pvrr_start);
if(e != NULL)
clients_send_ref(e->e_tag);
2007-08-09 15:42:01 +00:00
}
static void
pvr_free(pvr_rec_t *pvrr)
{
dtimer_disarm(&pvrr->pvrr_timer);
2007-08-09 15:42:01 +00:00
LIST_REMOVE(pvrr, pvrr_global_link);
free(pvrr->pvrr_title);
free(pvrr->pvrr_desc);
free(pvrr->pvrr_printname);
free(pvrr->pvrr_filename);
free(pvrr);
}
static void
pvr_abort(pvr_rec_t *pvrr)
2007-08-09 15:42:01 +00:00
{
if(pvrr->pvrr_status != HTSTV_PVR_STATUS_RECORDING)
return;
pvrr->pvrr_error = HTSTV_PVR_STATUS_ABORTED;
pvrr_fsm(pvrr);
pvr_database_save(pvrr);
clients_send_ref(-1);
2007-08-09 15:42:01 +00:00
}
static void
pvr_link_pvrr(pvr_rec_t *pvrr)
{
pvrr->pvrr_ref = tag_get();
pvrr->pvrr_printname = strdup(pvrr->pvrr_title ?: "");
LIST_INSERT_HEAD(&pvrr_global_list, pvrr, pvrr_global_link);
2007-08-09 15:42:01 +00:00
switch(pvrr->pvrr_status) {
case HTSTV_PVR_STATUS_FILE_ERROR:
case HTSTV_PVR_STATUS_DISK_FULL:
case HTSTV_PVR_STATUS_ABORTED:
case HTSTV_PVR_STATUS_BUFFER_ERROR:
case HTSTV_PVR_STATUS_NONE:
2007-08-09 15:42:01 +00:00
break;
case HTSTV_PVR_STATUS_SCHEDULED:
2007-08-09 15:42:01 +00:00
case HTSTV_PVR_STATUS_RECORDING:
pvrr->pvrr_status = HTSTV_PVR_STATUS_SCHEDULED;
pvrr_fsm(pvrr);
2007-08-09 15:42:01 +00:00
break;
}
pvr_inform_status_change(pvrr);
clients_send_ref(-1);
2007-08-09 15:42:01 +00:00
}
void
pvr_do_op(pvr_rec_t *pvrr, recop_t op)
{
switch(op) {
case RECOP_CLEAR:
if(pvrr->pvrr_status != HTSTV_PVR_STATUS_RECORDING) {
pvr_database_erase(pvrr);
pvr_free(pvrr);
break;
}
return;
case RECOP_ABORT:
pvr_abort(pvrr);
return;
default:
break;
}
}
/**
* Remove log info about all completed recordings
*/
void
pvr_clear_all_completed(void)
{
pvr_rec_t *pvrr, *next;
for(pvrr = LIST_FIRST(&pvrr_global_list); pvrr != NULL; pvrr = next) {
next = LIST_NEXT(pvrr, pvrr_global_link);
switch(pvrr->pvrr_status) {
case HTSTV_PVR_STATUS_SCHEDULED:
case HTSTV_PVR_STATUS_RECORDING:
break;
default:
pvr_database_erase(pvrr);
pvr_free(pvrr);
break;
}
}
}
2007-08-09 15:42:01 +00:00
void
pvr_event_record_op(th_channel_t *ch, event_t *e, recop_t op)
2007-08-09 15:42:01 +00:00
{
time_t start = e->e_start;
time_t stop = e->e_start + e->e_duration;
2007-08-09 15:42:01 +00:00
time_t now;
pvr_rec_t *pvrr;
char buf[100];
2007-08-09 15:42:01 +00:00
time(&now);
event_time_txt(start, e->e_duration, buf, sizeof(buf));
2007-08-09 15:42:01 +00:00
if(stop < now)
return;
/* Try to see if we already have a scheduled or active recording */
LIST_FOREACH(pvrr, &pvrr_global_list, pvrr_global_link) {
if(pvrr->pvrr_channel == ch && pvrr->pvrr_start == start &&
pvrr->pvrr_stop == stop)
break;
}
switch(op) {
case RECOP_CLEAR:
2008-02-13 16:02:13 +00:00
if(pvrr != NULL && pvrr->pvrr_status != HTSTV_PVR_STATUS_RECORDING) {
pvr_database_erase(pvrr);
pvr_free(pvrr);
break;
2007-08-09 15:42:01 +00:00
}
return;
case RECOP_ABORT:
if(pvrr != NULL)
pvr_abort(pvrr);
return;
case RECOP_ONCE:
if(pvrr != NULL)
return;
break;
case RECOP_DAILY:
case RECOP_WEEKLY:
syslog(LOG_ERR,"Recording type not supported yet");
return;
2007-08-09 15:42:01 +00:00
}
pvrr = calloc(1, sizeof(pvr_rec_t));
pvrr->pvrr_status = HTSTV_PVR_STATUS_SCHEDULED;
pvrr->pvrr_channel = ch;
pvrr->pvrr_start = start;
pvrr->pvrr_stop = stop;
pvrr->pvrr_title = e->e_title ? strdup(e->e_title) : NULL;
pvrr->pvrr_desc = e->e_desc ? strdup(e->e_desc) : NULL;
2007-08-09 15:42:01 +00:00
pvr_link_pvrr(pvrr);
pvr_database_save(pvrr);
2007-08-09 15:42:01 +00:00
}
void
pvr_channel_record_op(th_channel_t *ch, int duration)
{
time_t now = dispatch_clock;
time_t start = now;
time_t stop = now + duration;
pvr_rec_t *pvrr;
pvrr = calloc(1, sizeof(pvr_rec_t));
pvrr->pvrr_status = HTSTV_PVR_STATUS_SCHEDULED;
pvrr->pvrr_channel = ch;
pvrr->pvrr_start = start;
pvrr->pvrr_stop = stop;
pvrr->pvrr_title = strdup("Manual recording");
pvrr->pvrr_desc = NULL;
pvr_link_pvrr(pvrr);
pvr_database_save(pvrr);
}
2007-08-09 15:42:01 +00:00
/*****************************************************************************
*
* The recording database is real simple.
*
* We just store meta information about each recording in a separate
* textfile stored in a directory
2007-08-09 15:42:01 +00:00
*
*/
static void
pvr_database_save(pvr_rec_t *pvrr)
2007-08-09 15:42:01 +00:00
{
char buf[400];
2007-08-09 15:42:01 +00:00
FILE *fp;
if(pvrr->pvrr_id == 0)
pvrr->pvrr_id = ++pvr_id_ceil;
2007-08-09 15:42:01 +00:00
snprintf(buf, sizeof(buf), "%s/recordings/%d", settings_dir, pvrr->pvrr_id);
if((fp = settings_open_for_write(buf)) == NULL)
return;
fprintf(fp, "channel = %s\n", pvrr->pvrr_channel->ch_name);
fprintf(fp, "start = %ld\n", pvrr->pvrr_start);
fprintf(fp, "stop = %ld\n", pvrr->pvrr_stop);
2007-08-09 15:42:01 +00:00
if(pvrr->pvrr_title != NULL)
fprintf(fp, "title = %s\n", pvrr->pvrr_title);
2007-08-09 15:42:01 +00:00
if(pvrr->pvrr_desc != NULL)
fprintf(fp, "description = %s\n", pvrr->pvrr_desc);
2007-08-09 15:42:01 +00:00
if(pvrr->pvrr_filename != NULL)
fprintf(fp, "filename = %s\n", pvrr->pvrr_filename);
fprintf(fp, "status = %c\n", pvrr->pvrr_status);
fclose(fp);
}
2007-08-09 15:42:01 +00:00
/**
* Erase status from a recording
*/
static void
pvr_database_erase(pvr_rec_t *pvrr)
{
char buf[400];
if(pvrr->pvrr_id == 0)
return;
2007-08-09 15:42:01 +00:00
snprintf(buf, sizeof(buf), "%s/recordings/%d", settings_dir, pvrr->pvrr_id);
unlink(buf);
2007-08-09 15:42:01 +00:00
}
/**
* Load database
*/
2007-08-09 15:42:01 +00:00
static void
pvr_database_load(void)
{
struct config_head cl;
char buf[400];
struct dirent *d;
const char *channel, *title, *desc, *fname, *status;
DIR *dir;
time_t start, stop;
pvr_rec_t *pvrr;
2007-08-09 15:42:01 +00:00
snprintf(buf, sizeof(buf), "%s/recordings", settings_dir);
2007-08-09 15:42:01 +00:00
if((dir = opendir(buf)) == NULL)
return;
2007-08-09 15:42:01 +00:00
while((d = readdir(dir)) != NULL) {
2007-08-09 15:42:01 +00:00
if(d->d_name[0] == '.')
2007-08-09 15:42:01 +00:00
continue;
snprintf(buf, sizeof(buf), "%s/recordings/%s", settings_dir, d->d_name);
2007-08-09 15:42:01 +00:00
TAILQ_INIT(&cl);
config_read_file0(buf, &cl);
2007-08-09 15:42:01 +00:00
channel = config_get_str_sub(&cl, "channel", NULL);
start = atoi(config_get_str_sub(&cl, "start", "0"));
stop = atoi(config_get_str_sub(&cl, "stop", "0"));
2007-08-09 15:42:01 +00:00
title = config_get_str_sub(&cl, "title", NULL);
desc = config_get_str_sub(&cl, "description", NULL);
fname = config_get_str_sub(&cl, "filename", NULL);
status = config_get_str_sub(&cl, "status", NULL);
if(channel != NULL && start && stop && title && status) {
pvrr = calloc(1, sizeof(pvr_rec_t));
pvrr->pvrr_channel = channel_find(channel, 1, NULL);
pvrr->pvrr_start = start;
pvrr->pvrr_stop = stop;
pvrr->pvrr_status = *status;
pvrr->pvrr_filename = fname ? strdup(fname) : NULL;
pvrr->pvrr_title = title ? strdup(title) : NULL;
pvrr->pvrr_desc = desc ? strdup(desc) : NULL;
pvrr->pvrr_id = atoi(d->d_name);
2007-08-09 15:42:01 +00:00
if(pvrr->pvrr_id > pvr_id_ceil)
pvr_id_ceil = pvrr->pvrr_id;
2007-08-09 15:42:01 +00:00
pvr_link_pvrr(pvrr);
}
config_free0(&cl);
2007-08-09 15:42:01 +00:00
}
closedir(dir);
}
2007-08-09 15:42:01 +00:00
/*
* Replace any slash chars in a string with dash
*/
static void
deslashify(char *s)
{
int i, len = strlen(s);
for(i = 0; i < len; i++) if(s[i] == '/')
s[i] = '-';
}
2007-08-09 15:42:01 +00:00
/**
* Filename generator
*
* - convert from utf8
* - avoid duplicate filenames
*
*/
static void
pvr_generate_filename(pvr_rec_t *pvrr)
{
char fullname[1000];
char *x;
int tally = 0;
struct stat st;
char *name = pvrr->pvrr_title;
char *chname;
char *filename;
if(pvrr->pvrr_filename != NULL) {
free(pvrr->pvrr_filename);
pvrr->pvrr_filename = NULL;
}
pvrr->pvrr_fmt_lavfname = "matroska";
pvrr->pvrr_fmt_postfix = "mkv";
filename = utf8tofilename(name && name[0] ? name : "untitled");
deslashify(filename);
chname = utf8tofilename(pvrr->pvrr_channel->ch_name);
deslashify(chname);
snprintf(fullname, sizeof(fullname), "%s/%s-%s.%s",
config_get_str("pvrdir", "."), chname, filename,
pvrr->pvrr_fmt_postfix);
while(1) {
if(stat(fullname, &st) == -1) {
syslog(LOG_DEBUG, "pvr: File \"%s\" -- %s -- Using for recording",
fullname, strerror(errno));
break;
}
syslog(LOG_DEBUG, "pvr: Overwrite protection, file \"%s\" exists",
fullname);
tally++;
snprintf(fullname, sizeof(fullname), "%s/%s-%s-%d.%s",
config_get_str("pvrdir", "."), chname, filename, tally,
pvrr->pvrr_fmt_postfix);
}
pvrr->pvrr_filename = strdup(fullname);
if(pvrr->pvrr_printname != NULL)
free(pvrr->pvrr_printname);
x = strrchr(pvrr->pvrr_filename, '/');
pvrr->pvrr_printname = strdup(x ? x + 1 : pvrr->pvrr_filename);
free(filename);
free(chname);
}
static void
pvrr_fsm_timeout(void *aux, int64_t now)
{
pvr_rec_t *pvrr = aux;
pvrr_fsm(pvrr);
}
static void
pvrr_fsm(pvr_rec_t *pvrr)
{
time_t delta;
time_t now;
dtimer_disarm(&pvrr->pvrr_timer);
time(&now);
switch(pvrr->pvrr_status) {
case HTSTV_PVR_STATUS_NONE:
break;
case HTSTV_PVR_STATUS_SCHEDULED:
delta = pvrr->pvrr_start - 30 - now;
if(delta > 0) {
dtimer_arm(&pvrr->pvrr_timer, pvrr_fsm_timeout, pvrr, delta);
break;
}
delta = pvrr->pvrr_stop - now;
if(delta <= 0) {
syslog(LOG_NOTICE, "pvr: \"%s\" - Recording skipped, "
"program has already come to pass", pvrr->pvrr_printname);
pvrr->pvrr_status = HTSTV_PVR_STATUS_DONE;
pvr_inform_status_change(pvrr);
pvr_database_save(pvrr);
break;
}
/* Add a timer that fires when recording ends */
dtimer_arm(&pvrr->pvrr_timer, pvrr_fsm_timeout, pvrr, delta);
TAILQ_INIT(&pvrr->pvrr_pktq);
pthread_cond_init(&pvrr->pvrr_pktq_cond, NULL);
pthread_mutex_init(&pvrr->pvrr_pktq_mutex, NULL);
pvrr->pvrr_status = HTSTV_PVR_STATUS_RECORDING;
pvr_inform_status_change(pvrr);
pvrr_set_rec_state(pvrr,PVR_REC_WAIT_SUBSCRIPTION);
pvrr->pvrr_s = subscription_create(pvrr->pvrr_channel, 1000, "pvr",
pvrr_subscription_callback,
pvrr);
pvrr->pvrr_error = HTSTV_PVR_STATUS_DONE; /* assume everything will
work out ok */
break;
case HTSTV_PVR_STATUS_RECORDING:
/* recording completed (or aborted, or failed or somthing) */
pvrr->pvrr_status = pvrr->pvrr_error;
pvr_inform_status_change(pvrr);
pvr_database_save(pvrr);
subscription_unsubscribe(pvrr->pvrr_s);
dtimer_disarm(&pvrr->pvrr_timer);
break;
}
}
/*
* PVR new packet received
*/
static void
pvrr_packet_input(th_muxer_t *tm, th_stream_t *st, th_pkt_t *pkt)
{
pvr_rec_t *pvrr = tm->tm_opaque;
if(pvrr->pvrr_dts_offset == AV_NOPTS_VALUE)
pvrr->pvrr_dts_offset = pkt->pkt_dts;
pkt = pkt_copy(pkt);
pkt->pkt_dts -= pvrr->pvrr_dts_offset;
pkt->pkt_pts -= pvrr->pvrr_dts_offset;
if(pkt->pkt_dts < 0 || pkt->pkt_pts < 0) {
pkt_deref(pkt);
return;
}
pthread_mutex_lock(&pvrr->pvrr_pktq_mutex);
TAILQ_INSERT_TAIL(&pvrr->pvrr_pktq, pkt, pkt_queue_link);
pvrr->pvrr_pktq_len++;
pthread_cond_signal(&pvrr->pvrr_pktq_cond);
pthread_mutex_unlock(&pvrr->pvrr_pktq_mutex);
}
/*
* Internal recording state
*/
void
pvrr_set_rec_state(pvr_rec_t *pvrr, pvrr_rec_status_t status)
{
const char *tp;
if(pvrr->pvrr_rec_status == status)
return;
switch(status) {
case PVR_REC_STOP:
tp = "stopped";
break;
case PVR_REC_WAIT_SUBSCRIPTION:
tp = "waiting for subscription";
break;
case PVR_REC_WAIT_FOR_START:
tp = "waiting for program start";
break;
case PVR_REC_WAIT_AUDIO_LOCK:
tp = "waiting for audio lock";
break;
case PVR_REC_WAIT_VIDEO_LOCK:
tp = "waiting for video lock";
break;
case PVR_REC_RUNNING:
tp = "running";
break;
case PVR_REC_COMMERCIAL:
tp = "commercial break";
break;
default:
tp = "<invalid state>";
break;
}
syslog(LOG_INFO, "pvr: \"%s\" - Recorder entering state \"%s\"",
pvrr->pvrr_printname, tp);
pvrr->pvrr_rec_status = status;
}
/*
* We've got a transport now, start recording
*/
static void
pvrr_transport_available(pvr_rec_t *pvrr, th_transport_t *t)
{
th_muxer_t *tm = &pvrr->pvrr_muxer;
th_stream_t *st;
th_muxstream_t *tms;
AVFormatContext *fctx;
AVOutputFormat *fmt;
AVCodecContext *ctx;
AVCodec *codec;
enum CodecID codec_id;
enum CodecType codec_type;
const char *codec_name;
char urlname[500];
int err;
assert(pvrr->pvrr_rec_status == PVR_REC_WAIT_SUBSCRIPTION);
tm->tm_opaque = pvrr;
tm->tm_new_pkt = pvrr_packet_input;
pvr_generate_filename(pvrr);
/* Find lavf format */
fmt = guess_format(pvrr->pvrr_fmt_lavfname, NULL, NULL);
if(fmt == NULL) {
syslog(LOG_ERR,
"pvr: \"%s\" - Unable to open file format \"%s\" for output",
pvrr->pvrr_printname, pvrr->pvrr_fmt_lavfname);
pvrr->pvrr_error = HTSTV_PVR_STATUS_FILE_ERROR;
pvrr_fsm(pvrr);
return;
}
/* Init format context */
fctx = tm->tm_avfctx = av_alloc_format_context();
av_strlcpy(fctx->title, pvrr->pvrr_title ?: "",
sizeof(fctx->title));
av_strlcpy(fctx->comment, pvrr->pvrr_desc ?: "",
sizeof(tm->tm_avfctx->comment));
av_strlcpy(fctx->copyright, pvrr->pvrr_channel->ch_name,
sizeof(fctx->copyright));
fctx->oformat = fmt;
/* Open output file */
snprintf(urlname, sizeof(urlname), "file:%s", pvrr->pvrr_filename);
if((err = url_fopen(&fctx->pb, urlname, URL_WRONLY)) < 0) {
syslog(LOG_ERR,
"pvr: \"%s\" - Unable to create output file \"%s\" -- %s\n",
pvrr->pvrr_printname, pvrr->pvrr_filename,
strerror(AVUNERROR(err)));
av_free(fctx);
tm->tm_avfctx = NULL;
pvrr->pvrr_error = HTSTV_PVR_STATUS_FILE_ERROR;
pvrr_fsm(pvrr);
return;
}
av_set_parameters(tm->tm_avfctx, NULL);
LIST_FOREACH(st, &t->tht_streams, st_link) {
switch(st->st_type) {
default:
continue;
case HTSTV_MPEG2VIDEO:
codec_id = CODEC_ID_MPEG2VIDEO;
codec_type = CODEC_TYPE_VIDEO;
codec_name = "mpeg2 video";
break;
case HTSTV_MPEG2AUDIO:
codec_id = CODEC_ID_MP2;
codec_type = CODEC_TYPE_AUDIO;
codec_name = "mpeg2 audio";
break;
case HTSTV_AC3:
codec_id = CODEC_ID_AC3;
codec_type = CODEC_TYPE_AUDIO;
codec_name = "AC3 audio";
break;
case HTSTV_H264:
codec_id = CODEC_ID_H264;
codec_type = CODEC_TYPE_VIDEO;
codec_name = "h.264 video";
break;
}
codec = avcodec_find_decoder(codec_id);
if(codec == NULL) {
syslog(LOG_ERR,
"pvr: \"%s\" - Cannot find codec for %s, ignoring stream",
pvrr->pvrr_printname, codec_name);
continue;
}
ctx = avcodec_alloc_context();
ctx->codec_id = codec_id;
ctx->codec_type = codec_type;
if(avcodec_open(ctx, codec) < 0) {
syslog(LOG_ERR,
"pvr: \"%s\" - Cannot open codec for %s, ignoring stream",
pvrr->pvrr_printname, codec_name);
free(ctx);
continue;
}
tms = calloc(1, sizeof(th_muxstream_t));
tms->tms_stream = st;
LIST_INSERT_HEAD(&tm->tm_streams, tms, tms_muxer_link0);
tms->tms_avstream = av_mallocz(sizeof(AVStream));
tms->tms_avstream->codec = ctx;
memcpy(tms->tms_avstream->language, tms->tms_stream->st_lang, 4);
tms->tms_index = fctx->nb_streams;
tm->tm_avfctx->streams[fctx->nb_streams] = tms->tms_avstream;
fctx->nb_streams++;
}
/* Fire up recorder thread */
pvrr_set_rec_state(pvrr, PVR_REC_WAIT_FOR_START);
pthread_create(&pvrr->pvrr_ptid, NULL, pvr_recorder_thread, pvrr);
LIST_INSERT_HEAD(&t->tht_muxers, tm, tm_transport_link);
}
/*
* We've lost our transport, stop recording
*/
static void
pvrr_transport_unavailable(pvr_rec_t *pvrr, th_transport_t *t)
{
th_muxer_t *tm = &pvrr->pvrr_muxer;
th_muxstream_t *tms;
th_pkt_t *pkt;
AVFormatContext *fctx = tm->tm_avfctx;
AVStream *avst;
int i;
LIST_REMOVE(tm, tm_transport_link);
pvrr->pvrr_dts_offset = AV_NOPTS_VALUE;
pvrr_set_rec_state(pvrr, PVR_REC_STOP);
pthread_cond_signal(&pvrr->pvrr_pktq_cond);
pthread_join(pvrr->pvrr_ptid, NULL);
if(fctx != NULL) {
/* Write trailer if we've written anything at all */
if(pvrr->pvrr_header_written)
av_write_trailer(tm->tm_avfctx);
/* Close streams and format */
for(i = 0; i < fctx->nb_streams; i++) {
avst = fctx->streams[i];
avcodec_close(avst->codec);
free(avst->codec);
free(avst);
}
2007-11-21 09:30:18 +00:00
url_fclose(fctx->pb);
free(fctx);
}
/* Remove any pending packet for queue */
while((pkt = TAILQ_FIRST(&pvrr->pvrr_pktq)) != NULL)
pkt_deref(pkt);
/* Destroy muxstreams */
while((tms = LIST_FIRST(&tm->tm_streams)) != NULL) {
LIST_REMOVE(tms, tms_muxer_link0);
free(tms);
}
}
/*
* We get a callback here when the subscription status is updated,
* ie, when we are attached to a transport and when we are detached
*/
static void
pvrr_subscription_callback(struct th_subscription *s,
subscription_event_t event, void *opaque)
{
th_transport_t *t = s->ths_transport;
pvr_rec_t *pvrr = opaque;
switch(event) {
case TRANSPORT_AVAILABLE:
pvrr_transport_available(pvrr, t);
break;
case TRANSPORT_UNAVAILABLE:
pvrr_transport_unavailable(pvrr, t);
break;
}
}
/*
* Recorder thread
*/
static void *
pvr_recorder_thread(void *aux)
{
th_pkt_t *pkt;
pvr_rec_t *pvrr = aux;
char *t, txt2[50];
int run = 1;
time_t now;
ctime_r(&pvrr->pvrr_stop, txt2);
t = strchr(txt2, '\n');
if(t != NULL)
*t = 0;
syslog(LOG_INFO, "pvr: \"%s\" - Recording started, ends at %s",
pvrr->pvrr_printname, txt2);
pthread_mutex_lock(&pvrr->pvrr_pktq_mutex);
while(run) {
switch(pvrr->pvrr_rec_status) {
case PVR_REC_WAIT_FOR_START:
time(&now);
if(now >= pvrr->pvrr_start)
pvrr->pvrr_rec_status = PVR_REC_WAIT_AUDIO_LOCK;
break;
case PVR_REC_WAIT_AUDIO_LOCK:
case PVR_REC_WAIT_VIDEO_LOCK:
case PVR_REC_RUNNING:
case PVR_REC_COMMERCIAL:
break;
default:
run = 0;
continue;
}
if(pvrr->pvrr_stop < now)
break;
if((pkt = TAILQ_FIRST(&pvrr->pvrr_pktq)) == NULL) {
pthread_cond_wait(&pvrr->pvrr_pktq_cond, &pvrr->pvrr_pktq_mutex);
continue;
}
TAILQ_REMOVE(&pvrr->pvrr_pktq, pkt, pkt_queue_link);
pvrr->pvrr_pktq_len--;
pthread_mutex_unlock(&pvrr->pvrr_pktq_mutex);
pvrr_record_packet(pvrr, pkt);
pkt_deref(pkt);
pthread_mutex_lock(&pvrr->pvrr_pktq_mutex);
}
pthread_mutex_unlock(&pvrr->pvrr_pktq_mutex);
syslog(LOG_INFO, "pvr: \"%s\" - Recording completed",
pvrr->pvrr_printname);
return NULL;
}
/**
* Check if all streams of the given type has been decoded
*/
static int
is_all_decoded(th_muxer_t *tm, enum CodecType type)
{
th_muxstream_t *tms;
AVStream *st;
LIST_FOREACH(tms, &tm->tm_streams, tms_muxer_link0) {
st = tms->tms_avstream;
if(st->codec->codec->type == type && tms->tms_decoded == 0)
return 0;
}
return 1;
}
/**
* Write a packet to output file
*/
static void
pvrr_record_packet(pvr_rec_t *pvrr, th_pkt_t *pkt)
{
th_muxer_t *tm = &pvrr->pvrr_muxer;
AVFormatContext *fctx = tm->tm_avfctx;
th_muxstream_t *tms;
AVStream *st, *stx;
AVCodecContext *ctx;
AVPacket avpkt;
void *abuf;
AVFrame pic;
int r, data_size, i;
void *buf;
char txt[100];
size_t bufsize;
LIST_FOREACH(tms, &tm->tm_streams, tms_muxer_link0)
if(tms->tms_stream == pkt->pkt_stream)
break;
if(tms == NULL)
return;
st = tms->tms_avstream;
ctx = st->codec;
/* Make sure packet is in memory */
buf = pkt_payload(pkt);
bufsize = pkt_len(pkt);
switch(pvrr->pvrr_rec_status) {
default:
break;
case PVR_REC_WAIT_AUDIO_LOCK:
if(ctx->codec_type != CODEC_TYPE_AUDIO || tms->tms_decoded)
break;
abuf = malloc(AVCODEC_MAX_AUDIO_FRAME_SIZE);
r = avcodec_decode_audio(ctx, abuf, &data_size, buf, bufsize);
free(abuf);
if(r != 0 && data_size) {
syslog(LOG_DEBUG, "pvr: \"%s\" - "
"Stream #%d: \"%s\" decoded a complete audio frame: "
"%d channels in %d Hz\n",
pvrr->pvrr_printname, tms->tms_index,
st->codec->codec->name,
st->codec->channels,
st->codec->sample_rate);
tms->tms_decoded = 1;
}
if(is_all_decoded(tm, CODEC_TYPE_AUDIO))
pvrr_set_rec_state(pvrr, PVR_REC_WAIT_VIDEO_LOCK);
break;
case PVR_REC_WAIT_VIDEO_LOCK:
if(ctx->codec_type != CODEC_TYPE_VIDEO || tms->tms_decoded)
break;
r = avcodec_decode_video(st->codec, &pic, &data_size, buf, bufsize);
if(r != 0 && data_size) {
syslog(LOG_DEBUG, "pvr: \"%s\" - "
"Stream #%d: \"%s\" decoded a complete video frame: "
"%d x %d at %.2fHz\n",
pvrr->pvrr_printname, tms->tms_index,
ctx->codec->name,
ctx->width, st->codec->height,
(float)ctx->time_base.den / (float)ctx->time_base.num);
tms->tms_decoded = 1;
}
if(!is_all_decoded(tm, CODEC_TYPE_VIDEO))
break;
/* All Audio & Video decoded, start recording */
pvrr_set_rec_state(pvrr, PVR_REC_RUNNING);
if(!pvrr->pvrr_header_written) {
pvrr->pvrr_header_written = 1;
if(av_write_header(fctx))
break;
syslog(LOG_DEBUG,
"pvr: \"%s\" - Header written to file, stream dump:",
pvrr->pvrr_printname);
for(i = 0; i < fctx->nb_streams; i++) {
stx = fctx->streams[i];
avcodec_string(txt, sizeof(txt), stx->codec, 1);
syslog(LOG_DEBUG, "pvr: \"%s\" - Stream #%d: %s [%d/%d]",
pvrr->pvrr_printname, i, txt,
stx->time_base.num, stx->time_base.den);
}
}
/* FALLTHRU */
case PVR_REC_RUNNING:
if(pkt->pkt_commercial == COMMERCIAL_YES) {
pvrr_set_rec_state(pvrr, PVR_REC_COMMERCIAL);
break;
}
av_init_packet(&avpkt);
avpkt.stream_index = tms->tms_index;
avpkt.dts = av_rescale_q(pkt->pkt_dts, AV_TIME_BASE_Q, st->time_base);
avpkt.pts = av_rescale_q(pkt->pkt_pts, AV_TIME_BASE_Q, st->time_base);
avpkt.data = buf;
avpkt.size = bufsize;
avpkt.duration = pkt->pkt_duration;
r = av_interleaved_write_frame(fctx, &avpkt);
break;
case PVR_REC_COMMERCIAL:
if(pkt->pkt_commercial != COMMERCIAL_YES) {
LIST_FOREACH(tms, &tm->tm_streams, tms_muxer_link0)
tms->tms_decoded = 0;
pvrr_set_rec_state(pvrr, PVR_REC_WAIT_AUDIO_LOCK);
}
break;
}
}
2007-10-31 16:10:19 +00:00
static struct strtab recoptab[] = {
{ "once", RECOP_ONCE },
{ "daily", RECOP_DAILY },
{ "weekly", RECOP_WEEKLY },
{ "abort", RECOP_ABORT },
{ "clear", RECOP_CLEAR }
2007-10-31 16:10:19 +00:00
};
int
pvr_op2int(const char *op)
{
return str2val(op, recoptab);
}