Split out the ffmpeg guts of the PVR recorder to aid reusing it for other stuff

This commit is contained in:
Andreas Öman 2008-02-15 16:33:56 +00:00
parent 93888d02d3
commit 1f925ec9fc
4 changed files with 163 additions and 120 deletions

View file

@ -57,13 +57,13 @@ static struct strtab recstatustxt[] = {
};
static struct strtab recintstatustxt[] = {
{ "Stopped", PVR_REC_STOP },
{ "Waiting for transponder", PVR_REC_WAIT_SUBSCRIPTION },
{ "Waiting for program start", PVR_REC_WAIT_FOR_START },
{ "Waiting for valid audio frames", PVR_REC_WAIT_AUDIO_LOCK },
{ "Waiting for valid video frames", PVR_REC_WAIT_VIDEO_LOCK },
{ "Recording", PVR_REC_RUNNING },
{ "Commercial break", PVR_REC_COMMERCIAL }
{ "Stopped", TFFM_STOP },
{ "Waiting for transponder", TFFM_WAIT_SUBSCRIPTION },
{ "Waiting for program start", TFFM_WAIT_FOR_START },
{ "Waiting for valid audio frames", TFFM_WAIT_AUDIO_LOCK },
{ "Waiting for valid video frames", TFFM_WAIT_VIDEO_LOCK },
{ "Recording", TFFM_RUNNING },
{ "Commercial break", TFFM_COMMERCIAL }
};
@ -1025,7 +1025,8 @@ page_pvr(http_connection_t *hc, const char *remain, void *opaque)
"Recorder status:</span><td>"
"<td>%s</td>"
"</tr>",
val2str(pvrr->pvrr_rec_status, recintstatustxt) ?: "invalid");
val2str(pvrr->pvrr_tffm.tffm_state, recintstatustxt)
?: "invalid");
tcp_qprintf(&tq,
"</table>");

220
pvr.c
View file

@ -53,14 +53,18 @@ 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);
static void pvr_database_load(void);
static void pvr_set_rec_state(pvr_rec_t *pvrr, pvrr_rec_status_t status);
static void tffm_set_state(th_ffmuxer_t *tffm, int status);
static void pvr_fsm(pvr_rec_t *pvrr);
static void pvr_subscription_callback(struct th_subscription *s,
subscription_event_t event,
void *opaque);
static void *pvr_recorder_thread(void *aux);
static void pvr_record_packet(pvr_rec_t *pvrr, th_pkt_t *pkt);
static void pvr_record_packet(th_ffmuxer_t *tffm, th_pkt_t *pkt);
static void tffm_open(th_ffmuxer_t *tffm, th_transport_t *t,
AVFormatContext *fctx, const char *printname);
static void tffm_close(th_ffmuxer_t *tffm);
/**
* Initialize PVR framework
@ -548,6 +552,7 @@ pvr_fsm(pvr_rec_t *pvrr)
{
time_t delta;
time_t now;
th_ffmuxer_t *tffm = &pvrr->pvrr_tffm;
dtimer_disarm(&pvrr->pvrr_timer);
@ -586,7 +591,9 @@ pvr_fsm(pvr_rec_t *pvrr)
pvrr->pvrr_status = HTSTV_PVR_STATUS_RECORDING;
pvr_inform_status_change(pvrr);
pvr_set_rec_state(pvrr,PVR_REC_WAIT_SUBSCRIPTION);
tffm->tffm_state = TFFM_WAIT_SUBSCRIPTION; /* cant use set_state() since
tffm_printname is not
initialized */
pvrr->pvrr_s = subscription_create(pvrr->pvrr_channel, 1000, "pvr",
pvr_subscription_callback,
@ -641,33 +648,33 @@ pvrr_packet_input(th_muxer_t *tm, th_stream_t *st, th_pkt_t *pkt)
* Internal recording state
*/
static void
pvr_set_rec_state(pvr_rec_t *pvrr, pvrr_rec_status_t status)
tffm_set_state(th_ffmuxer_t *tffm, int status)
{
const char *tp;
if(pvrr->pvrr_rec_status == status)
if(tffm->tffm_state == status)
return;
switch(status) {
case PVR_REC_STOP:
case TFFM_STOP:
tp = "stopped";
break;
case PVR_REC_WAIT_SUBSCRIPTION:
case TFFM_WAIT_SUBSCRIPTION:
tp = "waiting for subscription";
break;
case PVR_REC_WAIT_FOR_START:
case TFFM_WAIT_FOR_START:
tp = "waiting for program start";
break;
case PVR_REC_WAIT_AUDIO_LOCK:
case TFFM_WAIT_AUDIO_LOCK:
tp = "waiting for audio lock";
break;
case PVR_REC_WAIT_VIDEO_LOCK:
case TFFM_WAIT_VIDEO_LOCK:
tp = "waiting for video lock";
break;
case PVR_REC_RUNNING:
case TFFM_RUNNING:
tp = "running";
break;
case PVR_REC_COMMERCIAL:
case TFFM_COMMERCIAL:
tp = "commercial break";
break;
default:
@ -675,10 +682,8 @@ pvr_set_rec_state(pvr_rec_t *pvrr, pvrr_rec_status_t status)
break;
}
syslog(LOG_INFO, "pvr: \"%s\" - Recorder entering state \"%s\"",
pvrr->pvrr_printname, tp);
pvrr->pvrr_rec_status = status;
syslog(LOG_INFO, "%s - Entering state \"%s\"", tffm->tffm_printname, tp);
tffm->tffm_state = status;
}
/**
@ -687,20 +692,15 @@ pvr_set_rec_state(pvr_rec_t *pvrr, pvrr_rec_status_t status)
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;
th_ffmuxer_t *tffm = &pvrr->pvrr_tffm;
th_muxer_t *tm = &tffm->tffm_muxer;
AVFormatContext *fctx;
AVOutputFormat *fmt;
AVCodecContext *ctx;
AVCodec *codec;
enum CodecID codec_id;
enum CodecType codec_type;
const char *codec_name;
char urlname[500];
char printname[500];
int err;
assert(pvrr->pvrr_rec_status == PVR_REC_WAIT_SUBSCRIPTION);
assert(tffm->tffm_state == TFFM_WAIT_SUBSCRIPTION);
tm->tm_opaque = pvrr;
tm->tm_new_pkt = pvrr_packet_input;
@ -721,13 +721,13 @@ pvrr_transport_available(pvr_rec_t *pvrr, th_transport_t *t)
/* Init format context */
fctx = tm->tm_avfctx = av_alloc_format_context();
fctx = 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));
sizeof(fctx->comment));
av_strlcpy(fctx->copyright, pvrr->pvrr_channel->ch_name,
sizeof(fctx->copyright));
@ -745,14 +745,40 @@ pvrr_transport_available(pvr_rec_t *pvrr, th_transport_t *t)
pvrr->pvrr_printname, pvrr->pvrr_filename,
strerror(AVUNERROR(err)));
av_free(fctx);
tm->tm_avfctx = NULL;
pvrr->pvrr_error = HTSTV_PVR_STATUS_FILE_ERROR;
pvr_fsm(pvrr);
return;
}
snprintf(printname, sizeof(printname), "pvr: \"%s\"", pvrr->pvrr_printname);
av_set_parameters(tm->tm_avfctx, NULL);
tffm_open(tffm, t, fctx, printname);
pthread_create(&pvrr->pvrr_ptid, NULL, pvr_recorder_thread, pvrr);
LIST_INSERT_HEAD(&t->tht_muxers, tm, tm_transport_link);
}
/**
* Open TFFM output
*/
static void
tffm_open(th_ffmuxer_t *tffm, th_transport_t *t,
AVFormatContext *fctx, const char *printname)
{
th_muxer_t *tm = &tffm->tffm_muxer;
th_stream_t *st;
th_muxstream_t *tms;
AVCodecContext *ctx;
AVCodec *codec;
enum CodecID codec_id;
enum CodecType codec_type;
const char *codec_name;
tffm->tffm_avfctx = fctx;
tffm->tffm_printname = strdup(printname);
av_set_parameters(fctx, NULL);
LIST_FOREACH(st, &t->tht_streams, st_link) {
switch(st->st_type) {
@ -786,8 +812,8 @@ pvrr_transport_available(pvr_rec_t *pvrr, th_transport_t *t)
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);
"%s - Cannot find codec for %s, ignoring stream",
printname, codec_name);
continue;
}
@ -797,8 +823,8 @@ pvrr_transport_available(pvr_rec_t *pvrr, th_transport_t *t)
if(avcodec_open(ctx, codec) < 0) {
syslog(LOG_ERR,
"pvr: \"%s\" - Cannot open codec for %s, ignoring stream",
pvrr->pvrr_printname, codec_name);
"%s - Cannot open codec for %s, ignoring stream",
printname, codec_name);
free(ctx);
continue;
}
@ -813,58 +839,35 @@ pvrr_transport_available(pvr_rec_t *pvrr, th_transport_t *t)
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->streams[fctx->nb_streams] = tms->tms_avstream;
fctx->nb_streams++;
}
/* Fire up recorder thread */
pvr_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);
tffm_set_state(tffm, TFFM_WAIT_FOR_START);
}
/**
* 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_ffmuxer_t *tffm = &pvrr->pvrr_tffm;
th_muxer_t *tm = &tffm->tffm_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;
pvr_set_rec_state(pvrr, PVR_REC_STOP);
tffm_set_state(tffm, TFFM_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);
}
url_fclose(fctx->pb);
free(fctx);
}
tffm_close(tffm);
/* Remove any pending packet for queue */
@ -880,6 +883,38 @@ pvrr_transport_unavailable(pvr_rec_t *pvrr, th_transport_t *t)
}
/**
* Close ffmuxer
*/
static void
tffm_close(th_ffmuxer_t *tffm)
{
AVFormatContext *fctx = tffm->tffm_avfctx;
AVStream *avst;
int i;
if(fctx == NULL)
return;
/* Write trailer if we've written anything at all */
if(tffm->tffm_header_written)
av_write_trailer(fctx);
/* 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);
}
url_fclose(fctx->pb);
free(fctx);
free(tffm->tffm_printname);
}
/**
@ -912,6 +947,7 @@ pvr_recorder_thread(void *aux)
{
th_pkt_t *pkt;
pvr_rec_t *pvrr = aux;
th_ffmuxer_t *tffm = &pvrr->pvrr_tffm;
char *t, txt2[50];
int run = 1;
time_t now;
@ -928,18 +964,18 @@ pvr_recorder_thread(void *aux)
pthread_mutex_lock(&pvrr->pvrr_pktq_mutex);
while(run) {
switch(pvrr->pvrr_rec_status) {
case PVR_REC_WAIT_FOR_START:
switch(tffm->tffm_state) {
case TFFM_WAIT_FOR_START:
time(&now);
if(now >= pvrr->pvrr_start)
pvrr->pvrr_rec_status = PVR_REC_WAIT_AUDIO_LOCK;
tffm_set_state(tffm, TFFM_WAIT_AUDIO_LOCK);
break;
case PVR_REC_WAIT_AUDIO_LOCK:
case PVR_REC_WAIT_VIDEO_LOCK:
case PVR_REC_RUNNING:
case PVR_REC_COMMERCIAL:
case TFFM_WAIT_AUDIO_LOCK:
case TFFM_WAIT_VIDEO_LOCK:
case TFFM_RUNNING:
case TFFM_COMMERCIAL:
break;
default:
@ -959,7 +995,7 @@ pvr_recorder_thread(void *aux)
pvrr->pvrr_pktq_len--;
pthread_mutex_unlock(&pvrr->pvrr_pktq_mutex);
pvr_record_packet(pvrr, pkt);
pvr_record_packet(tffm, pkt);
pkt_deref(pkt);
pthread_mutex_lock(&pvrr->pvrr_pktq_mutex);
@ -997,10 +1033,10 @@ is_all_decoded(th_muxer_t *tm, enum CodecType type)
* Write a packet to output file
*/
static void
pvr_record_packet(pvr_rec_t *pvrr, th_pkt_t *pkt)
pvr_record_packet(th_ffmuxer_t *tffm, th_pkt_t *pkt)
{
th_muxer_t *tm = &pvrr->pvrr_muxer;
AVFormatContext *fctx = tm->tm_avfctx;
th_muxer_t *tm = &tffm->tffm_muxer;
AVFormatContext *fctx = tffm->tffm_avfctx;
th_muxstream_t *tms;
AVStream *st, *stx;
AVCodecContext *ctx;
@ -1027,11 +1063,11 @@ pvr_record_packet(pvr_rec_t *pvrr, th_pkt_t *pkt)
buf = pkt_payload(pkt);
bufsize = pkt_len(pkt);
switch(pvrr->pvrr_rec_status) {
switch(tffm->tffm_state) {
default:
break;
case PVR_REC_WAIT_AUDIO_LOCK:
case TFFM_WAIT_AUDIO_LOCK:
if(ctx->codec_type != CODEC_TYPE_AUDIO || tms->tms_decoded)
break;
@ -1040,10 +1076,10 @@ pvr_record_packet(pvr_rec_t *pvrr, th_pkt_t *pkt)
free(abuf);
if(r != 0 && data_size) {
syslog(LOG_DEBUG, "pvr: \"%s\" - "
syslog(LOG_DEBUG, "%s - "
"Stream #%d: \"%s\" decoded a complete audio frame: "
"%d channels in %d Hz\n",
pvrr->pvrr_printname, tms->tms_index,
tffm->tffm_printname, tms->tms_index,
st->codec->codec->name,
st->codec->channels,
st->codec->sample_rate);
@ -1052,19 +1088,19 @@ pvr_record_packet(pvr_rec_t *pvrr, th_pkt_t *pkt)
}
if(is_all_decoded(tm, CODEC_TYPE_AUDIO))
pvr_set_rec_state(pvrr, PVR_REC_WAIT_VIDEO_LOCK);
tffm_set_state(tffm, TFFM_WAIT_VIDEO_LOCK);
break;
case PVR_REC_WAIT_VIDEO_LOCK:
case TFFM_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\" - "
syslog(LOG_DEBUG, "%s - "
"Stream #%d: \"%s\" decoded a complete video frame: "
"%d x %d at %.2fHz\n",
pvrr->pvrr_printname, tms->tms_index,
tffm->tffm_printname, tms->tms_index,
ctx->codec->name,
ctx->width, st->codec->height,
(float)ctx->time_base.den / (float)ctx->time_base.num);
@ -1077,35 +1113,35 @@ pvr_record_packet(pvr_rec_t *pvrr, th_pkt_t *pkt)
/* All Audio & Video decoded, start recording */
pvr_set_rec_state(pvrr, PVR_REC_RUNNING);
tffm_set_state(tffm, TFFM_RUNNING);
if(!pvrr->pvrr_header_written) {
pvrr->pvrr_header_written = 1;
if(!tffm->tffm_header_written) {
tffm->tffm_header_written = 1;
if(av_write_header(fctx))
break;
syslog(LOG_DEBUG,
"pvr: \"%s\" - Header written to file, stream dump:",
pvrr->pvrr_printname);
"%s - Header written to file, stream dump:",
tffm->tffm_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,
syslog(LOG_DEBUG, "%s - Stream #%d: %s [%d/%d]",
tffm->tffm_printname, i, txt,
stx->time_base.num, stx->time_base.den);
}
}
/* FALLTHRU */
case PVR_REC_RUNNING:
case TFFM_RUNNING:
if(pkt->pkt_commercial == COMMERCIAL_YES) {
pvr_set_rec_state(pvrr, PVR_REC_COMMERCIAL);
tffm_set_state(tffm, TFFM_COMMERCIAL);
break;
}
@ -1122,14 +1158,14 @@ pvr_record_packet(pvr_rec_t *pvrr, th_pkt_t *pkt)
break;
case PVR_REC_COMMERCIAL:
case TFFM_COMMERCIAL:
if(pkt->pkt_commercial != COMMERCIAL_YES) {
LIST_FOREACH(tms, &tm->tm_streams, tms_muxer_link0)
tms->tms_decoded = 0;
pvr_set_rec_state(pvrr, PVR_REC_WAIT_AUDIO_LOCK);
tffm_set_state(tffm, TFFM_WAIT_AUDIO_LOCK);
}
break;
}

20
pvr.h
View file

@ -25,20 +25,6 @@ extern char *pvrpath;
extern struct pvr_rec_list pvrr_global_list;
/*
* PVR Internal recording status
*/
typedef enum {
PVR_REC_STOP,
PVR_REC_WAIT_SUBSCRIPTION,
PVR_REC_WAIT_FOR_START,
PVR_REC_WAIT_AUDIO_LOCK,
PVR_REC_WAIT_VIDEO_LOCK,
PVR_REC_RUNNING,
PVR_REC_COMMERCIAL,
} pvrr_rec_status_t;
/*
* PVR recording session
@ -68,8 +54,6 @@ typedef struct pvr_rec {
char pvrr_status; /* defined in libhts/htstv.h */
char pvrr_error; /* dito - but status returned from recorder */
pvrr_rec_status_t pvrr_rec_status; /* internal recording status */
struct th_pkt_queue pvrr_pktq;
int pvrr_pktq_len;
pthread_mutex_t pvrr_pktq_mutex;
@ -82,9 +66,7 @@ typedef struct pvr_rec {
pthread_t pvrr_ptid;
dtimer_t pvrr_timer;
th_muxer_t pvrr_muxer;
int pvrr_header_written;
th_ffmuxer_t pvrr_tffm;
int64_t pvrr_dts_offset;

View file

@ -659,10 +659,34 @@ typedef struct th_muxer {
TM_PAUSE,
} tm_status;
struct AVFormatContext *tm_avfctx;
} th_muxer_t;
/**
* Output muxer for usage via ffmpeg (avformat)
*/
typedef struct th_ffmuxer {
th_muxer_t tffm_muxer;
enum {
TFFM_STOP,
TFFM_WAIT_SUBSCRIPTION,
TFFM_WAIT_FOR_START,
TFFM_WAIT_AUDIO_LOCK,
TFFM_WAIT_VIDEO_LOCK,
TFFM_RUNNING,
TFFM_COMMERCIAL,
} tffm_state;
int tffm_header_written;
char *tffm_printname;
struct AVFormatContext *tffm_avfctx;
} th_ffmuxer_t;
/*