[xiph-commits] r16934 - trunk/y4oi
xiphmont at svn.xiph.org
xiphmont at svn.xiph.org
Tue Feb 23 23:40:16 PST 2010
Author: xiphmont
Date: 2010-02-23 23:40:16 -0800 (Tue, 23 Feb 2010)
New Revision: 16934
Added:
trunk/y4oi/filter.c
trunk/y4oi/main.c
trunk/y4oi/output.c
trunk/y4oi/y4o.c
trunk/y4oi/y4oi.h
Removed:
trunk/y4oi/y4oi.c
Modified:
trunk/y4oi/Makefile
Log:
Refactor stream flow into a filter pipeline
Modified: trunk/y4oi/Makefile
===================================================================
--- trunk/y4oi/Makefile 2010-02-22 03:47:07 UTC (rev 16933)
+++ trunk/y4oi/Makefile 2010-02-24 07:40:16 UTC (rev 16934)
@@ -12,20 +12,20 @@
ETCDIR = /etc/$(TARGET)
MANDIR = $(PREFIX)/man
-SRC = y4oi.c
-OBJ = y4oi.o
+SRC = main.c y4o.c filter.c output.c
+OBJ = main.o y4o.o filter.o output.o
GCF = `pkg-config --static --cflags "ogg vorbis theora"`
LDF = `pkg-config --static --libs "ogg vorbis theora"`
all:
pkg-config --cflags "ogg vorbis theora" 1>/dev/null
- $(MAKE) target CFLAGS='-O2 -ffast-math $(GCF) $(ADD_DEF)'
+ $(MAKE) target CFLAGS='-O2 -ffast-math $(GCF) $(ADD_DEF) $(CFLAGS)'
$(STRIP) $(TARGETS)
debug:
pkg-config --cflags "ogg vorbis theora" 1>/dev/null
- $(MAKE) target CFLAGS='-g -Wall -W -Wno-unused-parameter -D__NO_MATH_INLINES $(GCF) $(ADD_DEF)'
+ $(MAKE) target CFLAGS='-g -Wall -W -Werror -Wno-unused-parameter -D__NO_MATH_INLINES $(GCF) $(ADD_DEF) $(CFLAGS)'
clean:
rm -f $(OBJ) *.d *.d.* gmon.out $(TARGET)
@@ -45,7 +45,7 @@
endif
target: $(OBJ)
- $(LD) y4oi.o $(CFLAGS) -o y4oi $(LDF)
+ $(LD) $(OBJ) $(CFLAGS) -o y4oi $(LDF)
install: target
$(INSTALL) -d -m 0755 $(BINDIR)
Added: trunk/y4oi/filter.c
===================================================================
--- trunk/y4oi/filter.c (rev 0)
+++ trunk/y4oi/filter.c 2010-02-24 07:40:16 UTC (rev 16934)
@@ -0,0 +1,303 @@
+/*
+ *
+ * y4oi:
+ * A utility for doing several simple but essential operations
+ * on yuv4ogg interchange streams.
+ *
+ * y4oi copyright (C) 2009 Monty <monty at xiph.org>
+ *
+ * y4oi 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, or (at your option)
+ * any later version.
+ *
+ * y4oi 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 Postfish; see the file COPYING. If not, write to the
+ * Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ *
+ */
+
+#include "y4oi.h"
+
+extern void filter_output_process(fq_t *);
+
+static filter_t filter_list[] = {
+ {
+ "output",
+ "y4o stream output filter",
+ filter_output_process,
+ 1
+ },
+ {
+ NULL,
+ NULL,
+ NULL,
+ 0
+ }
+};
+
+static fq_t *filter_stack = NULL;
+static fq_t *filter_head = NULL;
+
+static void free_frame(fq_frame_t *frame){
+ if(frame){
+ if(frame->body)free(frame->body);
+ free(frame);
+ }
+}
+
+static void push_frame(fq_t *queue, fq_frame_t *frame){
+ if(frame){
+
+ /* Turn implicit state into explicit state */
+ switch(queue->state){
+ case QUEUE_INIT:
+ queue->state=QUEUE_STARTUP;
+ break;
+ case QUEUE_STARTUP:
+ if(frame->body)
+ queue->state=QUEUE_PROCESS;
+ break;
+ case QUEUE_PROCESS:
+ case QUEUE_FLUSH:
+ case QUEUE_FINISHED:
+ break;
+ }
+
+ /* append frame */
+ if(queue->head == NULL){
+ queue->head = queue->tail = frame;
+ }else{
+ queue->head->next = frame;
+ frame->prev = queue->head;
+ queue->head = frame;
+ }
+
+ /* there may be some lazy init to handle */
+ if(!queue->stream_depth){
+ queue->streams=frame->sn;
+ queue->stream_depth = calloc(frame->sn,sizeof(*queue->stream_depth));
+ }
+ if(queue->streams != frame->sn || frame->sno<0 || frame->sno>=queue->streams){
+ yerror("number of media streams changed; aborting.\n");
+ exit(1);
+ }
+
+ queue->stream_depth[frame->sno]++;
+ }
+}
+
+static fq_frame_t *pull_frame(fq_t *fq){
+ fq_frame_t *frame = fq->tail;
+ if(frame){
+ fq->tail = frame->next;
+ if(frame->next)
+ frame->next->prev = NULL;
+ else
+ fq->head = NULL;
+ frame->next=frame->prev=NULL;
+ fq->stream_depth[frame->sno]--;
+ }
+ return frame;
+}
+
+void filter_forward_frame(fq_t *fq){
+ if(fq->tail){
+ fq_frame_t *frame = pull_frame(fq);
+ if(fq->next){
+ push_frame(fq->next,frame);
+ fq->next->filter->process(fq->next);
+ }else{
+ if(frame)
+ free_frame(frame);
+ }
+ }
+}
+
+void filter_drop_frame(fq_t *fq){
+ if(fq->tail){
+ fq_frame_t *frame = pull_frame(fq);
+ if(frame)
+ free_frame(frame);
+ }
+}
+
+void filter_submit_frame(stream_t *s, unsigned char *b, int len, double pts){
+ if(filter_stack){
+ unsigned char *body=NULL;
+ fq_frame_t *frame=calloc(1,sizeof(*frame));
+ y4o_in_t *y = s->y;
+
+ if(len)
+ body = malloc(len);
+ memcpy(body,b,len);
+
+ frame->body = body;
+ frame->body_size=len;
+ frame->sn = y->num_streams;
+ frame->sno = s->stream_num;
+ frame->st = y->streams[frame->sno]->type;
+ frame->sp = y->streams[frame->sno]->m;
+ frame->pts = pts;
+ frame->bos = s->bos;
+ frame->synced = (y->synced || !force_no_sync);
+ s->bos = 0;
+
+ push_frame(filter_stack,frame);
+ filter_stack->filter->process(filter_stack);
+ }
+}
+
+void filter_flush(){
+ fq_t *fq = filter_stack;
+ while(fq){
+ if(fq->state != QUEUE_FLUSH && fq->state != QUEUE_FINISHED){
+ fq->state = QUEUE_FLUSH;
+ fq->filter->process(fq);
+ }
+ fq=fq->next;
+ }
+}
+
+static char *pretrim(char *in){
+ while(in && *in && *in<32) in++;
+ return in;
+}
+
+static void posttrim(char *in){
+ char *last=in;
+ while(in && *in){
+ if(*in>=32) last=in;
+ in++;
+ }
+ if(last && *last && *last>=32)
+ last[1]=0;
+}
+
+#undef NAME
+#define NAME "y4oi"
+
+int filter_append(char *cmd){
+ /* "filtername:option=value:option=value,filtername..." */
+ int err = 0;
+ char *c = cmd?strdup(cmd):NULL;
+ char *fptr = pretrim(c);
+
+ while(fptr && *fptr){
+ filter_t *f = filter_list;
+ char *eptr;
+ char *optr=NULL;
+ char *nptr;
+
+ /* pick off the filter name */
+ eptr = strchr(fptr,':');
+ if(eptr){
+ optr=eptr+1;
+ eptr[0]=0;
+ }
+ posttrim(fptr);
+ pretrim(optr);
+
+ /* get pointer for next filter */
+ nptr = strchr(fptr,',');
+ if(nptr){
+ nptr[0]=0;
+ nptr++;
+ }
+
+ /* search for the filter name */
+ while(f->name){
+ if(!strcmp(f->name,fptr)){
+ /* found the filter */
+ /* parse option key/val pairs */
+ /* count the colons */
+ int count=0;
+ char *p=optr;
+ char **key;
+ char **val;
+ while(p && *p){
+ count++;
+ p=strchr(p,':');
+ if(p)p++;
+ }
+
+ /* alloc and fill */
+ key = calloc(count+1,sizeof(*key));
+ val = calloc(count+1,sizeof(*val));
+ p=optr;
+ count=0;
+ while(p && *p){
+ char *n=strchr(p,':');
+ char *v;
+ if(n){
+ n[0]=0;
+ n++;
+ }
+
+ v=strchr(p,'=');
+ if(v){
+ v[0]=0;
+ v++;
+ }
+
+ posttrim(p);
+ posttrim(v);
+
+ if(*p){
+ key[count]=strdup(p);
+ if(v && *v)
+ val[count]=strdup(v);
+ else
+ val[count]=strdup("");
+ }else{
+ yerror("error parsing '%s' filter options\n",f->name);
+ err=1;
+ }
+ count++;
+ p=pretrim(n);
+ }
+
+ /* construct/append the filter */
+ {
+ fq_t *fq = calloc(1,sizeof(*fq));
+ fq->filter = f;
+ fq->option_keys=key;
+ fq->option_vals=val;
+ fq->state = QUEUE_INIT;
+
+ if(filter_stack){
+ filter_head->next = fq;
+ }else{
+ filter_head = filter_stack = fq;
+ }
+
+ /* call the filter with no frames or preset to give it an option to parse/check options */
+ fq->filter->process(fq);
+ }
+
+ break;
+ }
+ f++;
+ }
+
+ if(!f->name){
+ yerror("unknown filter '%s'\n\n",fptr);
+ err=1;
+ }
+
+ fptr=pretrim(nptr);
+ }
+ if(c)free(c);
+ return err;
+}
+
+int filter_is_last_output(void){
+ if(!filter_head) return 0;
+ return filter_head->filter->is_type_output_p;
+}
Added: trunk/y4oi/main.c
===================================================================
--- trunk/y4oi/main.c (rev 0)
+++ trunk/y4oi/main.c 2010-02-24 07:40:16 UTC (rev 16934)
@@ -0,0 +1,1259 @@
+/*
+ *
+ * y4oi:
+ * A utility for doing several simple but essential operations
+ * on yuv4ogg interchange streams.
+ *
+ * y4oi copyright (C) 2010 Monty <monty at xiph.org>
+ *
+ * y4oi 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, or (at your option)
+ * any later version.
+ *
+ * y4oi 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 Postfish; see the file COPYING. If not, write to the
+ * Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ *
+ */
+
+#define NAME "sync"
+#include "y4oi.h"
+
+int verbose=0;
+static double fill_secs=25.;
+static double sync_secs=1.;
+ratio force_fps={0,0};
+static int force_sync=0;
+int force_no_sync=0;
+int force_non_interlaced=0;
+char *lastprint="";
+static int no_rewrite=0;
+static int global_ended=0;
+
+const char *optstring = "b:c:e:f:F:ho:sSvqNi";
+struct option options [] = {
+ {"begin",required_argument,NULL,'b'},
+ {"cut",required_argument,NULL,'c'},
+ {"end",required_argument,NULL,'e'},
+ {"force-fps",required_argument,NULL,'f'},
+ {"filter",required_argument,NULL,'F'},
+ {"force-non-interlaced",no_argument,NULL,'i'},
+ {"help",no_argument,NULL,'h'},
+ {"no-rewrite",no_argument,NULL,'N'},
+ {"output",required_argument,NULL,'o'},
+ {"force-sync",no_argument,NULL,'s'},
+ {"force-no-sync",no_argument,NULL,'S'},
+ {"quiet",no_argument,NULL,'q'},
+ {"verbose",required_argument,NULL,'v'},
+
+ {NULL,0,NULL,0}
+};
+
+void usage(FILE *out){
+ fprintf(out,
+ "\ny4oi 20100222\n"
+ "performs basic operations on yuv4ogg interchange streams in prep for\n"
+ "theora encoding\n\n"
+
+ "USAGE:\n"
+ " y4oi [options] instream [instream...]\n\n"
+
+ "OPTIONS:\n"
+ " -b --begin HH:MM:SS.XX : discard starting data up to specified\n"
+ " begin time. Time is measured relative to\n"
+ " output clock\n"
+ " -c --cut BEGIN-END : drop output data in the specified interval.\n"
+ " Time is measured relative to output clock,\n"
+ " but output timestamps will be re-written\n"
+ " such that there is no gap.\n"
+ " -e --end HH:MM:SS.XX : stop working at specified end time. Time\n"
+ " is measured relative to output clock\n"
+ " -f --force-fps N:D : Override declared input fps\n"
+ " -F --filter filterchain : Apply one or more filters to stream; the\n"
+ " filter chain syntax is of the form:\n"
+ " filter1:option=value:option=value,filter2...\n"
+ " -h --help : print this usage message to stdout and exit\n"
+ " with status zero\n"
+ " -i --force-non-interlaced : Force input to be treated as noninterlaced\n"
+ " regardless of stream flag\n"
+ " -N --no-rewrite : do not rewrite output PTS values. Retain\n"
+ " original PTS values even if input streams\n"
+ " were synchronized.\n"
+ " -o --output FILE : output file/pipe (stdout default)\n"
+ " -q --quiet : completely silent operation\n"
+ " -s --force-sync : perform autosync even on streams marked as\n"
+ " already synchronized\n"
+ " -S --force-no-sync : do not perform stream sync even on unsynced\n"
+ " streams\n"
+ " -v --verbose : increase verbosity level by one\n"
+ "\n"
+ "VIDEO FILTERS/OPTIONS:\n"
+ "\n"
+ "OUTPUT FILTERS/OPTIONS:\n"
+ " output : output y4o stream to a file or pipe.\n"
+ " file=<value> . destination file; default is stdout\n\n"
+ );
+}
+
+static char timebuffer[80];
+static char *make_time_string(double s){
+ long hrs=s/60/60;
+ long min=s/60-hrs*60;
+ long sec=s-hrs*60*60-min*60;
+ long hsec=(s-(int)s)*100;
+ if(hrs>0){
+ snprintf(timebuffer,80,"%ld:%02ld:%02ld.%02ld",hrs,min,sec,hsec);
+ }else if(min>0){
+ snprintf(timebuffer,80,"%ld:%02ld.%02ld",min,sec,hsec);
+ }else{
+ snprintf(timebuffer,80,"%ld.%02ld",sec,hsec);
+ }
+ return timebuffer;
+}
+
+/* depth-limited fill: bounded attempt to prime any given queue at the
+ point any other queue reaches fill_secs deep actual depth-of-queue is
+ used, not PTS (as PTS is unreliable and could be way off into the
+ weeds) */
+
+static int limited_prime(y4o_in_t *y, int sno){
+ int i;
+ double clock[y->num_streams];
+ stream_t *s=y->streams[sno];
+ if(s->inq_tail) return 0;
+
+ for(i=0;i<y->num_streams;i++){
+ stream_t *si=y->streams[i];
+ clock[i] = si->tick_depth*si->tickduration;
+ }
+
+ while(!s->inq_tail){
+ frame_t *p=y4o_read_frame(y);
+ if(!p) return 1;
+ i=p->streamno;
+ while(p){
+ clock[i]+=p->duration;
+ p=p->next;
+ }
+ if(clock[i]>fill_secs){
+ yerror("Buffer depth exceeded configured limit due to A/V skew; aborting input stream.\n");
+ return 1;
+ }
+ }
+ return 0;
+}
+
+/* bounded sync search: fill sync/search streams a min of search_secs
+ deep past prime point each, filling no queue more than fill_secs past
+ prime point depth-of-queue is used, not PTS (as PTS is unreliable and
+ could be way off ino the weeds) */
+
+static int limited_prime_sync(y4o_in_t *y, int sync, int sno){
+ int i;
+ double clock[y->num_streams];
+ stream_t *sy=y->streams[sync];
+ stream_t *sn=y->streams[sno];
+ frame_t *p=NULL;
+
+ if(limited_prime(y,sync)) return 1;
+ if(limited_prime(y,sno)) return 1;
+
+ /* if both are already deep enough, done */
+ if(sy->tick_depth*sy->tickduration >= sync_secs &&
+ sn->tick_depth*sn->tickduration >= sync_secs)
+ return 0;
+
+ /* Count/fill from sync point */
+ memset(clock,0,sizeof(clock));
+ p=sy->inq_tail;
+ while(p){
+ i=p->streamno;
+ if(p==sn->inq_tail){
+ for(i=0;i<y->num_streams;i++){
+ clock[i] = 0;
+ }
+ }
+ clock[i]+=p->duration;
+ p=p->f_next;
+ }
+
+ while(clock[sync] < sync_secs ||
+ clock[sno] < sync_secs){
+ p=y4o_read_frame(y);
+ if(!p) return 1;
+ i=p->streamno;
+ while(p){
+ clock[i]+=p->duration;
+ p=p->next;
+ }
+ if(clock[i]>fill_secs)return 1;
+ }
+
+ return 0;
+}
+
+static int search_offset(y4o_in_t *y, int sync, int sno,
+ long long *outclock, double *outoffsets, frame_t **lastframe){
+ /* first check is against prev sync frame out */
+ stream_t *s=y->streams[sno];
+
+ if(sync==sno || sync==-1){
+ s->inq_tail->presync=1;
+ outoffsets[sno]=0.;
+ return 0;
+ }
+
+ if(s->inq_tail->presync)
+ return 0;
+ else{
+ stream_t *ss=y->streams[sync];
+ frame_t *p = lastframe[sync];
+ double sync_clock = outclock[sync]*ss->tickduration;
+ double sno_clock = outclock[sno]*s->tickduration;
+ double best_time;
+ frame_t *best_frame = NULL;
+ double sync_offset=0;
+ double last_sno_pts;
+ if(p){
+ sync_offset = p->pts + p->duration - sync_clock;
+ best_time = sno_clock - s->inq_tail->pts + sync_offset;
+ if(fabs(best_time) < s->tolerance){
+ s->inq_tail->presync=1;
+ return 0;
+ }
+ }else if (ss->inq_tail)
+ sync_offset = ss->inq_tail->pts-sync_clock;
+
+ /* it would appear we're potentially out of tolerance. pre-fill
+ to the desired sync depth */
+ if(limited_prime_sync(y, sync, sno))
+ return 1;
+
+ /* search from y's file tail up to s's tail packet just to make
+ sure there's not an out of order packet from the sync stream */
+ p=y->f_tail;
+ last_sno_pts = s->inq_tail->pts;
+ while(p!=s->inq_tail){
+ if(p->streamno==sync){
+ double time;
+ sync_offset = p->pts - sync_clock;
+ time = sno_clock - last_sno_pts + sync_offset;
+ if(!best_frame || fabs(time) <= fabs(best_time)){
+ best_time=time;
+ best_frame=s->inq_tail;
+ }
+ sync_clock += p->duration;
+ }
+ p=p->f_next;
+ }
+
+ /* search forward sync_secs from next sync frame looking for tightest timing */
+ {
+ int count=0;
+ double search_clock=sync_clock;
+ while(sync_clock<search_clock+sync_secs && p){
+ if(p->streamno==sync){
+ sync_offset = p->pts - sync_clock;
+ sync_clock += p->duration;
+ }
+ if(p->streamno==sno){
+ count++;
+ sno_clock += p->duration;
+ last_sno_pts = p->pts+p->duration;
+ }
+ if(p->streamno==sync || p->streamno==sno){
+ double time = sno_clock-last_sno_pts+sync_offset;
+ if(!best_frame || fabs(time) <= fabs(best_time)){
+ best_time=time;
+ best_frame=p;
+ }
+ }
+
+ p=p->f_next;
+ }
+ }
+
+ outoffsets[sno]=best_time;
+
+ /* mark presync up to the position of best timing */
+ p=y->f_tail;
+ while(p){
+ if(p->streamno==sno)
+ p->presync=1;
+ if(p==best_frame)break;
+ p=p->f_next;
+ }
+ return 0;
+ }
+}
+
+/* a frame_pop/free that doesn't lock swap data into memory */
+static int discard_head_frame(y4o_in_t *y,stream_t *s, long long *outclock){
+ frame_t *p=s->inq_head;
+ if(!p) return 1;
+
+ if(verbose){
+ if(s->type==STREAM_VIDEO){
+ yverbose("Stream %d [%s]: Dropping video frame to maintain sync\n",
+ p->streamno,
+ make_time_string((outclock[p->streamno]+s->tick_depth)*s->tickduration));
+ }else{
+ yverbose("Stream %d [%s]: Dropping %gs of audio to maintain sync\n",
+ p->streamno,
+ make_time_string((outclock[p->streamno]+s->tick_depth)*s->tickduration),
+ p->duration);
+ }
+ }
+
+ y4o_free_frame(p);
+
+ return 0;
+}
+
+/* a frame_pull/free that doesn't lock swap data into memory */
+static int discard_tail_frame(y4o_in_t *y,stream_t *s, long long *outclock){
+ frame_t *p=s->inq_tail;
+ if(!p) return 1;
+
+ if(verbose){
+ if(s->type==STREAM_VIDEO){
+ yverbose("Stream %d [%s]: Dropping video frame to maintain sync\n",
+ p->streamno,
+ make_time_string(outclock[p->streamno]*s->tickduration));
+ }else{
+ yverbose("Stream %d [%s]: Dropping %gs of audio to maintain sync\n",
+ p->streamno,
+ make_time_string(outclock[p->streamno]*s->tickduration),
+ p->duration);
+ }
+ }
+
+ y4o_free_frame(p);
+ return 0;
+}
+
+/* conditionally remove data from the head; in the case of audio it
+ can discard partial frames */
+static int trim_from_head(y4o_in_t *y,int sno,long long *outclock,double *outoffsets){
+ stream_t *s=y->streams[sno];
+ frame_t *p=s->inq_head;
+ if(!p)return 1;
+ if(s->type==STREAM_AUDIO){
+ int samples = p->len/(s->m.audio.ch*3);
+ int remsamples = outoffsets[sno]*s->m.audio.rate;
+ if(samples<=remsamples){
+ /* remove whole frame */
+ if(samples==remsamples)
+ outoffsets[sno] = 0.;
+ else
+ outoffsets[sno] -= p->duration;
+ return discard_head_frame(y,s,outclock);
+ }else{
+ /* trim frame */
+ if(remsamples>0){
+ /* altering the len will not mess up swap */
+ p->len -= remsamples * s->m.audio.ch*3;
+ p->ticks -= remsamples;
+ p->duration -= remsamples*s->tickduration;
+ s->tick_depth -= remsamples;
+
+ if(verbose)
+ yverbose("Stream %d [%s]: Dropping %gs of audio to maintain sync\n",
+ p->streamno,
+ make_time_string((outclock[sno]+s->tick_depth)*s->tickduration),
+ outoffsets[sno]);
+
+ }
+ outoffsets[sno] = 0.;
+ }
+ }else if(s->type==STREAM_VIDEO){
+ /* because video granularity is relatively coarse, only remove the
+ frame if it gets us closer to the time goal */
+ if(fabs(outoffsets[sno]-p->duration)<outoffsets[sno]){
+ /* remove frame */
+ outoffsets[sno] -= p->duration;
+ if(outoffsets[sno]<0) outoffsets[sno]=0.;
+ return discard_head_frame(y,s,outclock);
+ }else
+ outoffsets[sno]=0.;
+ }else
+ return 1;
+ return 0;
+}
+
+/* conditionally remove data from the tail; in the case of audio it
+ can discard partial frames */
+static int trim_from_tail(y4o_in_t *y,int sno,long long *outclock,double *outoffsets){
+ stream_t *s=y->streams[sno];
+ frame_t *p=s->inq_tail;
+ if(!p)return 1;
+
+ if(s->type==STREAM_AUDIO){
+ int samples = p->len/(s->m.audio.ch*3);
+ int remsamples = outoffsets[sno]*s->m.audio.rate;
+ if(samples<=remsamples){
+ /* remove whole frame */
+ if(samples==remsamples)
+ outoffsets[sno] = 0.;
+ else
+ outoffsets[sno] -= p->duration;
+ return discard_tail_frame(y,s,outclock);
+ }else{
+ /* trim frame */
+ if(remsamples>0){
+ int bytes = remsamples * s->m.audio.ch*3;
+
+ if(verbose)
+ yverbose("Stream %d [%s]: Dropping %gs of audio to maintain sync\n",
+ p->streamno,make_time_string(outclock[sno]*s->tickduration),
+ outoffsets[sno]);
+
+ /* load frame data into memory and release swap */
+ y4o_lock_frame(p);
+ memmove(p->data,p->data+bytes,p->len-bytes);
+ p->len -= bytes;
+ p->duration -= remsamples*s->tickduration;
+ p->ticks -= remsamples;
+ s->tick_depth -= remsamples;
+ }
+ outoffsets[sno] = 0.;
+ }
+ }else if(s->type==STREAM_VIDEO){
+ /* because video granularity is relatively coarse, only remove the
+ frame if it gets us closer to the time goal */
+ if(fabs(outoffsets[sno]-p->duration)<outoffsets[sno]){
+ /* remove frame */
+ outoffsets[sno] -= p->duration;
+ if(outoffsets[sno]<0) outoffsets[sno]=0.;
+ return discard_tail_frame(y,s,outclock);
+ }else
+ outoffsets[sno]=0.;
+ }else
+ return 1;
+ return 0;
+}
+
+typedef struct {
+ long long begin;
+ long long end; // one past
+} cutentry;
+
+typedef struct {
+ int cuts;
+ cutentry *cut;
+ int ended;
+} cutlist;
+
+static int intervalcmp(const void *p1, const void *p2){
+ cutentry *a=(cutentry *)p1;
+ cutentry *b=(cutentry *)p2;
+
+ return (int)(a->begin-b->begin);
+}
+
+/* Sanitize and distill the list of user-requested cuts into a more
+ efficient form */
+
+static cutlist *cuts_into_cutlist(y4o_in_t *y, interval *list, int n, ratio fps){
+ cutlist *ret=calloc(y->num_streams,sizeof(*ret));
+ cutentry *l = calloc(n,sizeof(*l));
+ int i,j;
+
+ /* Quantize the cut requests to primary video stream frames. Video
+ has much coarser resolution than audio, and we likely care about
+ the primary stream over any subsidary streams. Quantizing to the
+ vid stream we care about most will result in the most predictable
+ cut behavior. */
+
+ for(i=0;i<n;i++){
+ l[i].begin = (long long)rint(list[i].begin*fps.num/fps.den);
+ if(list[i].end<0)
+ l[i].end = -1;
+ else
+ l[i].end = (long long)rint(list[i].end*fps.num/fps.den);
+ }
+
+ /* sort by beginning time */
+ qsort(l,n,sizeof(*l),intervalcmp);
+
+ /* remove nonsensical cut regions */
+ for(i=0;i<n;i++){
+ int flag=0;
+ /* zero or negative range check */
+ if(l[i].begin>=l[i].end && !l[i].end<0) flag=1;
+ if(flag){
+ if(i+1<n)
+ memcpy(&l[i],&l[i+1],(n-i-1)*sizeof(l[i]));
+ n--;
+ }
+ }
+
+ /* merge overlapping cut ranges */
+ for(i=1;i<n;i++){
+ if(l[i].begin<=l[i-1].end){
+ if(l[i-1].end<l[i].end && !l[i-1].end<0)
+ l[i-1].end=l[i].end;
+ if(i+1<n)
+ memcpy(&l[i],&l[i+1],(n-i-1)*sizeof(l[i]));
+ n--;
+ }
+ }
+
+ /* convert cut ranges into native tick units of each stream,
+ tracking fractional ticks forward across cuts so there's no
+ cumulative drift */
+ for(j=0;j<y->num_streams;j++){
+ stream_t *s = y->streams[j];
+ double track=0.;
+ ret[j].cut=calloc(n,sizeof(*ret[j].cut));
+ for(i=0;i<n;i++){
+ double b = l[i].begin*(double)fps.den/fps.num;
+ double e = (l[i].end<0?-1:l[i].end*(double)fps.den/fps.num);
+ long long bt = ceil(b/s->tickduration-EPSILON);
+ long long et = (e<0?LONG_MAX:ceil(e/s->tickduration-EPSILON));
+ double bf = b/s->tickduration - bt;
+ double ef = (e<0?0:et - e/s->tickduration);
+
+ if(e>=0){
+ /* compensate for cumulative fractional ticks.
+ Rather than choosing the closest frame, we always choose the
+ next; it is better for video to slightly lead than slightly
+ lag. */
+ track += bf+ef;
+ if(track-EPSILON>1.){
+ et--;
+ track-=1;
+ }
+ if(track+EPSILON<0.){
+ et++;
+ track+=1;
+ }
+ }
+
+ ret[j].cut[i].begin = bt;
+ ret[j].cut[i].end = et;
+ }
+ ret[j].cuts=n;
+
+ }
+
+ return ret;
+}
+
+/* Cut functionality injected here; thus the seperate out and cut clocks */
+static void write_frame(y4o_in_t *y,
+ long long *outclock,
+ long long *cutclock, cutlist *cut,
+ frame_t *p){
+ int sno = p->streamno;
+ stream_t *s=y->streams[sno];
+ double outpts = (no_rewrite?p->pts:cutclock[sno]*s->tickduration);
+ cutlist *c=&cut[sno];
+
+ /* does the next cut impact this frame? */
+ if(c->cuts && outclock[sno]+p->ticks>c->cut[0].begin){
+ /* is the entire frame to be cut? */
+ if(outclock[sno]<c->cut[0].begin || outclock[sno]+p->ticks > c->cut[0].end){
+ /* no, frame must be bisected. */
+ /* is any of the frame prior to cut beginning? */
+ if(outclock[sno]<c->cut[0].begin){
+ /* yes; write the data prior to the cut, mutate current packet
+ and push the rest back onto the stream tail for another
+ pass */
+ int beginticks=c->cut[0].begin-outclock[sno];
+ int beginbytes=beginticks*s->bytes_per_tick;
+
+ filter_submit_frame(s, p->data, beginbytes, outpts);
+ cutclock[sno]+=beginticks;
+ outclock[sno]+=beginticks;
+
+ /* mutate packet */
+ p->data+=beginbytes;
+ p->len-=beginbytes;
+ p->ticks-=beginticks;
+
+ /* recurse */
+ write_frame(y, outclock, cutclock, cut, p);
+
+ /* restore packet */
+ p->data-=beginbytes;
+ p->len+=beginbytes;
+ p->ticks+=beginticks;
+
+ }else{
+ /* no; the beginning is cut. Drop it, mutate current packet
+ and push any remaining data at the end back onto the stream
+ tail for another pass (don't just write it, there may be
+ another cut here in this same frame) */
+ int beginticks=c->cut[0].end-outclock[sno];
+ int beginbytes=beginticks*s->bytes_per_tick;
+
+ outclock[sno]+=beginticks;
+
+ /* mutate packet */
+ p->data+=beginbytes;
+ p->len-=beginbytes;
+ p->ticks-=beginticks;
+
+ /* remove cut entry */
+ memmove(c->cut,c->cut+1,(c->cuts-1)*sizeof(*c->cut));
+ c->cuts--;
+
+ /* recurse */
+ write_frame(y, outclock, cutclock, cut, p);
+
+ /* restore packet */
+ p->data-=beginbytes;
+ p->len+=beginbytes;
+ p->ticks+=beginticks;
+
+ }
+ }else{
+ /* drop whole frame */
+ outclock[sno]+=p->ticks;
+ if(c->cuts==1 && c->cut[0].end==LONG_MAX && !c->ended){
+ c->ended=1;
+ global_ended++;
+ }
+ }
+ }else{
+ /* No cuts, write the frame */
+ filter_submit_frame(s, p->data, p->len, outpts);
+ cutclock[sno]+=p->ticks;
+ outclock[sno]+=p->ticks;
+ }
+}
+
+static void pull_write_frame(y4o_in_t *y,frame_t **lastframe,
+ long long *outclock,long long *cutclock, cutlist *cutticks,
+ int sno){
+ stream_t *s = y->streams[sno];
+ frame_t *p = s->inq_tail;
+
+ if(!p) return;
+
+ y4o_pull_frame(p);
+ write_frame(y,outclock,cutclock,cutticks,p);
+ if(lastframe[sno])
+ y4o_free_frame(lastframe[sno]);
+ lastframe[sno]=p;
+
+}
+
+/* conditionally duplicates last frame in video stream, or generates a frame of audio silence */
+static int duplicate_frame(y4o_in_t *y,frame_t **lastframe, long long *outclock,
+ long long *cutclock, cutlist *cutticks,
+ double *outoffsets, int sno){
+ stream_t *s = y->streams[sno];
+ frame_t *p = lastframe[sno];
+
+ if(!p){
+ /* no preceeding frame. Look to next frame instead */
+ if(limited_prime(y,sno)) return 1;
+ p=s->inq_tail;
+ y4o_lock_frame(p);
+ }
+
+ switch(s->type){
+ case STREAM_VIDEO:
+ /* we only dup the frame if doing so gets us closer to the ideal clock sync */
+ if(fabs(outoffsets[sno]+p->duration)<fabs(outoffsets[sno])){
+ /* dup it */
+ if(verbose)
+ yverbose("Stream %d [%s]: Repeating video frame to maintain sync\n",
+ p->streamno,make_time_string(outclock[sno]*s->tickduration));
+
+ write_frame(y,outclock,cutclock,cutticks,p);
+ outoffsets[sno]+=p->duration;
+ if(outoffsets[sno]>0)outoffsets[sno]=0.;
+ }else
+ outoffsets[sno]=0.;
+ break;
+ case STREAM_AUDIO:
+ /* can't dup audio. Write silence (evenrually, we want to
+ extrapolate and smooth the discontinuity) */
+ {
+ frame_t lp;
+ /* number of samples already bounded by fill_secs */
+ int samples = -outoffsets[sno] * s->m.audio.rate;
+ int bytes = samples * s->m.audio.ch;
+ unsigned char *data=calloc(bytes,1);
+
+ if(verbose)
+ yverbose("Stream %d [%s]: Adding %gs of silence to maintain sync\n",
+ p->streamno,make_time_string(outclock[sno]*s->tickduration),-outoffsets[sno]);
+
+ lp.data=data;
+ lp.len=bytes;
+ lp.streamno=sno;
+ lp.pts=(p?p->pts:0.);
+ lp.ticks=samples;
+ write_frame(y,outclock,cutclock,cutticks,&lp);
+ free(data);
+ if(p)p->pts-=outoffsets[sno];
+ outoffsets[sno]=0.;
+ }
+ break;
+ default:
+ break;
+ }
+ return 0;
+}
+
+static int parse_time(char *s,double *t){
+ double secf;
+ long secl;
+ const char *pos;
+ char *end;
+ int err;
+ err=0;
+ secl=0;
+ pos=strchr(optarg,':');
+ if(pos!=NULL){
+ char *pos2;
+ secl=strtol(optarg,&end,10)*60;
+ err|=pos!=end;
+ pos2=strchr(++pos,':');
+ if(pos2!=NULL){
+ secl=(secl+strtol(pos,&end,10))*60;
+ err|=pos2!=end;
+ pos=pos2+1;
+ }
+ }
+ else pos=optarg;
+ secf=strtod(pos,&end);
+ if(err||*end!='\0')return -1;
+
+ *t = secl+secf;
+ return 0;
+}
+
+static int parse_interval(char *s,interval *i){
+ char *pos=strchr(s,'-');
+ if(!pos)return 1;
+ *pos='\0';
+ parse_time(s,&i->begin);
+ parse_time(pos+1,&i->end);
+ *pos='-';
+ return 0;
+}
+
+static int parse_ratio(char *s, ratio *r){
+ char *pos=strpbrk(optarg,":/");
+ if(pos){
+ r->num=atof(optarg);
+ r->den=atof(pos+1);
+ if(r->den==0)return 1;
+ }else{
+ r->num=atof(optarg);
+ r->den=1;
+ }
+ if(r->num==0)return 1;
+ return 0;
+}
+
+#undef NAME
+#define NAME NULL
+
+int main(int argc,char *const *argv){
+ int c,long_option_index;
+
+ long long *outclock=NULL;
+ long long *cutclock=NULL;
+ cutlist *cutticks=NULL;
+ double *outoffsets=NULL;
+ frame_t **lastframe=NULL;
+
+ /* for time cuts */
+ double begin=-1;
+ double end=-1;
+ interval *ccut=NULL;
+ int ccuts=0;
+
+ int filterprime=0;
+
+ FILE **infile = NULL;
+ char *const*infilenames=NULL;
+ int infiles=0,i;
+
+ char *outfile=NULL;
+
+ int primary_video=-1;
+ int primary_audio=-1;
+ int sync_stream=-1;
+
+ y4o_in_t *ty=NULL;
+ y4o_in_t *y=NULL;
+
+ while((c=getopt_long(argc,argv,optstring,options,&long_option_index))!=EOF){
+ switch(c){
+ case 'h':
+ usage(stdout);
+ return 0;
+ case 'v':
+ verbose++;
+ break;
+ case 'q':
+ verbose=-1;
+ break;
+ case 'b':
+ parse_time(optarg,&begin);
+ break;
+ case 'e':
+ parse_time(optarg,&end);
+ break;
+ case 'f':
+ if(parse_ratio(optarg,&force_fps)){
+ yerror("cannot parse fps argument '%s'\n",optarg);
+ exit(1);
+ }
+ break;
+ case 'F':
+ if(filter_append(optarg)){
+ exit(1);
+ }
+ break;
+ case 'c':
+ if(!ccut)
+ ccut=calloc(1,sizeof(*ccut));
+ else
+ ccut=realloc(ccut,(ccuts+1)*sizeof(*ccut));
+ if(parse_interval(optarg,ccut+ccuts)){
+ yerror("cannot parse cut interval '%s'\n",optarg);
+ exit(1);
+ }
+ ccuts++;
+ break;
+ case 's':
+ force_sync=1;
+ force_no_sync=0;
+ break;
+ case 'i':
+ force_non_interlaced=1;
+ break;
+ case 'S':
+ force_sync=0;
+ force_no_sync=1;
+ break;
+ case 'N':
+ no_rewrite=1;
+ break;
+ case 'o':
+ if(outfile)
+ free(outfile);
+ outfile=strdup(optarg);
+ break;
+ default:
+ usage(stderr);
+ exit(1);
+ }
+ }
+
+ if(optind<argc){
+ /* assume that anything following the options must be an input filename */
+ infiles=argc-optind;
+ infile=calloc(infiles,sizeof(*infile));
+ infilenames=argv+optind;
+
+ for(i=0;i<infiles;i++){
+ infile[i]=fopen(argv[optind+i],"rb");
+ if(infile[i]==NULL){
+ yerror("Unable to open '%s' for input; %s\n",argv[optind+i],strerror(errno));
+ exit(1);
+ }
+ }
+ optind++;
+ }
+
+ /* is the end of the filter stack explicitly an output filter? */
+ if(!filter_is_last_output()){
+ char *buffer=NULL;
+ /* add an implicit output filter */
+ if(outfile){
+ asprintf(&buffer,"output:file=%s",outfile);
+ }else{
+ asprintf(&buffer,"output");
+ }
+
+ if(filter_append(buffer)){
+ exit(1);
+ }
+ free(buffer);
+ }
+
+ for(i=0;i<infiles;i++){
+ int j;
+
+ yinfo("Begin processing input stream \"%s\"...\n",infilenames[i]);
+
+#undef NAME
+#define NAME "input"
+
+ ty=y4o_open_in(infile[i]);
+
+ if(y==NULL){
+
+ for(j=0;j<ty->num_streams;j++){
+ stream_t *s = ty->streams[j];
+ switch(s->type){
+ case STREAM_VIDEO:
+ if(primary_video<0)primary_video=j;
+ yverbose("Using stream %d as primary video stream\n",j);
+ break;
+ case STREAM_AUDIO:
+ if(primary_audio<0)primary_audio=j;
+ yverbose("Using stream %d as primary audio stream\n",j);
+ break;
+ default:
+ break;
+ }
+ }
+ if(sync_stream==-1)
+ sync_stream=primary_audio;
+ }
+
+ /* warn about force mismatches, unknown chroma spaces */
+ if(!force_fps.num && primary_video>=0){
+ force_fps.num=ty->streams[primary_video]->m.video.fps_n;
+ force_fps.den=ty->streams[primary_video]->m.video.fps_d;
+ }
+
+ if(!cutticks){
+ /* translate begin/end to cut intervals */
+ if(begin!=-1){
+ if(!ccut)
+ ccut=calloc(1,sizeof(*ccut));
+ else
+ ccut=realloc(ccut,(ccuts+1)*sizeof(*ccut));
+ ccut[ccuts].begin=0;
+ ccut[ccuts].end=begin;
+ ccuts++;
+ }
+
+ if(end!=-1){
+ if(!ccut)
+ ccut=calloc(1,sizeof(*ccut));
+ else
+ ccut=realloc(ccut,(ccuts+1)*sizeof(*ccut));
+ ccut[ccuts].begin=end;
+ ccut[ccuts].end=-1;
+ ccuts++;
+ }
+
+ cutticks=cuts_into_cutlist(ty, ccut, ccuts, force_fps);
+
+ }
+
+ for(j=0;j<ty->num_streams;j++){
+ switch(ty->streams[j]->type){
+ case STREAM_VIDEO:
+ if(fabs(force_fps.num/force_fps.den -
+ ty->streams[j]->m.video.fps_n/(double)ty->streams[j]->m.video.fps_d)>EPSILON){
+ yinfo("Forcing stream %d file \"%s\" to %.3ffps.\n",
+ j,infilenames[i],force_fps.num/(double)force_fps.den);
+ }
+ if(ty->streams[j]->m.video.format == C420unknown){
+ ywarn("Assuming mpeg2 chroma positioning for stream %d.\n",
+ j);
+ ty->streams[j]->m.video.format = C420mpeg2;
+ }
+ if(ty->streams[j]->m.video.format == C422unknown){
+ ywarn("Assuming smpte chroma positioning for stream %d.\n",
+ j);
+ ty->streams[j]->m.video.format = C422smpte;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ if(y){
+ /* if opening a new file in sequence, it must have the same traits as the first */
+ if(ty->num_streams!=y->num_streams){
+ yerror("Number of streams mismatch in file \"%s\".\n"
+ "\tSequential input streams must have matching stream\n"
+ "\tnumbers and types.\n",
+ infilenames[i]);
+ exit(1);
+ }
+ for(j=0;j<y->num_streams;j++){
+ stream_t *r = y->streams[j];
+ stream_t *s = ty->streams[j];
+
+ if(s->type != r->type){
+ yerror("Stream %d type mismatch in file \"%s\".\n"
+ "\tSequential input streams must have matching stream\n"
+ "\tnumbers and types.\n",
+ j,infilenames[i]);
+ exit(1);
+ }
+
+ /* eventually, we should be able to autoconvert and force all
+ these settings */
+ switch(s->type){
+ case STREAM_VIDEO:
+ if(s->m.video.w != r->m.video.w ||
+ s->m.video.h != r->m.video.h){
+ yerror("Video dimensions mismatch in stream %d file \"%s\".\n"
+ "\tSequential video streams must have matching dimensions.\n",
+ j,infilenames[i]);
+ exit(1);
+ }
+ if(abs(s->m.video.pa_n/(double)s->m.video.pa_d -
+ r->m.video.pa_n/(double)r->m.video.pa_d)>EPSILON){
+ ywarn("Pixel aspect mismatch in stream %d file \"%s\".\n"
+ "\tForcing aspect to match original stream.\n",
+ j,infilenames[i]);
+ s->m.video.pa_n = r->m.video.pa_n;
+ s->m.video.pa_d = r->m.video.pa_d;
+ }
+ if(s->m.video.format != r->m.video.format){
+ yerror("Chroma format mismatch in stream %d file \"%s\".\n"
+ "\tSequential video streams must have matching chroma\n"
+ "\tformats.\n",
+ j,infilenames[i]);
+ exit(1);
+ }
+ if(s->m.video.i != r->m.video.i){
+ yerror("Interlacing format mismatch in stream %d file \"%s\".\n"
+ "\tSequential video streams must have matching interlacing\n"
+ "\tformats.\n",
+ j,infilenames[i]);
+ exit(1);
+ }
+ break;
+ case STREAM_AUDIO:
+ if(s->m.audio.rate != r->m.audio.rate){
+ yerror("Audio rate mismatch in stream %d file \"%s\"."
+ "\tSequential audio streams must have matching sampling rate\n"
+ "\tand channels\n.",
+ j,infilenames[i]);
+ exit(1);
+ }
+ if(s->m.audio.ch != r->m.audio.ch){
+ yerror("Audio rate mismatch in stream %d file \"%s\"."
+ "\tSequential audio streams must have matching sampling rate\n"
+ "\tand channels\n.",
+ j,infilenames[i]);
+ exit(1);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ /* ready output tracking if unallocated *****************************************************/
+ if(!outclock){
+ outclock = calloc(ty->num_streams,sizeof(*outclock));
+ cutclock = calloc(ty->num_streams,sizeof(*cutclock));
+ outoffsets = calloc(ty->num_streams,sizeof(*outoffsets));
+ lastframe = calloc(ty->num_streams,sizeof(*lastframe));
+ }
+
+#undef NAME
+#define NAME "sync"
+
+ /* prime input stream queues ****************************************************************/
+ /* not necessary, but it allows reporting some timing information */
+ for(j=0;j<ty->num_streams;j++){
+ if(limited_prime(ty,j)){
+ yerror("Did not find start of stream %d within first %f seconds\n"
+ "\tof data. Aborting.\n",j,sync_secs);
+ exit(1);
+ }else{
+ if(verbose){
+ int h=floor(ty->streams[j]->inq_tail->pts/60/60);
+ int m=floor(ty->streams[j]->inq_tail->pts/60)-h*60;
+ int s=floor(ty->streams[j]->inq_tail->pts)-h*60*60-m*60;
+ int d=floor((ty->streams[j]->inq_tail->pts-floor(ty->streams[j]->inq_tail->pts))*100);
+ yverbose("Stream %d initial PTS %02d:%02d:%02d.%02d\n",
+ j,h,m,s,d);
+ }
+ }
+ }
+
+ /* prime the filter chain with empty frames that carry stream
+ params; this makes sure we write an output header in timely fashion *********************/
+ if(!filterprime){
+ for(j=0;j<ty->num_streams;j++)
+ filter_submit_frame(ty->streams[j], NULL, 0, -1);
+ filterprime=1;
+ }
+
+ yverbose("\n");
+
+ if(!ty->synced && !force_no_sync){
+
+ /* find clock start times of new substreams ***********************************************/
+ {
+ long long rec_outclock[ty->num_streams];
+ for(j=0;j<ty->num_streams;j++){
+ rec_outclock[j]=outclock[j];
+ if(y)
+ rec_outclock[j]+=y->streams[j]->tick_depth;
+ }
+ for(j=0;j<ty->num_streams;j++)
+ search_offset(ty,sync_stream,j,outclock,outoffsets,lastframe);
+ }
+
+ /* trim holdover streams if needed given new offsets **************************************/
+ if(y){
+ stream_t *ss=y->streams[sync_stream];
+ double sync_time = ss->tickduration*outclock[sync_stream];
+ for(j=0;j<y->num_streams;j++){
+ if(outoffsets[j]>0){
+ /* we have too many frames; where to trim? */
+ stream_t *ys = y->streams[j];
+ double time = ys->tickduration*outclock[j];
+ if(time>sync_time){
+ /* trim holdover back, but no farther than sync_time */
+ if(time-outoffsets[j]<sync_time)
+ outoffsets[j]=time-sync_time;
+
+ /* remove frames from head of holdover stream */
+ while(outoffsets[j]>0)
+ if(trim_from_head(y,j,outclock,outoffsets))break;
+ }
+ }
+ }
+ }
+
+ /* sync loop *****************************************************************************/
+ while(global_ended < ty->num_streams){
+ double earliest=outclock[0]*ty->streams[0]->tickduration;
+ int sno=0;
+
+ /* look for lowest clock time of stream queues */
+ for(j=1;j<ty->num_streams;j++){
+ double time=outclock[j]*ty->streams[j]->tickduration;
+ if(time<earliest){
+ earliest=time;
+ sno=j;
+ }
+ }
+
+ /* we know which stream to pull first */
+ if(y && y->streams[sno]->inq_tail){
+ /* holdover stream; rules here are a little different. We simply flush. */
+ pull_write_frame(y,lastframe,outclock,cutclock,cutticks,sno);
+ }else{
+ stream_t *s=ty->streams[sno];
+ if(limited_prime(ty,sno)){
+ break;
+ }
+ if(outoffsets[sno]<0){
+ /* there's an already-established gap in the stream. dup it out */
+ if(duplicate_frame(ty,lastframe,outclock,cutclock,cutticks,outoffsets,sno)){
+ break;
+ }
+ }else if (outoffsets[sno]>0){
+ trim_from_tail(ty,sno,outclock,outoffsets);
+ }else{
+ if(search_offset(ty,sync_stream,sno,outclock,outoffsets,lastframe)){
+ break;
+ }
+ if(s->inq_tail->presync && outoffsets[sno]==0)
+ pull_write_frame(ty,lastframe,outclock,cutclock,cutticks,sno);
+ }
+ }
+ if(y && !y->f_tail){
+ y4o_close_in(y);
+ y=NULL;
+ }
+ }
+
+ if(y && global_ended < ty->num_streams){
+ /* got through loop without flushing all of y? Alert the
+ user, dump the frames */
+ int flag=0;
+
+ for(j=0;j<y->num_streams;j++){
+ if(y->streams[j]->inq_tail){
+ ywarn("Previous stream %d completely overlaps current stream?\n",j);
+ flag=1;
+ }
+ }
+ if(!flag)
+ ywarn("Previous input stream queues emptied, but stream still open?\n");
+
+ y4o_close_in(y);
+ }
+
+ }else{
+ if(y) y4o_close_in(y);
+
+ /* if we're not trying to sync, just write the frames in output clock order */
+ while(global_ended < ty->num_streams){
+ double earliest;
+ int sno=-1;
+
+ /* look for lowest clock time of stream queues */
+ for(j=0;j<ty->num_streams;j++){
+ stream_t *s = ty->streams[j];
+ double time=outclock[j]*s->tickduration;
+ limited_prime(ty,j);
+ if(s->inq_tail && (sno==-1 || time<earliest)){
+ earliest=time;
+ sno=j;
+ }
+ }
+
+ if(sno==-1)break;
+ pull_write_frame(ty,lastframe,outclock,cutclock,cutticks,sno);
+ }
+ }
+
+ y=ty;
+
+ }
+
+#undef NAME
+#define NAME NULL
+
+ if(y){
+ yinfo("Flushing end of stream...\n");
+ while(y->f_tail && global_ended < ty->num_streams){
+ /* continue to obey clock order */
+ double earliest=-1;
+ int j,sno=-1;
+
+ /* look for lowest clock time of remaning stream queues */
+ for(j=0;j<ty->num_streams;j++){
+ if(y->streams[j]->inq_tail){
+ double time=outclock[j]*y->streams[j]->tickduration;
+ if(sno==-1 || time<earliest){
+ earliest=time;
+ sno=j;
+ }
+ }
+ }
+ pull_write_frame(y,lastframe,outclock,cutclock,cutticks,sno);
+ }
+ filter_flush();
+ yinfo("Done processing all streams\n");
+
+ if(cutticks){
+ for(i=0;i<y->num_streams;i++)
+ if(cutticks[i].cut)free(cutticks[i].cut);
+ free(cutticks);
+ }
+ y4o_close_in(y);
+ }else{
+ usage(stderr);
+ }
+ if(outclock)free(outclock);
+ if(cutclock)free(cutclock);
+ if(outoffsets)free(outoffsets);
+ if(lastframe)free(lastframe);
+ return 0;
+}
Added: trunk/y4oi/output.c
===================================================================
--- trunk/y4oi/output.c (rev 0)
+++ trunk/y4oi/output.c 2010-02-24 07:40:16 UTC (rev 16934)
@@ -0,0 +1,182 @@
+/*
+ *
+ * y4oi:
+ * A utility for doing several simple but essential operations
+ * on yuv4ogg interchange streams.
+ *
+ * y4oi copyright (C) 2010 Monty <monty at xiph.org>
+ *
+ * y4oi 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, or (at your option)
+ * any later version.
+ *
+ * y4oi 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 Postfish; see the file COPYING. If not, write to the
+ * Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ *
+ */
+
+#include "y4oi.h"
+
+typedef struct {
+ char *filename;
+ FILE *outfile;
+ int wrote_header;
+} internal;
+
+static void parse_options(fq_t *queue){
+ internal *i = queue->internal;
+ char **key = queue->option_keys;
+ char **val = queue->option_vals;
+ while(key && *key){
+
+ if(!strcmp(*key,"file")){
+ if(!*val){
+ yerror("No filename given for option 'file'\n");
+ exit(1);
+ }
+ i->filename=strdup(*val);
+ if(i->outfile)
+ fclose(i->outfile);
+ i->outfile=fopen(i->filename,"wb");
+ if(!i->outfile){
+ yerror("Unable to open '%s' for output; %s\n",i->filename,strerror(errno));
+ exit(1);
+ }
+ }else{
+ yerror("No such option '%s'\n",*key);
+ }
+
+ key++;
+ val++;
+ }
+
+ if(!i->outfile){
+ i->outfile=stdout;
+ i->filename=strdup("stdout");
+ }
+}
+
+/* the output filter needs to wait until it has seen parameter frames
+ from each stream before it can write an output header */
+static void startup_header(fq_t *queue){
+ internal *i = queue->internal;
+
+ if(!i->wrote_header){
+ int j;
+ for(j=0;j<queue->streams;j++)
+ if(!queue->stream_depth[j])break;
+
+ if(j==queue->streams){
+
+ /* container header */
+ fprintf(i->outfile,"YUV4OGG S%c\n", (queue->tail->synced ? 'n' : 'y'));
+
+ /* stream headers */
+ for(j=0;j<queue->streams;j++){
+ /* find first stream of the given type */
+ fq_frame_t *ptr = queue->tail;
+ while(ptr && ptr->sno != j)
+ ptr=ptr->next;
+
+ if(!ptr){
+ yerror("Output couldn't find stream number %d\n\n",j);
+ exit(1);
+ }
+
+ switch(ptr->st){
+ case STREAM_AUDIO:
+ fprintf(i->outfile,"AUDIO R%d C%d\n",
+ ptr->sp.audio.rate,ptr->sp.audio.ch);
+ break;
+ case STREAM_VIDEO:
+ fprintf(i->outfile,"VIDEO W%d H%d F%d:%d I%c A%d:%d C%s\n",
+ ptr->sp.video.w,
+ ptr->sp.video.h,
+ ptr->sp.video.fps_n,
+ ptr->sp.video.fps_d,
+ ptr->sp.video.i?(ptr->sp.video.i==TOP_FIRST?'t':'b'):'p',
+ ptr->sp.video.pa_n,
+ ptr->sp.video.pa_d,
+ chromaformat[ptr->sp.video.format]);
+ break;
+ default:
+ yerror("Unknown stream type in output filter.\n");
+ exit(1);
+ }
+ }
+ i->wrote_header=1;
+ }else
+ return;
+ }
+}
+
+void filter_output_process(fq_t *queue){
+ internal *i = queue->internal;
+ fq_frame_t *ptr = NULL;
+
+ switch(queue->state){
+ case QUEUE_INIT:
+ /* called once during filter setup immediately after allocating
+ queue. The queue is uninitialized; only filter options are
+ filled in. */
+
+ queue->internal = calloc(1,sizeof(*i));
+ parse_options(queue);
+
+ break;
+ case QUEUE_STARTUP:
+ /* called once for each stream as a way of passing through initial
+ parameters of each stream. Queue is fully initialized and
+ holds only one or more empty frames bearing parameters. */
+ if(!queue->tail) return;
+ startup_header(queue);
+
+ break;
+ case QUEUE_PROCESS:
+ /* Called for each submission of a new frame (of any stream type)
+ during normal processing */
+ case QUEUE_FLUSH:
+ /* Same as QUEUE_PROCESS except that no further frames will be
+ submitted; the filter is directed to flush any queued frames */
+ if(!i->wrote_header){
+ yerror("Output filter did not see all stream parameters before\n"
+ "\tbeginning of processing.\n");
+ exit(1);
+ }
+ if(!queue->tail) break;
+
+ while((ptr=queue->tail)){
+ if(ptr->body){
+ if(fprintf(i->outfile,"FRAME S%d L%d P%.3f\n",ptr->sno,ptr->body_size,ptr->pts)<0 ||
+ fwrite(ptr->body,1,ptr->body_size,i->outfile)<(unsigned)ptr->body_size){
+ yerror("Unable to write to output; %s\n",strerror(errno));
+ exit(1);
+ }
+ }
+
+ /* It's possible something follows this output filter. Safe to call
+ if the queue is empty (eg, during QUEUE_INIT)*/
+ filter_forward_frame(queue);
+
+ }
+
+ if(queue->state==QUEUE_FLUSH){
+ fclose(i->outfile);
+ i->outfile=NULL;
+ queue->state=QUEUE_FINISHED;
+ }
+ break;
+
+ case QUEUE_FINISHED:
+ break;
+ }
+
+}
Added: trunk/y4oi/y4o.c
===================================================================
--- trunk/y4oi/y4o.c (rev 0)
+++ trunk/y4oi/y4o.c 2010-02-24 07:40:16 UTC (rev 16934)
@@ -0,0 +1,797 @@
+/*
+ *
+ * y4oi:
+ * A utility for doing several simple but essential operations
+ * on yuv4ogg interchange streams.
+ *
+ * y4oi copyright (C) 2010 Monty <monty at xiph.org>
+ *
+ * y4oi 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, or (at your option)
+ * any later version.
+ *
+ * y4oi 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 Postfish; see the file COPYING. If not, write to the
+ * Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ *
+ */
+
+#define NAME "input"
+#include "y4oi.h"
+
+char *chromaformat[]={
+ "mono", //0
+ "411ntscdv", //1
+ "420jpeg", //2 chroma sample is centered vertically and horizontally between luma samples
+ "420mpeg2", //3 chroma sample is centered vertically between lines, cosited horizontally */
+ "420paldv", //4 chroma sample is cosited vertically and horizontally */
+ "420unknown",//5
+ "422jpeg", //6 chroma sample is horizontally centered between luma samples */
+ "422smpte", //7 chroma sample is cosited horizontally */
+ "422unknown",//8
+ "444", //9
+ NULL
+};
+
+unsigned char chromabits[]={
+ 8,
+ 12,
+ 12,
+ 12,
+ 12,
+ 12,
+ 16,
+ 16,
+ 16,
+ 24
+};
+
+char *chromaformat_long[]={
+ "monochrome", //0
+ "4:1:1 [ntscdv chroma]", //1
+ "4:2:0 [jpeg chroma]", //2 chroma sample is centered vertically and horizontally between luma samples
+ "4:2:0 [mpeg2 chroma]", //3 chroma sample is centered vertically between lines, cosited horizontally */
+ "4:2:0 [paldv chroma]", //4 chroma sample is cosited vertically and horizontally */
+ "4:2:0 [unknown chroma]",//5
+ "4:2:2 [jpeg chroma]", //6 chroma sample is horizontally centered between luma samples */
+ "4:2:2 [smpte chroma]", //7 chroma sample is cosited horizontally */
+ "4:2:2 [unknown chroma]",//8
+ "4:4:4", //9
+ NULL
+};
+
+static int read_container_header(FILE *f, int *sync){
+ char line[80];
+ char *p;
+ int n;
+ size_t ret,len;
+
+ len=sizeof("YUV4OGG");
+ ret = fread(line, 1, len,f);
+ if (ret<len){
+ if(feof(f))
+ yerror("EOF reading y4o file header\n");
+ return 1;
+ }
+
+ if (strncmp(line, "YUV4OGG ", sizeof("YUV4OGG ")-1)){
+ yerror("cannot parse y4o file header\n");
+ return 1;
+ }
+
+ /* proceed to get the tags... (overwrite the magic) */
+ for (n = 0, p = line; n < 80; n++, p++) {
+ if ((ret=fread(p,1,1,f))<1){
+ if(feof(f))
+ yerror("EOF reading y4o file header\n");
+ return 1;
+ }
+ if (*p == '\n') {
+ *p = '\0';
+ break;
+ }
+ }
+ if (n >= 80) {
+ yerror("line too long reading y4o file header\n");
+ return 1;
+ }
+
+ /* read tags */
+ /* S%c = sync */
+
+ {
+ char *token, *value;
+ char tag;
+
+ *sync=-1;
+
+ /* parse fields */
+ for (token = strtok(line, " ");
+ token != NULL;
+ token = strtok(NULL, " ")) {
+ if (token[0] == '\0') continue; /* skip empty strings */
+ tag = token[0];
+ value = token + 1;
+ switch (tag) {
+ case 'S':
+ switch(token[1]){
+ case 'y':
+ case 'Y':
+ *sync = 1;
+ break;
+ case 'n':
+ case 'N':
+ *sync=0;
+ break;
+ default:
+ yerror("unknown sync tag setting in y4o header\n");
+ return 1;
+ }
+ break;
+ default:
+ yerror("unknown file tag in y4o header\n");
+ return 1;
+ }
+ }
+ }
+
+ if(*sync==-1)return 1;
+
+ return 0;
+}
+
+static stream_t *read_stream_header(FILE *f){
+ char line[80];
+ char *p;
+ int n;
+ size_t ret;
+ int af=0,vf=0;
+ stream_t *s;
+
+ /* Check for past the stream headers and at first frame */
+ line[0]=getc(f);
+ ungetc(line[0],f);
+ if(line[0]=='F'){
+ return NULL;
+ }
+
+ s=calloc(1,sizeof(*s));
+
+ ret = fread(line, 1, sizeof("AUDIO"), f);
+ if (ret<sizeof("AUDIO")){
+ if(feof(f))
+ yerror("EOF reading y4o stream header\n");
+ goto err;
+ }
+
+ if (!strncmp(line, "VIDEO ", sizeof("VIDEO ")-1)) vf=1;
+ else if (!strncmp(line, "AUDIO ", sizeof("AUDIO ")-1)) af=1;
+
+ if(!(af||vf)){
+ yerror("unknown y4o stream header type\n");
+ goto err;
+ }
+
+ if(vf){
+ /* video stream! */
+ /* proceed to get the tags... (overwrite the magic) */
+ for (n = 0, p = line; n < 80; n++, p++) {
+ if ((ret=fread(p, 1, 1, f))<1){
+ if(feof(f))
+ yerror("EOF reading y4o video stream header\n");
+ goto err;
+ }
+ if (*p == '\n') {
+ *p = '\0';
+ break;
+ }
+ }
+ if (n >= 80) {
+ yerror("line too long reading y4o video stream header\n");
+ goto err;
+ }
+
+ /* read tags */
+ /* W%d = width
+ H%d = height
+ F%d:%d = fps
+ I%c = interlace
+ A%d:%d = pixel aspect
+ C%s = chroma format
+ */
+
+ {
+ char *token, *value;
+ char tag;
+ int i;
+ s->m.video.format=-1;
+ s->m.video.i=-1;
+
+ /* parse fields */
+ for (token = strtok(line, " ");
+ token != NULL;
+ token = strtok(NULL, " ")) {
+ if (token[0] == '\0') continue; /* skip empty strings */
+ tag = token[0];
+ value = token + 1;
+ switch (tag) {
+ case 'W':
+ s->m.video.w = atoi(token+1);
+ break;
+ case 'H':
+ s->m.video.h = atoi(token+1);
+ break;
+ case 'F':
+ {
+ char *pos=strchr(token+1,':');
+ if(pos){
+ *pos='\0';
+ s->m.video.fps_n = atoi(token+1);
+ s->m.video.fps_d = atoi(pos+1);
+ *pos=':';
+ }else{
+ s->m.video.fps_n = atoi(token+1);
+ s->m.video.fps_d = 1;
+ }
+ }
+ break;
+ case 'I':
+ switch(token[1]){
+ case 'p':
+ case 'P':
+ s->m.video.i=PROGRESSIVE;
+ break;
+ case 't':
+ case 'T':
+ s->m.video.i=TOP_FIRST;
+ break;
+ case 'b':
+ case 'B':
+ s->m.video.i=BOTTOM_FIRST;
+ break;
+ default:
+ yerror("unknown y4o video interlace setting\n");
+ goto err;
+ }
+ case 'A':
+ {
+ char *pos=strchr(token+1,':');
+ if(pos){
+ *pos='\0';
+ s->m.video.pa_n = atoi(token+1);
+ s->m.video.pa_d = atoi(pos+1);
+ *pos=':';
+ }else{
+ s->m.video.pa_n = atoi(token+1);
+ s->m.video.pa_d = 1;
+ }
+ }
+ break;
+ case 'C':
+ for(i=0;chromaformat[i];i++)
+ if(!strcasecmp(chromaformat[i],token+1))break;
+ if(!chromaformat[i]){
+ yerror("unknown y4o video chroma format\n");
+ goto err;
+ }
+ s->m.video.format=i;
+ break;
+ default:
+ yerror("unknown y4o video stream tag\n");
+ goto err;
+ }
+ }
+ }
+
+ if(s->m.video.fps_n>0 &&
+ s->m.video.fps_d>0 &&
+ s->m.video.pa_n>0 &&
+ s->m.video.pa_d>0 &&
+ s->m.video.format>=0 &&
+ s->m.video.w>0 &&
+ s->m.video.h>0 &&
+ s->m.video.i>=0)
+ s->type=STREAM_VIDEO;
+ else{
+ yerror("missing flags in y4o video stream header\n");
+ goto err;
+ }
+
+ {
+ int dw = s->m.video.w*s->m.video.pa_n;
+ int dh = s->m.video.h*s->m.video.pa_d;
+ int d;
+ for(d=1;d<10000;d++)
+ if(fabs(rint(dw/(double)dh*d) - dw/(double)dh*d)<EPSILON)
+ break;
+ s->m.video.frame_n=rint(dw/(double)dh*d);
+ s->m.video.frame_d=d;
+ }
+
+ if(force_fps.num>0){
+ s->m.video.fps_n = force_fps.num;
+ s->m.video.fps_d = force_fps.den;
+ }
+
+ s->tickduration = (double)s->m.video.fps_d/s->m.video.fps_n;
+ s->bytes_per_tick = s->m.video.w*s->m.video.h*chromabits[s->m.video.format]/8;
+ s->tolerance = (double)s->m.video.fps_d/(double)s->m.video.fps_n;
+
+ return s;
+
+ }
+
+ if(af){
+ /* audio stream! */
+ /* proceed to get the tags... (overwrite the magic) */
+ for (n = 0, p = line; n < 80; n++, p++) {
+ if ((ret=fread(p, 1, 1, f))<1){
+ if(feof(f))
+ yerror("EOF reading y4o audio stream header\n");
+ goto err;
+ }
+ if (*p == '\n') {
+ *p = '\0';
+ break;
+ }
+ }
+ if (n >= 80) {
+ yerror("line too long reading y4o audio stream header\n");
+ goto err;
+ }
+
+ /* read tags */
+ /* R%d = rate
+ C%d = channels
+ all interchange audio is 24 bit signed LE
+ */
+
+ {
+ char *token, *value;
+ char tag;
+
+ /* parse fields */
+ for (token = strtok(line, " ");
+ token != NULL;
+ token = strtok(NULL, " ")) {
+ if (token[0] == '\0') continue; /* skip empty strings */
+ tag = token[0];
+ value = token + 1;
+ switch (tag) {
+ case 'R':
+ s->m.audio.rate = atoi(token+1);
+ break;
+ case 'C':
+ s->m.audio.ch = atoi(token+1);
+ break;
+ default:
+ yerror("unknown y4o audio stream tag\n");
+ goto err;
+ }
+ }
+ }
+
+ if(s->m.audio.rate>0 &&
+ s->m.audio.ch>0)
+ s->type=STREAM_AUDIO;
+ else{
+ yerror("missing flags in y4o stream header\n");
+ goto err;
+ }
+
+ s->tickduration = (double)1./s->m.audio.rate;
+ s->bytes_per_tick = 3*s->m.audio.ch;
+ s->tolerance = .01;
+
+ return s;
+ }
+
+ err:
+ s->type = STREAM_INVALID;
+ return s;
+}
+
+y4o_in_t *y4o_open_in(FILE *f){
+ // id the file before anything else
+ int i;
+ y4o_in_t *y;
+ stream_t *ll=NULL;
+
+ if(read_container_header(f,&i))
+ return NULL;
+
+ y=calloc(1,sizeof(*y));
+ y->synced=i;
+
+ // seekable?
+ if(!fseeko(f,0,SEEK_CUR))
+ y->seekable=1;
+
+ // read stream headers
+ while(1){
+ stream_t *s=read_stream_header(f);
+ if(!s)
+ break;
+ if(s->type == STREAM_INVALID)
+ yerror("Stream #%d unreadable; trying to continue\n",y->num_streams);
+ s->stream_num=y->num_streams++;
+ s->prev=ll;
+ s->y=y;
+ s->bos=1;
+
+ if(!y->seekable){
+ s->inswap.f[0]=tmpfile();
+ s->inswap.f[1]=tmpfile();
+ }
+
+ if(verbose)
+ switch(s->type){
+ case STREAM_VIDEO:
+ yinfo("Stream #%d, VIDEO %dx%d %d:%d %s %.3ffps %s\n",
+ s->stream_num, s->m.video.w,s->m.video.h, s->m.video.frame_n,s->m.video.frame_d,
+ chromaformat_long[s->m.video.format],s->m.video.fps_n/(double)s->m.video.fps_d,
+ s->m.video.i?"interlaced":"progressive");
+ if(s->m.video.i && force_non_interlaced)
+ yinfo("FORCING NON-INTERLACED\n");
+ break;
+ case STREAM_AUDIO:
+ yinfo("Stream #%d, AUDIO %dHz, %d channel(s) [24bit]\n",
+ s->stream_num, s->m.audio.rate,s->m.audio.ch);
+ default:
+ break;
+ }
+
+ if(s->type==STREAM_VIDEO && force_non_interlaced) s->m.video.i=PROGRESSIVE;
+ if(ll)ll->next=s;
+ ll=s;
+ }
+
+ // finish setting up stream list
+ y->streams = calloc(y->num_streams,sizeof(*y->streams));
+ for(i=y->num_streams;i>0;i--){
+ y->streams[i-1]=ll;
+ ll=ll->prev;
+ }
+ y->f = f;
+
+ return y;
+}
+
+void y4o_close_in(y4o_in_t *y){
+ int i;
+ if(y){
+ if(y->streams){
+ for(i=0;i<y->num_streams;i++){
+ stream_t *s=y->streams[i];
+ /* free frame queue */
+ while(s->inq_tail){
+ frame_t *next=s->inq_tail->next;
+ if(s->inq_tail->data)
+ free(s->inq_tail->data);
+ free(s->inq_tail);
+ s->inq_tail=next;
+ }
+ /* free swapfiles */
+ if(s->inswap.f[0])
+ fclose(s->inswap.f[0]);
+ if(s->inswap.f[1])
+ fclose(s->inswap.f[1]);
+ free(s);
+ }
+ free(y->streams);
+ }
+ fclose(y->f);
+ free(y);
+ }
+}
+
+/* Reads one frame, potentially splits it internally into multiple
+ frames; returns the first of the split frames */
+frame_t *y4o_read_frame(y4o_in_t *y){
+ FILE *f=y->f;
+ int streamno;
+ int length;
+ double pts;
+ char line[80];
+ char *p;
+ int n;
+ size_t ret;
+ frame_t *pret=NULL;
+
+ ret = fread(line, 1, sizeof("FRAME"),f);
+ if (ret<sizeof("FRAME"))
+ {
+ /* A clean EOF should end exactly at a frame-boundary */
+ if( ret != 0 && feof(f) )
+ yerror("EOF reading y4o frame\n");
+ y->eof=1;
+ return NULL;
+ }
+
+ if (strncmp(line, "FRAME ", sizeof("FRAME ")-1)){
+ yerror("loss of y4o framing\n");
+ return NULL;
+ }
+
+ /* proceed to get the tags... (overwrite the magic) */
+ for (n = 0, p = line; n < 80; n++, p++) {
+ if ((ret=fread(p, 1, 1, f))<1){
+ if(feof(f))
+ yerror("EOF reading y4o frame\n");
+ return NULL;
+ }
+ if (*p == '\n') {
+ *p = '\0'; /* Replace linefeed by end of string */
+ break;
+ }
+ }
+ if (n >= 80) {
+ yerror("line too long reading y4o frame header\n");
+ return NULL;
+ }
+
+ /* read tags */
+ /* S%d = streamno
+ L%d = length
+ P%g = pts */
+
+ {
+ char *token, *value;
+ char tag;
+
+ streamno=-1;
+ length=-1;
+ pts=-1;
+
+ /* parse fields */
+ for (token = strtok(line, " ");
+ token != NULL;
+ token = strtok(NULL, " ")) {
+ if (token[0] == '\0') continue; /* skip empty strings */
+ tag = token[0];
+ value = token + 1;
+ switch (tag) {
+ case 'S':
+ streamno = atoi(token+1);
+ break;
+ case 'L':
+ length = atoi(token+1);
+ break;
+ case 'P':
+ pts = atof(token+1);
+ break;
+ default:
+ yerror("unknown y4o frame tag\n");
+ return NULL;
+ }
+ }
+ }
+
+ if(streamno>=y->num_streams){
+ yerror("error reading frame; streamno out of range\n");
+ return NULL;
+ }
+
+ if(streamno==-1 || length==-1 || pts==-1){
+ yerror("missing y4o frame tags; frame unreadable\n");
+ return NULL;
+ }
+
+ /* read frame */
+ {
+ /* if this is huge audio frame, break it into smaller frames. It
+ will ease buffering in the encoder. */
+ stream_t *s = y->streams[streamno];
+ int total_ticks=length/s->bytes_per_tick;
+ int max_ticks=4096;
+ int ticks_sofar;
+ for(ticks_sofar=0;ticks_sofar<total_ticks;ticks_sofar+=max_ticks){
+ int ticks = ticks_sofar+max_ticks>total_ticks ? total_ticks-ticks_sofar : max_ticks;
+ int bytes = ticks*s->bytes_per_tick;
+ double thispts = pts+ticks_sofar*s->tickduration;
+
+ frame_t *p=calloc(1,sizeof(*p));
+
+ if(!p){
+ yerror("unable to allocate memory for frame\n");
+ return pret;
+ }
+ p->pts=thispts;
+ p->len=bytes;
+ p->s=s;
+
+ if(s->inq_tail && y->seekable){
+ /* there's already queued data and this stream is seekable. Save positioning
+ and seek past */
+ p->swap = f;
+ p->swap_pos = ftello(f);
+ if(fseeko(f,bytes,SEEK_CUR)){
+ yerror("unable to advance in frame data; %s\n",strerror(errno));
+ return pret;
+ }
+ }else{
+ unsigned char *data=malloc(bytes);
+ if(!data){
+ yerror("unable to allocate memory for frame\n");
+ free(p);
+ return pret;
+ }
+
+ ret=fread(data,1,bytes,f);
+ if(ret<(unsigned)bytes){
+ if(feof(f)){
+ yerror("unable to read frame; EOF\n");
+ }else{
+ yerror("unable to read frame; %s\n",strerror(errno));
+ }
+ free(p);
+ free(data);
+ return pret;
+ }
+
+ /* If there's already queued data, this read goes straight to
+ bufferswap */
+ if(s->inq_tail){
+ p->swap=s->inswap.f[s->inswap.write];
+ p->swap_pos=s->inswap.head[s->inswap.write];
+ s->inswap.head[s->inswap.write]+=bytes;
+ if(fwrite(data,1,bytes,p->swap)<(unsigned)bytes){
+ yerror("unable to write to swap; %s\n",strerror(errno));
+ free(p);
+ free(data);
+ return pret;
+ }
+ free(data);
+ }else{
+ p->data=data;
+ }
+ }
+
+ p->streamno = streamno;
+ p->ticks = ticks;
+ p->duration = ticks * s->tickduration;
+
+ p->prev=s->inq_head;
+ if(s->inq_head){
+ s->inq_head->next=p;
+ }else{
+ s->inq_tail=p;
+ }
+ s->inq_head=p;
+ s->tick_depth+=p->ticks;
+
+ p->f_prev=y->f_head;
+ if(y->f_head){
+ y->f_head->f_next=p;
+ }else{
+ y->f_tail=p;
+ }
+ y->f_head=p;
+
+ if(!pret) pret=p;
+ }
+
+ return pret;
+ }
+}
+
+static void release_frame_swap(frame_t *p){
+ if(p->swap){
+ int from = (p->swap==p->s->inswap.f[0]?0:(p->swap==p->s->inswap.f[1]?1:-1));
+ /* 'swap' might have been the seekable input file. */
+ if(from>=0){
+ p->s->inswap.tail[from]=p->swap_pos+p->len;
+
+ /* if this is currently the write queue, swap banks */
+ if(p->s->inswap.write==from){
+ int b=!from;
+ p->s->inswap.write=b;
+ p->s->inswap.tail[b]=p->s->inswap.head[b]=0;
+ if(fseeko(p->s->inswap.f[b],0,SEEK_SET)){
+ yerror("unable to seek in swap file; %s\n",strerror(errno));
+ exit(1);
+ }
+ }
+ }
+ }
+
+ p->swap=NULL;
+ p->swap_pos=0;
+}
+
+int y4o_lock_frame(frame_t *p){
+ if(p->swap && p->s && !p->data){
+ off_t savepos;
+ y4o_in_t *y = p->s->y;
+ /* fetch data from swap / seekable stream */
+ size_t sl=p->len;
+ unsigned char *d=malloc(sl);
+ if(!d){
+ yerror("unable to allocate memory for frame\n");
+ return 1;
+ }
+
+ if(p->swap == y->f)
+ savepos=ftello(y->f);
+
+ if(fseeko(p->swap,p->swap_pos,SEEK_SET)){
+ yerror("unable to seek in swap file; %s\n",strerror(errno));
+ free(d);
+ return 1;
+ }
+ if(fread(d,1,sl,p->swap)<sl){
+ if(d)free(d);
+ if(feof(p->swap)){
+ yerror("unable to read frame from swap; EOF\n");
+ }else{
+ yerror("unable to read frame from swap; %s\n",strerror(errno));
+ }
+ free(d);
+ return 1;
+ }
+
+ if(p->swap == y->f)
+ if(fseeko(p->swap,savepos,SEEK_SET)){
+ yerror("unable to seek in input file; %s\n",strerror(errno));
+ exit(1);
+ }
+
+ p->data=d;
+ }
+ release_frame_swap(p);
+ return 0;
+}
+
+void y4o_pull_frame(frame_t *p){
+ /* pull out of the stream queue */
+ stream_t *s=p->s;
+ y4o_lock_frame(p);
+ if(s){
+ y4o_in_t *y=s->y;
+ if(p->prev){
+ p->prev->next=p->next;
+ }else{
+ s->inq_tail=p->next;
+ }
+ if(p->next){
+ p->next->prev=p->prev;
+ }else{
+ s->inq_head=p->prev;
+ }
+ s->tick_depth-=p->ticks;
+
+ /* pull out of the file queue */
+ if(p->f_prev){
+ p->f_prev->f_next=p->f_next;
+ }else{
+ y->f_tail=p->f_next;
+ }
+ if(p->f_next){
+ p->f_next->f_prev=p->f_prev;
+ }else{
+ y->f_head=p->f_prev;
+ }
+
+ p->next=NULL;
+ p->prev=NULL;
+ p->f_next=NULL;
+ p->f_prev=NULL;
+ p->s=NULL;
+ }
+}
+
+void y4o_free_frame(frame_t *p){
+ release_frame_swap(p);
+ y4o_pull_frame(p);
+ if(p->data)free(p->data);
+ memset(p,0,sizeof(*p));
+ free(p);
+}
+
Deleted: trunk/y4oi/y4oi.c
===================================================================
--- trunk/y4oi/y4oi.c 2010-02-22 03:47:07 UTC (rev 16933)
+++ trunk/y4oi/y4oi.c 2010-02-24 07:40:16 UTC (rev 16934)
@@ -1,2129 +0,0 @@
-/*
- *
- * y4oi:
- * A utility for doing several simple but essential operations
- * on yuv4ogg interchange streams.
- *
- * y4oi copyright (C) 2009 Monty <monty at xiph.org>
- *
- * y4oi 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, or (at your option)
- * any later version.
- *
- * y4oi 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 Postfish; see the file COPYING. If not, write to the
- * Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
- *
- *
- */
-
-#define _FILE_OFFSET_BITS 64
-#define EPSILON 1e-6
-
-typedef struct {
- double begin;
- double end;
-} interval;
-
-typedef struct {
- double num;
- double den;
-} ratio;
-
-static int verbose=0;
-static double fill_secs=25.;
-static double sync_secs=1.;
-static ratio force_fps={0,0};
-static int force_sync=0;
-static int force_no_sync=0;
-static int no_rewrite=0;
-static int global_ended=0;
-
-#include <unistd.h>
-#include <string.h>
-#include <stdlib.h>
-#include <stdio.h>
-#include <errno.h>
-#include <math.h>
-#include <getopt.h>
-#include <limits.h>
-
-typedef enum {
- Cmono=0,
- C411ntscdv=1,
- C420jpeg=2,
- C420mpeg2=3,
- C420paldv=4,
- C420unknown=5,
- C422jpeg=6,
- C422smpte=7,
- C422unknown=8,
- C444=9,
-} chromafmt;
-
-static char *chromaformat[]={
- "mono", //0
- "411ntscdv", //1
- "420jpeg", //2 chroma sample is centered vertically and horizontally between luma samples
- "420mpeg2", //3 chroma sample is centered vertically between lines, cosited horizontally */
- "420paldv", //4 chroma sample is cosited vertically and horizontally */
- "420unknown",//5
- "422jpeg", //6 chroma sample is horizontally centered between luma samples */
- "422smpte", //7 chroma sample is cosited horizontally */
- "422unknown",//8
- "444", //9
- NULL
-};
-
-static unsigned char chromabits[]={
- 8,
- 12,
- 12,
- 12,
- 12,
- 12,
- 16,
- 16,
- 16,
- 24
-};
-
-static char *chromaformat_long[]={
- "monochrome", //0
- "4:1:1 [ntscdv chroma]", //1
- "4:2:0 [jpeg chroma]", //2 chroma sample is centered vertically and horizontally between luma samples
- "4:2:0 [mpeg2 chroma]", //3 chroma sample is centered vertically between lines, cosited horizontally */
- "4:2:0 [paldv chroma]", //4 chroma sample is cosited vertically and horizontally */
- "4:2:0 [unknown chroma]",//5
- "4:2:2 [jpeg chroma]", //6 chroma sample is horizontally centered between luma samples */
- "4:2:2 [smpte chroma]", //7 chroma sample is cosited horizontally */
- "4:2:2 [unknown chroma]",//8
- "4:4:4", //9
- NULL
-};
-
-typedef enum {
- STREAM_INVALID=0,
- STREAM_VIDEO=1,
- STREAM_AUDIO=2
-} stream_type;
-
-typedef enum {
- INVALID=-1,
- PROGRESSIVE=0,
- TOP_FIRST=1,
- BOTTOM_FIRST=2
-} interlace_type;
-
-typedef struct frame_s frame_t;
-struct frame_s {
- double pts;
- size_t len;
- int streamno;
- int presync;
- int ticks;
- double duration;
-
- unsigned char *data;
- FILE *swap;
- off_t swap_pos;
-
- frame_t *next;
- frame_t *prev;
-
- frame_t *f_next;
- frame_t *f_prev;
-};
-
-typedef struct {
- FILE *f[2];
- off_t head[2];
- off_t tail[2];
- int write;
-} swap_t;
-
-typedef struct stream_s stream_t;
-struct stream_s{
- stream_type type;
- int stream_num;
- double tickduration;
- long bytes_per_tick;
- double tolerance;
-
- union stream_t {
- struct {
- int rate;
- int ch;
- } audio;
- struct {
- int fps_n;
- int fps_d;
- int pa_n;
- int pa_d;
- int frame_n;
- int frame_d;
- int format;
- int w;
- int h;
- interlace_type i;
- } video;
- }m;
-
- frame_t *inq_head;
- frame_t *inq_tail;
- swap_t inswap;
- long tick_depth;
-
- stream_t *next;
- stream_t *prev;
-};
-
-typedef struct {
- FILE *f;
- int eof;
- stream_t **streams;
- int num_streams;
- int synced;
- int seekable;
- frame_t *f_head;
- frame_t *f_tail;
-} y4o_in_t;
-
-static int y4o_read_container_header(FILE *f, int *sync){
- char line[80];
- char *p;
- int n;
- size_t ret,len;
-
- len=sizeof("YUV4OGG");
- ret = fread(line, 1, len,f);
- if (ret<len){
- if(feof(f))
- fprintf(stderr, "ERROR: EOF reading y4o file header\n");
- return 1;
- }
-
- if (strncmp(line, "YUV4OGG ", sizeof("YUV4OGG ")-1)){
- fprintf(stderr, "ERROR: cannot parse y4o file header\n");
- return 1;
- }
-
- /* proceed to get the tags... (overwrite the magic) */
- for (n = 0, p = line; n < 80; n++, p++) {
- if ((ret=fread(p,1,1,f))<1){
- if(feof(f))
- fprintf(stderr,"ERROR: EOF reading y4o file header\n");
- return 1;
- }
- if (*p == '\n') {
- *p = '\0';
- break;
- }
- }
- if (n >= 80) {
- fprintf(stderr,"ERROR: line too long reading y4o file header\n");
- return 1;
- }
-
- /* read tags */
- /* S%c = sync */
-
- {
- char *token, *value;
- char tag;
-
- *sync=-1;
-
- /* parse fields */
- for (token = strtok(line, " ");
- token != NULL;
- token = strtok(NULL, " ")) {
- if (token[0] == '\0') continue; /* skip empty strings */
- tag = token[0];
- value = token + 1;
- switch (tag) {
- case 'S':
- switch(token[1]){
- case 'y':
- case 'Y':
- *sync = 1;
- break;
- case 'n':
- case 'N':
- *sync=0;
- break;
- default:
- fprintf(stderr,"ERROR: unknown sync tag setting in y4o header\n");
- return 1;
- }
- break;
- default:
- fprintf(stderr,"ERROR: unknown file tag in y4o header\n");
- return 1;
- }
- }
- }
-
- if(*sync==-1)return 1;
-
- return 0;
-}
-
-static stream_t *y4o_read_stream_header(FILE *f){
- char line[80];
- char *p;
- int n;
- size_t ret;
- int af=0,vf=0;
- stream_t *s;
-
- /* Check for past the stream headers and at first frame */
- line[0]=getc(f);
- ungetc(line[0],f);
- if(line[0]=='F'){
- return NULL;
- }
-
- s=calloc(1,sizeof(*s));
-
- ret = fread(line, 1, sizeof("AUDIO"), f);
- if (ret<sizeof("AUDIO")){
- if(feof(f))
- fprintf(stderr,"ERROR: EOF reading y4o stream header\n");
- goto err;
- }
-
- if (!strncmp(line, "VIDEO ", sizeof("VIDEO ")-1)) vf=1;
- else if (!strncmp(line, "AUDIO ", sizeof("AUDIO ")-1)) af=1;
-
- if(!(af||vf)){
- fprintf(stderr,"ERROR: unknown y4o stream header type\n");
- goto err;
- }
-
- if(vf){
- /* video stream! */
- /* proceed to get the tags... (overwrite the magic) */
- for (n = 0, p = line; n < 80; n++, p++) {
- if ((ret=fread(p, 1, 1, f))<1){
- if(feof(f))
- fprintf(stderr,"ERROR: EOF reading y4o video stream header\n");
- goto err;
- }
- if (*p == '\n') {
- *p = '\0';
- break;
- }
- }
- if (n >= 80) {
- fprintf(stderr,"ERROR: line too long reading y4o video stream header\n");
- goto err;
- }
-
- /* read tags */
- /* W%d = width
- H%d = height
- F%d:%d = fps
- I%c = interlace
- A%d:%d = pixel aspect
- C%s = chroma format
- */
-
- {
- char *token, *value;
- char tag;
- int i;
- s->m.video.format=-1;
- s->m.video.i=-1;
-
- /* parse fields */
- for (token = strtok(line, " ");
- token != NULL;
- token = strtok(NULL, " ")) {
- if (token[0] == '\0') continue; /* skip empty strings */
- tag = token[0];
- value = token + 1;
- switch (tag) {
- case 'W':
- s->m.video.w = atoi(token+1);
- break;
- case 'H':
- s->m.video.h = atoi(token+1);
- break;
- case 'F':
- {
- char *pos=strchr(token+1,':');
- if(pos){
- *pos='\0';
- s->m.video.fps_n = atoi(token+1);
- s->m.video.fps_d = atoi(pos+1);
- *pos=':';
- }else{
- s->m.video.fps_n = atoi(token+1);
- s->m.video.fps_d = 1;
- }
- }
- break;
- case 'I':
- switch(token[1]){
- case 'p':
- case 'P':
- s->m.video.i=PROGRESSIVE;
- break;
- case 't':
- case 'T':
- s->m.video.i=TOP_FIRST;
- break;
- case 'b':
- case 'B':
- s->m.video.i=BOTTOM_FIRST;
- break;
- default:
- fprintf(stderr,"ERROR: unknown y4o video interlace setting\n");
- goto err;
- }
- case 'A':
- {
- char *pos=strchr(token+1,':');
- if(pos){
- *pos='\0';
- s->m.video.pa_n = atoi(token+1);
- s->m.video.pa_d = atoi(pos+1);
- *pos=':';
- }else{
- s->m.video.pa_n = atoi(token+1);
- s->m.video.pa_d = 1;
- }
- }
- break;
- case 'C':
- for(i=0;chromaformat[i];i++)
- if(!strcasecmp(chromaformat[i],token+1))break;
- if(!chromaformat[i]){
- fprintf(stderr,"ERROR: unknown y4o video chroma format\n");
- goto err;
- }
- s->m.video.format=i;
- break;
- default:
- fprintf(stderr,"ERROR: unknown y4o video stream tag\n");
- goto err;
- }
- }
- }
-
- if(s->m.video.fps_n>0 &&
- s->m.video.fps_d>0 &&
- s->m.video.pa_n>0 &&
- s->m.video.pa_d>0 &&
- s->m.video.format>=0 &&
- s->m.video.w>0 &&
- s->m.video.h>0 &&
- s->m.video.i>=0)
- s->type=STREAM_VIDEO;
- else{
- fprintf(stderr,"ERROR: missing flags in y4o video stream header\n");
- goto err;
- }
-
- {
- int dw = s->m.video.w*s->m.video.pa_n;
- int dh = s->m.video.h*s->m.video.pa_d;
- int d;
- for(d=1;d<10000;d++)
- if(fabs(rint(dw/(double)dh*d) - dw/(double)dh*d)<EPSILON)
- break;
- s->m.video.frame_n=rint(dw/(double)dh*d);
- s->m.video.frame_d=d;
- }
-
- if(force_fps.num>0){
- s->m.video.fps_n = force_fps.num;
- s->m.video.fps_d = force_fps.den;
- }
-
- s->tickduration = (double)s->m.video.fps_d/s->m.video.fps_n;
- s->bytes_per_tick = s->m.video.w*s->m.video.h*chromabits[s->m.video.format]/8;
- s->tolerance = (double)s->m.video.fps_d/(double)s->m.video.fps_n;
-
- return s;
-
- }
-
- if(af){
- /* audio stream! */
- /* proceed to get the tags... (overwrite the magic) */
- for (n = 0, p = line; n < 80; n++, p++) {
- if ((ret=fread(p, 1, 1, f))<1){
- if(feof(f))
- fprintf(stderr,"ERROR: EOF reading y4o audio stream header\n");
- goto err;
- }
- if (*p == '\n') {
- *p = '\0';
- break;
- }
- }
- if (n >= 80) {
- fprintf(stderr,"ERROR: line too long reading y4o audio stream header\n");
- goto err;
- }
-
- /* read tags */
- /* R%d = rate
- C%d = channels
- all interchange audio is 24 bit signed LE
- */
-
- {
- char *token, *value;
- char tag;
-
- /* parse fields */
- for (token = strtok(line, " ");
- token != NULL;
- token = strtok(NULL, " ")) {
- if (token[0] == '\0') continue; /* skip empty strings */
- tag = token[0];
- value = token + 1;
- switch (tag) {
- case 'R':
- s->m.audio.rate = atoi(token+1);
- break;
- case 'C':
- s->m.audio.ch = atoi(token+1);
- break;
- default:
- fprintf(stderr,"ERROR: unknown y4o audio stream tag\n");
- goto err;
- }
- }
- }
-
- if(s->m.audio.rate>0 &&
- s->m.audio.ch>0)
- s->type=STREAM_AUDIO;
- else{
- fprintf(stderr,"ERROR: missing flags in y4o stream header\n");
- goto err;
- }
-
- s->tickduration = (double)1./s->m.audio.rate;
- s->bytes_per_tick = 3*s->m.audio.ch;
- s->tolerance = .01;
-
- return s;
- }
-
- err:
- s->type = STREAM_INVALID;
- return s;
-}
-
-static y4o_in_t *y4o_open_in(FILE *f){
- // id the file before anything else
- int i;
- y4o_in_t *y;
- stream_t *ll=NULL;
-
- if(y4o_read_container_header(f,&i))
- return NULL;
-
- y=calloc(1,sizeof(*y));
- y->synced=i;
-
- // seekable?
- if(!fseeko(f,0,SEEK_CUR))
- y->seekable=1;
-
- // read stream headers
- while(1){
- stream_t *s=y4o_read_stream_header(f);
- if(!s)
- break;
- if(s->type == STREAM_INVALID)
- fprintf(stderr,"ERROR: Stream #%d unreadable; trying to continue\n",y->num_streams);
- s->stream_num=y->num_streams++;
- s->prev=ll;
-
- if(!y->seekable){
- s->inswap.f[0]=tmpfile();
- s->inswap.f[1]=tmpfile();
- }
-
- if(verbose)
- switch(s->type){
- case STREAM_VIDEO:
- fprintf(stderr,"Stream #%d, VIDEO %dx%d %d:%d %s %.3ffps %s\n",
- s->stream_num, s->m.video.w,s->m.video.h, s->m.video.frame_n,s->m.video.frame_d,
- chromaformat_long[s->m.video.format],s->m.video.fps_n/(double)s->m.video.fps_d,
- s->m.video.i?"interlaced":"progressive");
- break;
- case STREAM_AUDIO:
- fprintf(stderr,"Stream #%d, AUDIO %dHz, %d channel(s) [24bit]\n",
- s->stream_num, s->m.audio.rate,s->m.audio.ch);
- default:
- break;
- }
-
- if(ll)ll->next=s;
- ll=s;
- }
-
- // finish setting up stream list
- y->streams = calloc(y->num_streams,sizeof(*y->streams));
- for(i=y->num_streams;i>0;i--){
- y->streams[i-1]=ll;
- ll=ll->prev;
- }
- y->f = f;
-
- return y;
-}
-
-static void y4o_close_in(y4o_in_t *y){
- int i;
- if(y){
- if(y->streams){
- for(i=0;i<y->num_streams;i++){
- stream_t *s=y->streams[i];
- /* free frame queue */
- while(s->inq_tail){
- frame_t *next=s->inq_tail->next;
- if(s->inq_tail->data)
- free(s->inq_tail->data);
- free(s->inq_tail);
- s->inq_tail=next;
- }
- /* free swapfiles */
- if(s->inswap.f[0])
- fclose(s->inswap.f[0]);
- if(s->inswap.f[1])
- fclose(s->inswap.f[1]);
- free(s);
- }
- free(y->streams);
- }
- fclose(y->f);
- free(y);
- }
-}
-
-/* Reads one frame, potentially splits it internally into multiple
- frames; returns the first of the split frames */
-static frame_t *y4o_read_frame(y4o_in_t *y){
- FILE *f=y->f;
- int streamno;
- int length;
- double pts;
- char line[80];
- char *p;
- int n;
- size_t ret;
- frame_t *pret=NULL;
-
- ret = fread(line, 1, sizeof("FRAME"),f);
- if (ret<sizeof("FRAME"))
- {
- /* A clean EOF should end exactly at a frame-boundary */
- if( ret != 0 && feof(f) )
- fprintf(stderr,"ERROR: EOF reading y4o frame\n");
- y->eof=1;
- return NULL;
- }
-
- if (strncmp(line, "FRAME ", sizeof("FRAME ")-1)){
- fprintf(stderr,"ERROR: loss of y4o framing\n");
- return NULL;
- }
-
- /* proceed to get the tags... (overwrite the magic) */
- for (n = 0, p = line; n < 80; n++, p++) {
- if ((ret=fread(p, 1, 1, f))<1){
- if(feof(f))
- fprintf(stderr,"ERROR: EOF reading y4o frame\n");
- return NULL;
- }
- if (*p == '\n') {
- *p = '\0'; /* Replace linefeed by end of string */
- break;
- }
- }
- if (n >= 80) {
- fprintf(stderr,"ERROR: line too long reading y4o frame header\n");
- return NULL;
- }
-
- /* read tags */
- /* S%d = streamno
- L%d = length
- P%g = pts */
-
- {
- char *token, *value;
- char tag;
-
- streamno=-1;
- length=-1;
- pts=-1;
-
- /* parse fields */
- for (token = strtok(line, " ");
- token != NULL;
- token = strtok(NULL, " ")) {
- if (token[0] == '\0') continue; /* skip empty strings */
- tag = token[0];
- value = token + 1;
- switch (tag) {
- case 'S':
- streamno = atoi(token+1);
- break;
- case 'L':
- length = atoi(token+1);
- break;
- case 'P':
- pts = atof(token+1);
- break;
- default:
- fprintf(stderr,"ERROR: unknown y4o frame tag\n");
- return NULL;
- }
- }
- }
-
- if(streamno>=y->num_streams){
- fprintf(stderr,"ERROR: error reading frame; streamno out of range\n");
- return NULL;
- }
-
- if(streamno==-1 || length==-1 || pts==-1){
- fprintf(stderr,"ERROR: missing y4o frame tags; frame unreadable\n");
- return NULL;
- }
-
- /* read frame */
- {
- /* if this is huge audio frame, break it into smaller frames. It
- will ease buffering in the encoder. */
- stream_t *s = y->streams[streamno];
- int total_ticks=length/s->bytes_per_tick;
- int max_ticks=4096;
- int ticks_sofar;
- for(ticks_sofar=0;ticks_sofar<total_ticks;ticks_sofar+=max_ticks){
- int ticks = ticks_sofar+max_ticks>total_ticks ? total_ticks-ticks_sofar : max_ticks;
- int bytes = ticks*s->bytes_per_tick;
- double thispts = pts+ticks_sofar*s->tickduration;
-
- frame_t *p=calloc(1,sizeof(*p));
-
- if(!p){
- fprintf(stderr,"ERROR: unable to allocate memory for frame\n");
- return pret;
- }
- p->pts=thispts;
- p->len=bytes;
-
- if(s->inq_tail && y->seekable){
- /* there's already queued data and this stream is seekable. Save positioning
- and seek past */
- p->swap = f;
- p->swap_pos = ftello(f);
- if(fseeko(f,bytes,SEEK_CUR)){
- fprintf(stderr,"ERROR: unable to advance in frame data; %s\n",strerror(errno));
- return pret;
- }
- }else{
- unsigned char *data=malloc(bytes);
- if(!data){
- fprintf(stderr,"ERROR: unable to allocate memory for frame\n");
- free(p);
- return pret;
- }
-
- ret=fread(data,1,bytes,f);
- if(ret<bytes){
- if(feof(f)){
- fprintf(stderr,"ERROR: unable to read frame; EOF\n");
- }else{
- fprintf(stderr,"ERROR: unable to read frame; %s\n",strerror(errno));
- }
- free(p);
- free(data);
- return pret;
- }
-
- /* If there's already queued data, this read goes straight to
- bufferswap */
- if(s->inq_tail){
- p->swap=s->inswap.f[s->inswap.write];
- p->swap_pos=s->inswap.head[s->inswap.write];
- s->inswap.head[s->inswap.write]+=bytes;
- if(fwrite(data,1,bytes,p->swap)<bytes){
- fprintf(stderr,"ERROR: unable to write to swap; %s\n",strerror(errno));
- free(p);
- free(data);
- return pret;
- }
- free(data);
- }else{
- p->data=data;
- }
- }
-
- p->streamno = streamno;
- p->ticks = ticks;
- p->duration = ticks * s->tickduration;
-
- p->prev=s->inq_head;
- if(s->inq_head){
- s->inq_head->next=p;
- }else{
- s->inq_tail=p;
- }
- s->inq_head=p;
- s->tick_depth+=p->ticks;
-
- p->f_prev=y->f_head;
- if(y->f_head){
- y->f_head->f_next=p;
- }else{
- y->f_tail=p;
- }
- y->f_head=p;
-
- if(!pret) pret=p;
- }
-
- return pret;
- }
-}
-
-static void y4o_free_frame(frame_t *p){
- if(p->data)free(p->data);
- memset(p,0,sizeof(*p));
- free(p);
-}
-
-// depth-limited fill.
-// bounded attempt to prime any given queue at the point any other queue reaches fill_secs deep
-// actual depth-of-queue is used, not PTS (as PTS is unreliable and could be way off ino the weeds)
-
-static int limited_prime(y4o_in_t *y, int sno){
- int i;
- double clock[y->num_streams];
- stream_t *s=y->streams[sno];
- if(s->inq_tail) return 0;
-
- for(i=0;i<y->num_streams;i++){
- stream_t *si=y->streams[i];
- clock[i] = si->tick_depth*si->tickduration;
- }
-
- while(!s->inq_tail){
- frame_t *p=y4o_read_frame(y);
- if(!p) return 1;
- i=p->streamno;
- while(p){
- clock[i]+=p->duration;
- p=p->next;
- }
- if(clock[i]>fill_secs){
- fprintf(stderr,"ERROR: Buffer depth exceeded configured limit due to A/V skew;\n"
- " aborting input stream.\n");
- return 1;
- }
- }
- return 0;
-}
-
-// bounded sync search
-// fill sync/search streams a min of search_secs deep past prime point each, filling no queue more than fill_secs past prime point
-// depth-of-queue is used, not PTS (as PTS is unreliable and could be way off ino the weeds)
-
-static int limited_prime_sync(y4o_in_t *y, int sync, int sno){
- int i;
- double clock[y->num_streams];
- stream_t *sy=y->streams[sync];
- stream_t *sn=y->streams[sno];
- frame_t *p=NULL;
-
- if(limited_prime(y,sync)) return 1;
- if(limited_prime(y,sno)) return 1;
-
- /* if both are already deep enough, done */
- if(sy->tick_depth*sy->tickduration >= sync_secs &&
- sn->tick_depth*sn->tickduration >= sync_secs)
- return 0;
-
- /* Count/fill from sync point */
- memset(clock,0,sizeof(clock));
- p=sy->inq_tail;
- while(p){
- i=p->streamno;
- if(p==sn->inq_tail){
- for(i=0;i<y->num_streams;i++){
- clock[i] = 0;
- }
- }
- clock[i]+=p->duration;
- p=p->f_next;
- }
-
- while(clock[sync] < sync_secs ||
- clock[sno] < sync_secs){
- p=y4o_read_frame(y);
- if(!p) return 1;
- i=p->streamno;
- while(p){
- clock[i]+=p->duration;
- p=p->next;
- }
- if(clock[i]>fill_secs)return 1;
- }
-
- return 0;
-}
-
-/* release swap in use if any */
-static void release_frame_swap(stream_t *s, frame_t *p){
- if(p->swap){
- int from = (p->swap==s->inswap.f[0]?0:(p->swap==s->inswap.f[1]?1:-1));
- /* 'swap' might have been the seekable input file. */
- if(from>=0){
- s->inswap.tail[from]=p->swap_pos+p->len;
-
- /* if this is currently the write queue, swap banks */
- if(s->inswap.write==from){
- int b=!from;
- s->inswap.write=b;
- s->inswap.tail[b]=s->inswap.head[b]=0;
- if(fseeko(s->inswap.f[b],0,SEEK_SET)){
- fprintf(stderr,"ERROR: unable to seek in swap file; %s\n",strerror(errno));
- exit(1);
- }
- }
- }
- }
-
- p->swap=NULL;
- p->swap_pos=0;
-}
-
-static void remove_frame_from_stream(y4o_in_t *y, stream_t *s, frame_t *p){
- /* pull out of the stream queue */
- if(p->prev){
- p->prev->next=p->next;
- }else{
- s->inq_tail=p->next;
- }
- if(p->next){
- p->next->prev=p->prev;
- }else{
- s->inq_head=p->prev;
- }
- s->tick_depth-=p->ticks;
-
- /* pull out of the file queue */
- if(p->f_prev){
- p->f_prev->f_next=p->f_next;
- }else{
- y->f_tail=p->f_next;
- }
- if(p->f_next){
- p->f_next->f_prev=p->f_prev;
- }else{
- y->f_head=p->f_prev;
- }
-
- p->next=NULL;
- p->prev=NULL;
- p->f_next=NULL;
- p->f_prev=NULL;
-}
-
-static int swap_in_frame(y4o_in_t *y, frame_t *p){
- if(!p->data){
- off_t savepos;
-
- /* fetch data from swap / seekable stream */
- size_t sl=p->len;
- unsigned char *d=malloc(sl);
- if(!d){
- fprintf(stderr,"ERROR: unable to allocate memory for frame\n");
- return 1;
- }
-
- if(p->swap == y->f)
- savepos=ftello(y->f);
-
- if(fseeko(p->swap,p->swap_pos,SEEK_SET)){
- fprintf(stderr,"ERROR: unable to seek in swap file; %s\n",strerror(errno));
- free(d);
- return 1;
- }
- if(fread(d,1,sl,p->swap)<sl){
- if(d)free(d);
- if(feof(p->swap)){
- fprintf(stderr,"ERROR: unable to read frame from swap; EOF\n");
- }else{
- fprintf(stderr,"ERROR: unable to read frame from swap; %s\n",strerror(errno));
- }
- free(d);
- return 1;
- }
-
- if(p->swap == y->f)
- if(fseeko(p->swap,savepos,SEEK_SET)){
- fprintf(stderr,"ERROR: unable to seek in input file; %s\n",strerror(errno));
- exit(1);
- }
-
- p->data=d;
- }
- return 0;
-}
-
-static int parse_time(char *s,double *t){
- long sec=0;
- long usec=0;
- char *pos=strchr(s,':');
- sec=atol(s);
- if(pos){
- char *pos2=strchr(++pos,':');
- sec*=60;
- sec+=atol(pos);
- if(pos2){
- pos2++;
- sec*=60;
- sec+=atol(pos2);
- pos=pos2;
- }
- }else
- pos=s;
- pos=strchr(pos,'.');
- if(pos){
- int digits = strlen(++pos);
- usec=atol(pos);
- while(digits++ < 6)
- usec*=10;
- }
- *t = sec+(usec/(double)1000000);
- return 0;
-}
-
-static int parse_interval(char *s,interval *i){
- char *pos=strchr(s,'-');
- if(!pos)return 1;
- *pos='\0';
- parse_time(s,&i->begin);
- parse_time(pos+1,&i->end);
- *pos='-';
- return 0;
-}
-
-static int parse_ratio(char *s, ratio *r){
- char *pos=strpbrk(optarg,":/");
- if(pos){
- r->num=atof(optarg);
- r->den=atof(pos+1);
- if(r->den==0)return 1;
- }else{
- r->num=atof(optarg);
- r->den=1;
- }
- if(r->num==0)return 1;
- return 0;
-}
-
-const char *optstring = "b:c:e:f:ho:sSvN";
-struct option options [] = {
- {"begin",required_argument,NULL,'b'},
- {"cut",required_argument,NULL,'c'},
- {"end",required_argument,NULL,'e'},
- {"force-fps",required_argument,NULL,'f'},
- {"help",no_argument,NULL,'h'},
- {"no-rewrite",no_argument,NULL,'N'},
- {"output",required_argument,NULL,'o'},
- {"force-sync",no_argument,NULL,'s'},
- {"force-no-sync",no_argument,NULL,'S'},
- {"verbose",required_argument,NULL,'v'},
-
- {NULL,0,NULL,0}
-};
-
-
-void usage(FILE *out){
- fprintf(out,
- "\ny4theora 20090718\n"
- "performs basic operations on yuv4ogg interchange streams in prep for\n"
- "theora encoding\n\n"
-
- "USAGE:\n"
- " y4theora [options] instream [instream...]\n\n"
-
- "OPTIONS:\n"
- " -b --begin HH:MM:SS.XX : discard starting data up to specified\n"
- " begin time. Time is measured relative to\n"
- " output clock\n"
- " -c --cut BEGIN-END : drop output data in the specified interval.\n"
- " Time is measured relative to output clock,\n"
- " but output timestamps will be re-written\n"
- " such that there is no gap.\n"
- " -e --end HH:MM:SS.XX : stop working at specified end time. Time\n"
- " is measured relative to output clock\n"
- " -f --force-fps N:D : Override declared input fps\n"
- " -h --help : print this usage message to stdout and exit\n"
- " with status zero\n"
- " -N --no-rewrite : do not rewrite output PTS values. Retain\n"
- " original PTS values even if input streams\n"
- " were synchronized.\n"
- " -o --output FILE : output file/pipe (stdout default)\n"
- " -s --force-sync : perform autosync even on streams marked as\n"
- " already synchronized\n"
- " -S --force-no-sync : do not perform stream sync even on unsynced\n"
- " streams\n"
- " -v --verbose : turn on all reports\n"
- "\n"
- );
-}
-
-static int search_offset(y4o_in_t *y, int sync, int sno,
- long long *outclock, double *outoffsets, frame_t **lastframe){
- /* first check is against prev sync frame out */
- stream_t *s=y->streams[sno];
-
- if(sync==sno || sync==-1){
- s->inq_tail->presync=1;
- outoffsets[sno]=0.;
- return 0;
- }
-
- if(s->inq_tail->presync)
- return 0;
- else{
- stream_t *ss=y->streams[sync];
- frame_t *p = lastframe[sync];
- double sync_clock = outclock[sync]*ss->tickduration;
- double sno_clock = outclock[sno]*s->tickduration;
- double best_time;
- frame_t *best_frame = NULL;
- double sync_offset=0;
- double last_sno_pts;
- if(p){
- sync_offset = p->pts + p->duration - sync_clock;
- best_time = sno_clock - s->inq_tail->pts + sync_offset;
- if(fabs(best_time) < s->tolerance){
- s->inq_tail->presync=1;
- return 0;
- }
- }else if (ss->inq_tail)
- sync_offset = ss->inq_tail->pts-sync_clock;
-
- /* it would appear we're potentially out of tolerance. pre-fill
- to the desired sync depth */
- if(limited_prime_sync(y, sync, sno))
- return 1;
-
- /* search from y's file tail up to s's tail packet just to make
- sure there's not an out of order packet from the sync stream */
- p=y->f_tail;
- last_sno_pts = s->inq_tail->pts;
- while(p!=s->inq_tail){
- if(p->streamno==sync){
- double time;
- sync_offset = p->pts - sync_clock;
- time = sno_clock - last_sno_pts + sync_offset;
- if(!best_frame || fabs(time) <= fabs(best_time)){
- best_time=time;
- best_frame=s->inq_tail;
- }
- sync_clock += p->duration;
- }
- p=p->f_next;
- }
-
- /* search forward sync_secs from next sync frame looking for tightest timing */
- {
- int count=0;
- double search_clock=sync_clock;
- while(sync_clock<search_clock+sync_secs && p){
- if(p->streamno==sync){
- sync_offset = p->pts - sync_clock;
- sync_clock += p->duration;
- }
- if(p->streamno==sno){
- count++;
- sno_clock += p->duration;
- last_sno_pts = p->pts+p->duration;
- }
- if(p->streamno==sync || p->streamno==sno){
- double time = sno_clock-last_sno_pts+sync_offset;
- if(!best_frame || fabs(time) <= fabs(best_time)){
- best_time=time;
- best_frame=p;
- }
- }
-
- p=p->f_next;
- }
- }
-
- outoffsets[sno]=best_time;
-
- /* mark presync up to the position of best timing */
- p=y->f_tail;
- while(p){
- if(p->streamno==sno)
- p->presync=1;
- if(p==best_frame)break;
- p=p->f_next;
- }
- return 0;
- }
-}
-
-static char timebuffer[80];
-static char *make_time_string(double s){
- long hrs=s/60/60;
- long min=s/60-hrs*60;
- long sec=s-hrs*60*60-min*60;
- long hsec=(s-(int)s)*100;
- if(hrs>0){
- snprintf(timebuffer,80,"%ld:%02ld:%02ld.%02ld",hrs,min,sec,hsec);
- }else if(min>0){
- snprintf(timebuffer,80,"%ld:%02ld.%02ld",min,sec,hsec);
- }else{
- snprintf(timebuffer,80,"%ld.%02ld",sec,hsec);
- }
- return timebuffer;
-}
-
-/* a frame_pop/free that doesn't lock swap data into memory */
-static int discard_head_frame(y4o_in_t *y,stream_t *s, long long *outclock){
- frame_t *p=s->inq_head;
- if(!p) return 1;
-
- // no need to release swap; it will evaporate on its own
- remove_frame_from_stream(y,s,p);
-
- if(verbose){
- if(s->type==STREAM_VIDEO){
- fprintf(stderr,"Stream %d [%s]: Dropping video frame to maintain sync\n",
- p->streamno,
- make_time_string((outclock[p->streamno]+s->tick_depth)*s->tickduration));
- }else{
- fprintf(stderr,"Stream %d [%s]: Dropping %gs of audio to maintain sync\n",
- p->streamno,
- make_time_string((outclock[p->streamno]+s->tick_depth)*s->tickduration),
- p->duration);
- }
- }
-
- y4o_free_frame(p);
-
- return 0;
-}
-
-/* a frame_pull/free that doesn't lock swap data into memory */
-static int discard_tail_frame(y4o_in_t *y,stream_t *s, long long *outclock){
- frame_t *p=s->inq_tail;
- if(!p) return 1;
-
- if(verbose){
- if(s->type==STREAM_VIDEO){
- fprintf(stderr,"Stream %d [%s]: Dropping video frame to maintain sync\n",
- p->streamno,
- make_time_string(outclock[p->streamno]*s->tickduration));
- }else{
- fprintf(stderr,"Stream %d [%s]: Dropping %gs of audio to maintain sync\n",
- p->streamno,
- make_time_string(outclock[p->streamno]*s->tickduration),
- p->duration);
- }
- }
-
- release_frame_swap(s,p);
- remove_frame_from_stream(y,s,p);
- y4o_free_frame(p);
- return 0;
-}
-
-/* conditionally remove data from the head; in the case of audio it
- can discard partial frames */
-static int trim_from_head(y4o_in_t *y,int sno,long long *outclock,double *outoffsets){
- stream_t *s=y->streams[sno];
- frame_t *p=s->inq_head;
- if(!p)return 1;
- if(s->type==STREAM_AUDIO){
- int samples = p->len/(s->m.audio.ch*3);
- int remsamples = outoffsets[sno]*s->m.audio.rate;
- if(samples<=remsamples){
- /* remove whole frame */
- if(samples==remsamples)
- outoffsets[sno] = 0.;
- else
- outoffsets[sno] -= p->duration;
- return discard_head_frame(y,s,outclock);
- }else{
- /* trim frame */
- if(remsamples>0){
- /* altering the len will not mess up swap */
- p->len -= remsamples * s->m.audio.ch*3;
- p->ticks -= remsamples;
- p->duration -= remsamples*s->tickduration;
- s->tick_depth -= remsamples;
-
- if(verbose)
- fprintf(stderr,"Stream %d [%s]: Dropping %gs of audio to maintain sync\n",
- p->streamno,
- make_time_string((outclock[sno]+s->tick_depth)*s->tickduration),
- outoffsets[sno]);
-
- }
- outoffsets[sno] = 0.;
- }
- }else if(s->type==STREAM_VIDEO){
- /* because video granularity is relatively coarse, only remove the
- frame if it gets us closer to the time goal */
- if(fabs(outoffsets[sno]-p->duration)<outoffsets[sno]){
- /* remove frame */
- outoffsets[sno] -= p->duration;
- if(outoffsets[sno]<0) outoffsets[sno]=0.;
- return discard_head_frame(y,s,outclock);
- }else
- outoffsets[sno]=0.;
- }else
- return 1;
- return 0;
-}
-
-/* conditionally remove data from the tail; in the case of audio it
- can discard partial frames */
-static int trim_from_tail(y4o_in_t *y,int sno,long long *outclock,double *outoffsets){
- stream_t *s=y->streams[sno];
- frame_t *p=s->inq_tail;
- if(!p)return 1;
-
- if(s->type==STREAM_AUDIO){
- int samples = p->len/(s->m.audio.ch*3);
- int remsamples = outoffsets[sno]*s->m.audio.rate;
- if(samples<=remsamples){
- /* remove whole frame */
- if(samples==remsamples)
- outoffsets[sno] = 0.;
- else
- outoffsets[sno] -= p->duration;
- return discard_tail_frame(y,s,outclock);
- }else{
- /* trim frame */
- if(remsamples>0){
- int bytes = remsamples * s->m.audio.ch*3;
-
- if(verbose)
- fprintf(stderr,"Stream %d [%s]: Dropping %gs of audio to maintain sync\n",
- p->streamno,make_time_string(outclock[sno]*s->tickduration),
- outoffsets[sno]);
-
- /* load frame data into memory and release swap */
- swap_in_frame(y,p);
- release_frame_swap(s,p);
- memmove(p->data,p->data+bytes,p->len-bytes);
- p->len -= bytes;
- p->duration -= remsamples*s->tickduration;
- p->ticks -= remsamples;
- s->tick_depth -= remsamples;
- }
- outoffsets[sno] = 0.;
- }
- }else if(s->type==STREAM_VIDEO){
- /* because video granularity is relatively coarse, only remove the
- frame if it gets us closer to the time goal */
- if(fabs(outoffsets[sno]-p->duration)<outoffsets[sno]){
- /* remove frame */
- outoffsets[sno] -= p->duration;
- if(outoffsets[sno]<0) outoffsets[sno]=0.;
- return discard_tail_frame(y,s,outclock);
- }else
- outoffsets[sno]=0.;
- }else
- return 1;
- return 0;
-}
-
-static void write_frame_i(FILE *outfile, int sno, unsigned char *b, int len, double pts){
-
- if(fprintf(outfile,"FRAME S%d L%d P%.3f\n",sno,len,pts)<0 ||
- fwrite(b,1,len,outfile)<len){
- fprintf(stderr,"ERROR: Unable to write to output; %s\n",strerror(errno));
- exit(1);
- }
-}
-
-typedef struct {
- long long begin;
- long long end; // one past
-} cutentry;
-
-typedef struct {
- int cuts;
- cutentry *cut;
- int ended;
-} cutlist;
-
-static int intervalcmp(const void *p1, const void *p2){
- cutentry *a=(cutentry *)p1;
- cutentry *b=(cutentry *)p2;
-
- return (int)(a->begin-b->begin);
-}
-
-/* Sanitize and distill the list of user-requested cuts into a more
- efficient form */
-
-static cutlist *cuts_into_cutlist(y4o_in_t *y, interval *list, int n, ratio fps){
- cutlist *ret=calloc(y->num_streams,sizeof(*ret));
- cutentry *l = calloc(n,sizeof(*l));
- int i,j;
-
- /* Quantize the cut requests to primary video stream frames. Video
- has much coarser resolution than audio, and we likely care about
- the primary stream over any subsidary streams. Quantizing to the
- vid stream we care about most will result in the most predictable
- cut behavior. */
-
- for(i=0;i<n;i++){
- l[i].begin = (long long)rint(list[i].begin*fps.num/fps.den);
- if(list[i].end<0)
- l[i].end = -1;
- else
- l[i].end = (long long)rint(list[i].end*fps.num/fps.den);
- }
-
- /* sort by beginning time */
- qsort(l,n,sizeof(*l),intervalcmp);
-
- /* remove nonsensical cut regions */
- for(i=0;i<n;i++){
- int flag=0;
- /* zero or negative range check */
- if(l[i].begin>=l[i].end && !l[i].end<0) flag=1;
- if(flag){
- if(i+1<n)
- memcpy(&l[i],&l[i+1],(n-i-1)*sizeof(l[i]));
- n--;
- }
- }
-
- /* merge overlapping cut ranges */
- for(i=1;i<n;i++){
- if(l[i].begin<=l[i-1].end){
- if(l[i-1].end<l[i].end && !l[i-1].end<0)
- l[i-1].end=l[i].end;
- if(i+1<n)
- memcpy(&l[i],&l[i+1],(n-i-1)*sizeof(l[i]));
- n--;
- }
- }
-
- /* convert cut ranges into native tick units of each stream,
- tracking fractional ticks forward across cuts so there's no
- cumulative drift */
- for(j=0;j<y->num_streams;j++){
- stream_t *s = y->streams[j];
- double track=0.;
- ret[j].cut=calloc(n,sizeof(*ret[j].cut));
- for(i=0;i<n;i++){
- double b = l[i].begin*(double)fps.den/fps.num;
- double e = (l[i].end<0?-1:l[i].end*(double)fps.den/fps.num);
- long long bt = ceil(b/s->tickduration-EPSILON);
- long long et = (e<0?LONG_MAX:ceil(e/s->tickduration-EPSILON));
- double bf = b/s->tickduration - bt;
- double ef = (e<0?0:et - e/s->tickduration);
-
- if(e>=0){
- /* compensate for cumulative fractional ticks.
- Rather than choosing the closest frame, we always choose the
- next; it is better for video to slightly lead than slightly
- lag. */
- track += bf+ef;
- if(track-EPSILON>1.){
- et--;
- track-=1;
- }
- if(track+EPSILON<0.){
- et++;
- track+=1;
- }
- }
-
- ret[j].cut[i].begin = bt;
- ret[j].cut[i].end = et;
- }
- ret[j].cuts=n;
-
- }
-
- return ret;
-}
-
-/* Cut functionality injected here; thus the seperate out and cut clocks */
-static void write_frame(FILE *outfile, y4o_in_t *y,
- long long *outclock,
- long long *cutclock, cutlist *cut,
- frame_t *p){
- int sno = p->streamno;
- stream_t *s=y->streams[sno];
- double outpts = (no_rewrite?p->pts:cutclock[sno]*s->tickduration);
- cutlist *c=&cut[sno];
-
- /* does the next cut impact this frame? */
- if(c->cuts && outclock[sno]+p->ticks>c->cut[0].begin){
- /* is the entire frame to be cut? */
- if(outclock[sno]<c->cut[0].begin || outclock[sno]+p->ticks > c->cut[0].end){
- /* no, frame must be bisected. */
- /* is any of the frame prior to cut beginning? */
- if(outclock[sno]<c->cut[0].begin){
- /* yes; write the data prior to the cut, mutate current packet
- and push the rest back onto the stream tail for another
- pass */
- int beginticks=c->cut[0].begin-outclock[sno];
- int beginbytes=beginticks*s->bytes_per_tick;
-
- write_frame_i(outfile, sno, p->data, beginbytes, outpts);
- cutclock[sno]+=beginticks;
- outclock[sno]+=beginticks;
-
- /* mutate packet */
- p->data+=beginbytes;
- p->len-=beginbytes;
- p->ticks-=beginticks;
-
- /* recurse */
- write_frame(outfile, y, outclock, cutclock, cut, p);
-
- /* restore packet */
- p->data-=beginbytes;
- p->len+=beginbytes;
- p->ticks+=beginticks;
-
- }else{
- /* no; the beginning is cut. Drop it, mutate current packet
- and push any remaining data at the end back onto the stream
- tail for another pass (don't just write it, there may be
- another cut here in this same frame) */
- int beginticks=c->cut[0].end-outclock[sno];
- int beginbytes=beginticks*s->bytes_per_tick;
-
- outclock[sno]+=beginticks;
-
- /* mutate packet */
- p->data+=beginbytes;
- p->len-=beginbytes;
- p->ticks-=beginticks;
-
- /* remove cut entry */
- memmove(c->cut,c->cut+1,(c->cuts-1)*sizeof(*c->cut));
- c->cuts--;
-
- /* recurse */
- write_frame(outfile, y, outclock, cutclock, cut, p);
-
- /* restore packet */
- p->data-=beginbytes;
- p->len+=beginbytes;
- p->ticks+=beginticks;
-
- }
- }else{
- /* drop whole frame */
- outclock[sno]+=p->ticks;
- if(c->cuts==1 && c->cut[0].end==LONG_MAX && !c->ended){
- c->ended=1;
- global_ended++;
- }
- }
- }else{
- /* No cuts, write the frame */
- write_frame_i(outfile, sno, p->data, p->len, outpts);
- cutclock[sno]+=p->ticks;
- outclock[sno]+=p->ticks;
- }
-}
-
-static void pull_write_frame(FILE *outfile, y4o_in_t *y,frame_t **lastframe,
- long long *outclock,long long *cutclock, cutlist *cutticks,
- int sno){
- stream_t *s = y->streams[sno];
- frame_t *p = s->inq_tail;
-
- if(!p) return;
-
- swap_in_frame(y,p);
- release_frame_swap(s,p);
- remove_frame_from_stream(y,s,p);
- write_frame(outfile,y,outclock,cutclock,cutticks,p);
- if(lastframe[sno])
- y4o_free_frame(lastframe[sno]);
- lastframe[sno]=p;
-
-}
-
-/* conditionally duplicates last frame in video stream, or generates a frame of audio silence */
-static int duplicate_frame(FILE *outfile,y4o_in_t *y,frame_t **lastframe, long long *outclock,
- long long *cutclock, cutlist *cutticks,
- double *outoffsets, int sno){
- stream_t *s = y->streams[sno];
- frame_t *p = lastframe[sno];
-
- if(!p){
- /* no preceeding frame. Look to next frame instead */
- if(limited_prime(y,sno)) return 1;
- p=s->inq_tail;
- swap_in_frame(y,p);
- release_frame_swap(s,p);
- }
-
- switch(s->type){
- case STREAM_VIDEO:
- /* we only dup the frame if doing so gets us closer to the ideal clock sync */
- if(fabs(outoffsets[sno]+p->duration)<fabs(outoffsets[sno])){
- /* dup it */
- if(verbose)
- fprintf(stderr,"Stream %d [%s]: Repeating video frame to maintain sync\n",
- p->streamno,make_time_string(outclock[sno]*s->tickduration));
-
- write_frame(outfile,y,outclock,cutclock,cutticks,p);
- outoffsets[sno]+=p->duration;
- if(outoffsets[sno]>0)outoffsets[sno]=0.;
- }else
- outoffsets[sno]=0.;
- break;
- case STREAM_AUDIO:
- /* can't dup audio. Write silence (evenrually, we want to
- extrapolate and smooth the discontinuity) */
- {
- frame_t lp;
- /* number of samples already bounded by fill_secs */
- int samples = -outoffsets[sno] * s->m.audio.rate;
- int bytes = samples * s->m.audio.ch;
- unsigned char *data=calloc(bytes,1);
-
- if(verbose)
- fprintf(stderr,"Stream %d [%s]: Adding %gs of silence to maintain sync\n",
- p->streamno,make_time_string(outclock[sno]*s->tickduration),-outoffsets[sno]);
-
- lp.data=data;
- lp.len=bytes;
- lp.streamno=sno;
- lp.pts=(p?p->pts:0.);
- lp.ticks=samples;
- write_frame(outfile,y,outclock,cutclock,cutticks,&lp);
- free(data);
- if(p)p->pts-=outoffsets[sno];
- outoffsets[sno]=0.;
- }
- break;
- default:
- break;
- }
- return 0;
-}
-
-int main(int argc,char *const *argv){
- int c,long_option_index;
-
- long long *outclock=NULL;
- long long *cutclock=NULL;
- cutlist *cutticks=NULL;
- double *outoffsets=NULL;
- frame_t **lastframe=NULL;
-
- /* for time cuts */
- double begin=-1;
- double end=-1;
- interval *ccut=NULL;
- int ccuts=0;
-
- FILE *outfile = NULL;
- int outheader=0;
-
- FILE **infile = NULL;
- char *const*infilenames=NULL;
- int infiles=0,i;
-
- int primary_video=-1;
- int primary_audio=-1;
- int sync_stream=-1;
-
- y4o_in_t *ty=NULL;
- y4o_in_t *y=NULL;
-
- while((c=getopt_long(argc,argv,optstring,options,&long_option_index))!=EOF){
- switch(c){
- case 'h':
- usage(stdout);
- return 0;
- case 'v':
- verbose=1;
- break;
- case 'b':
- parse_time(optarg,&begin);
- break;
- case 'e':
- parse_time(optarg,&end);
- break;
- case 'f':
- if(parse_ratio(optarg,&force_fps)){
- fprintf(stderr,"ERROR: cannot parse fps argument '%s'\n",optarg);
- exit(1);
- }
- break;
- case 'c':
- if(!ccut)
- ccut=calloc(1,sizeof(*ccut));
- else
- ccut=realloc(ccut,(ccuts+1)*sizeof(*ccut));
- if(parse_interval(optarg,ccut+ccuts)){
- fprintf(stderr,"ERROR: cannot parse cut interval '%s'\n",optarg);
- exit(1);
- }
- ccuts++;
- break;
- case 's':
- force_sync=1;
- force_no_sync=0;
- break;
- case 'S':
- force_sync=0;
- force_no_sync=1;
- break;
- case 'N':
- no_rewrite=1;
- break;
- case 'o':
- if(outfile)
- fclose(outfile);
- outfile=fopen(optarg,"wb");
- if(!outfile){
- fprintf(stderr,"ERROR: Unable to open '%s' for output; %s\n",optarg,strerror(errno));
- exit(1);
- }
- break;
- default:
- usage(stderr);
- exit(1);
- }
- }
-
- if(optind<argc){
- /* assume that anything following the options must be an input filename */
- infiles=argc-optind;
- infile=calloc(infiles,sizeof(*infile));
- infilenames=argv+optind;
-
- for(i=0;i<infiles;i++){
- infile[i]=fopen(argv[optind+i],"rb");
- if(infile[i]==NULL){
- fprintf(stderr,"ERROR: Unable to open '%s' for input; %s\n",argv[optind+i],strerror(errno));
- exit(1);
- }
- }
- optind++;
- }
-
- if(outfile==NULL)outfile=stdout;
-
- for(i=0;i<infiles;i++){
- int j;
-
- if(verbose)
- fprintf(stderr,"Begin processing input stream \"%s\"...\n",infilenames[i]);
-
- ty=y4o_open_in(infile[i]);
-
- if(y==NULL){
-
- for(j=0;j<ty->num_streams;j++){
- stream_t *s = ty->streams[j];
- switch(s->type){
- case STREAM_VIDEO:
- if(primary_video<0)primary_video=j;
- if(verbose)
- fprintf(stderr,"Using stream %d as primary video stream\n",j);
- break;
- case STREAM_AUDIO:
- if(primary_audio<0)primary_audio=j;
- if(verbose)
- fprintf(stderr,"Using stream %d as primary audio stream\n",j);
- break;
- default:
- break;
- }
- }
- if(sync_stream==-1)
- sync_stream=primary_audio;
- }
-
- /* warn about force mismatches, unknown chroma spaces */
- if(!force_fps.num && primary_video>=0){
- force_fps.num=ty->streams[primary_video]->m.video.fps_n;
- force_fps.den=ty->streams[primary_video]->m.video.fps_d;
- }
-
- if(!cutticks){
- /* translate begin/end to cut intervals */
- if(begin!=-1){
- if(!ccut)
- ccut=calloc(1,sizeof(*ccut));
- else
- ccut=realloc(ccut,(ccuts+1)*sizeof(*ccut));
- ccut[ccuts].begin=0;
- ccut[ccuts].end=begin;
- ccuts++;
- }
-
- if(end!=-1){
- if(!ccut)
- ccut=calloc(1,sizeof(*ccut));
- else
- ccut=realloc(ccut,(ccuts+1)*sizeof(*ccut));
- ccut[ccuts].begin=end;
- ccut[ccuts].end=-1;
- ccuts++;
- }
-
- cutticks=cuts_into_cutlist(ty, ccut, ccuts, force_fps);
-
- }
-
- for(j=0;j<ty->num_streams;j++){
- switch(ty->streams[j]->type){
- case STREAM_VIDEO:
- if(fabs(force_fps.num/force_fps.den -
- ty->streams[j]->m.video.fps_n/(double)ty->streams[j]->m.video.fps_d)>EPSILON){
- fprintf(stderr,"Forcing stream %d file \"%s\" to %.3ffps.\n",
- j,infilenames[i],force_fps.num/(double)force_fps.den);
- }
- if(ty->streams[j]->m.video.format == C420unknown){
- fprintf(stderr,"WARNING: Assuming mpeg2 chroma positioning for stream %d.\n",
- j);
- ty->streams[j]->m.video.format = C420mpeg2;
- }
- if(ty->streams[j]->m.video.format == C422unknown){
- fprintf(stderr,"WARNING: Assuming smpte chroma positioning for stream %d.\n",
- j);
- ty->streams[j]->m.video.format = C422smpte;
- }
- break;
- default:
- break;
- }
- }
-
- if(y){
- /* if opening a new file in sequence, it must have the same traits as the first */
- if(ty->num_streams!=y->num_streams){
- fprintf(stderr,"ERROR: Number of streams mismatch in file \"%s\".\n"
- " Sequential input streams must have matching stream\n"
- " numbers and types.\n",
- infilenames[i]);
- exit(1);
- }
- for(j=0;j<y->num_streams;j++){
- stream_t *r = y->streams[j];
- stream_t *s = ty->streams[j];
-
- if(s->type != r->type){
- fprintf(stderr,"ERROR: Stream %d type mismatch in file \"%s\".\n"
- " Sequential input streams must have matching stream\n"
- " numbers and types.\n",
- j,infilenames[i]);
- exit(1);
- }
-
- /* eventually, we should be able to autoconvert and force all
- these settings */
- switch(s->type){
- case STREAM_VIDEO:
- if(s->m.video.w != r->m.video.w ||
- s->m.video.h != r->m.video.h){
- fprintf(stderr,"ERROR: Video dimensions mismatch in stream %d file \"%s\".\n"
- " Sequential video streams must have matching dimensions.\n",
- j,infilenames[i]);
- exit(1);
- }
- if(abs(s->m.video.pa_n/(double)s->m.video.pa_d -
- r->m.video.pa_n/(double)r->m.video.pa_d)>EPSILON){
- fprintf(stderr,"WARNING: Pixel aspect mismatch in stream %d file \"%s\".\n"
- " Forcing aspect to match original stream.\n",
- j,infilenames[i]);
- s->m.video.pa_n = r->m.video.pa_n;
- s->m.video.pa_d = r->m.video.pa_d;
- }
- if(s->m.video.format != r->m.video.format){
- fprintf(stderr,"ERROR: Chroma format mismatch in stream %d file \"%s\".\n"
- " Sequential video streams must have matching chroma\n"
- " formats.\n",
- j,infilenames[i]);
- exit(1);
- }
- if(s->m.video.i != r->m.video.i){
- fprintf(stderr,"ERROR: Interlacing format mismatch in stream %d file \"%s\".\n"
- " Sequential video streams must have matching interlacing\n"
- " formats.\n",
- j,infilenames[i]);
- exit(1);
- }
- break;
- case STREAM_AUDIO:
- if(s->m.audio.rate != r->m.audio.rate){
- fprintf(stderr,"ERROR: Audio rate mismatch in stream %d file \"%s\"."
- " Sequential audio streams must have matching sampling rate\n"
- " and channels\n.",
- j,infilenames[i]);
- exit(1);
- }
- if(s->m.audio.ch != r->m.audio.ch){
- fprintf(stderr,"ERROR: Audio rate mismatch in stream %d file \"%s\"."
- " Sequential audio streams must have matching sampling rate\n"
- " and channels\n.",
- j,infilenames[i]);
- exit(1);
- }
- break;
- default:
- break;
- }
- }
- }
-
- /* ready output tracking if unallocated *****************************************************/
- if(!outclock){
- outclock = calloc(ty->num_streams,sizeof(*outclock));
- cutclock = calloc(ty->num_streams,sizeof(*cutclock));
- outoffsets = calloc(ty->num_streams,sizeof(*outoffsets));
- lastframe = calloc(ty->num_streams,sizeof(*lastframe));
- }
-
- /* prime input stream queues ****************************************************************/
- /* not necessary, but it allows reporting some timing information */
- for(j=0;j<ty->num_streams;j++){
- if(limited_prime(ty,j)){
- fprintf(stderr,"ERROR: Did not find start of stream %d within first %f seconds\n"
- " of data. Aborting.\n",j,sync_secs);
- exit(1);
- }else{
- if(verbose){
- int h=floor(ty->streams[j]->inq_tail->pts/60/60);
- int m=floor(ty->streams[j]->inq_tail->pts/60)-h*60;
- int s=floor(ty->streams[j]->inq_tail->pts)-h*60*60-m*60;
- int d=floor((ty->streams[j]->inq_tail->pts-floor(ty->streams[j]->inq_tail->pts))*100);
- fprintf(stderr,"Stream %d initial PTS %02d:%02d:%02d.%02d\n",
- j,h,m,s,d);
- }
- }
- }
-
- /* initialize output if not already initialized *********************************************/
- if(!outheader){
- /* container header */
- fprintf(outfile,"YUV4OGG S%c\n",
- (!ty->synced && force_no_sync ? 'n' : 'y'));
-
- /* stream headers */
- for(j=0;j<ty->num_streams;j++){
- switch(ty->streams[j]->type){
- case STREAM_AUDIO:
- fprintf(outfile,"AUDIO R%d C%d\n",
- ty->streams[j]->m.audio.rate,ty->streams[j]->m.audio.ch);
- break;
- case STREAM_VIDEO:
- fprintf(outfile,"VIDEO W%d H%d F%d:%d I%c A%d:%d C%s\n",
- ty->streams[j]->m.video.w,
- ty->streams[j]->m.video.h,
- ty->streams[j]->m.video.fps_n,
- ty->streams[j]->m.video.fps_d,
- ty->streams[j]->m.video.i?(ty->streams[j]->m.video.i==TOP_FIRST?'t':'b'):'p',
- ty->streams[j]->m.video.pa_n,
- ty->streams[j]->m.video.pa_d,
- chromaformat[ty->streams[j]->m.video.format]);
- break;
- default:
- break;
- }
- }
- outheader=1;
- }
-
- if(verbose) fprintf(stderr,"\n");
-
- if(!ty->synced && !force_no_sync){
-
- /* find clock start times of new substreams ***********************************************/
- {
- long long rec_outclock[ty->num_streams];
- for(j=0;j<ty->num_streams;j++){
- rec_outclock[j]=outclock[j];
- if(y)
- rec_outclock[j]+=y->streams[j]->tick_depth;
- }
- for(j=0;j<ty->num_streams;j++)
- search_offset(ty,sync_stream,j,outclock,outoffsets,lastframe);
- }
-
- /* trim holdover streams if needed given new offsets **************************************/
- if(y){
- stream_t *ss=y->streams[sync_stream];
- double sync_time = ss->tickduration*outclock[sync_stream];
- for(j=0;j<y->num_streams;j++){
- if(outoffsets[j]>0){
- /* we have too many frames; where to trim? */
- stream_t *ys = y->streams[j];
- double time = ys->tickduration*outclock[j];
- if(time>sync_time){
- /* trim holdover back, but no farther than sync_time */
- if(time-outoffsets[j]<sync_time)
- outoffsets[j]=time-sync_time;
-
- /* remove frames from head of holdover stream */
- while(outoffsets[j]>0)
- if(trim_from_head(y,j,outclock,outoffsets))break;
- }
- }
- }
- }
-
- /* sync loop *****************************************************************************/
- while(global_ended < ty->num_streams){
- double earliest=outclock[0]*ty->streams[0]->tickduration;
- int sno=0;
-
- /* look for lowest clock time of stream queues */
- for(j=1;j<ty->num_streams;j++){
- double time=outclock[j]*ty->streams[j]->tickduration;
- if(time<earliest){
- earliest=time;
- sno=j;
- }
- }
-
- /* we know which stream to pull first */
- if(y && y->streams[sno]->inq_tail){
- /* holdover stream; rules here are a little different. We simply flush. */
- pull_write_frame(outfile,y,lastframe,outclock,cutclock,cutticks,sno);
- }else{
- stream_t *s=ty->streams[sno];
- if(limited_prime(ty,sno)){
- break;
- }
- if(outoffsets[sno]<0){
- /* there's an already-established gap in the stream. dup it out */
- if(duplicate_frame(outfile,ty,lastframe,outclock,cutclock,cutticks,outoffsets,sno)){
- break;
- }
- }else if (outoffsets[sno]>0){
- trim_from_tail(ty,sno,outclock,outoffsets);
- }else{
- if(search_offset(ty,sync_stream,sno,outclock,outoffsets,lastframe)){
- break;
- }
- if(s->inq_tail->presync && outoffsets[sno]==0)
- pull_write_frame(outfile,ty,lastframe,outclock,cutclock,cutticks,sno);
- }
- }
- if(y && !y->f_tail){
- y4o_close_in(y);
- y=NULL;
- }
- }
-
- if(y && global_ended < ty->num_streams){
- /* got through loop without flushing all of y? Alert the
- user, dump the frames */
- int flag=0;
-
- for(j=0;j<y->num_streams;j++){
- if(y->streams[j]->inq_tail){
- fprintf(stderr,"WARNING: Previous stream %d completely overlaps current stream?\n",j);
- flag=1;
- }
- }
- if(!flag)
- fprintf(stderr,"WARNING: Previous input stream queues emptied, but stream still open?\n");
-
- y4o_close_in(y);
- }
-
- }else{
- if(y) y4o_close_in(y);
-
- /* if we're not trying to sync, just write the frames in output clock order */
- while(global_ended < ty->num_streams){
- double earliest;
- int sno=-1;
-
- /* look for lowest clock time of stream queues */
- for(j=0;j<ty->num_streams;j++){
- stream_t *s = ty->streams[j];
- double time=outclock[j]*s->tickduration;
- limited_prime(ty,j);
- if(s->inq_tail && (sno==-1 || time<earliest)){
- earliest=time;
- sno=j;
- }
- }
-
- if(sno==-1)break;
- pull_write_frame(outfile,ty,lastframe,outclock,cutclock,cutticks,sno);
- }
- }
-
- y=ty;
-
- }
-
- if(y){
- if(verbose)
- fprintf(stderr,"Flushing end of stream\n\n");
- while(y->f_tail && global_ended < ty->num_streams){
- /* continue to obey clock order */
- double earliest=-1;
- int j,sno=-1;
-
- /* look for lowest clock time of remaning stream queues */
- for(j=0;j<ty->num_streams;j++){
- if(y->streams[j]->inq_tail){
- double time=outclock[j]*y->streams[j]->tickduration;
- if(sno==-1 || time<earliest){
- earliest=time;
- sno=j;
- }
- }
- }
- pull_write_frame(outfile,y,lastframe,outclock,cutclock,cutticks,sno);
- }
- if(verbose)
- fprintf(stderr,"Done processing all streams\n\n");
-
- if(cutticks){
- for(i=0;i<y->num_streams;i++)
- if(cutticks[i].cut)free(cutticks[i].cut);
- free(cutticks);
- }
- y4o_close_in(y);
- }else{
- usage(stderr);
- }
- if(outfile!=stdout)fclose(outfile);
- if(outclock)free(outclock);
- if(cutclock)free(cutclock);
- if(outoffsets)free(outoffsets);
- if(lastframe)free(lastframe);
- return 0;
-}
Added: trunk/y4oi/y4oi.h
===================================================================
--- trunk/y4oi/y4oi.h (rev 0)
+++ trunk/y4oi/y4oi.h 2010-02-24 07:40:16 UTC (rev 16934)
@@ -0,0 +1,285 @@
+/*
+ *
+ * y4oi:
+ * A utility for doing several simple but essential operations
+ * on yuv4ogg interchange streams.
+ *
+ * y4oi copyright (C) 2010 Monty <monty at xiph.org>
+ *
+ * y4oi 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, or (at your option)
+ * any later version.
+ *
+ * y4oi 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 Postfish; see the file COPYING. If not, write to the
+ * Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ *
+ */
+
+#define _FILE_OFFSET_BITS 64
+#define EPSILON 1e-6
+#define _GNU_SOURCE
+
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+#include <math.h>
+#include <getopt.h>
+#include <limits.h>
+
+typedef struct {
+ double begin;
+ double end;
+} interval;
+
+typedef struct {
+ double num;
+ double den;
+} ratio;
+
+typedef enum {
+ Cmono=0,
+ C411ntscdv=1,
+ C420jpeg=2,
+ C420mpeg2=3,
+ C420paldv=4,
+ C420unknown=5,
+ C422jpeg=6,
+ C422smpte=7,
+ C422unknown=8,
+ C444=9,
+} chromafmt;
+
+extern char *chromaformat[];
+extern unsigned char chromabits[];
+extern char *chromaformat_long[];
+extern ratio force_fps;
+extern int force_no_sync;
+extern int force_non_interlaced;
+
+typedef enum {
+ STREAM_INVALID=0,
+ STREAM_VIDEO=1,
+ STREAM_AUDIO=2
+} stream_type;
+
+typedef enum {
+ INVALID=-1,
+ PROGRESSIVE=0,
+ TOP_FIRST=1,
+ BOTTOM_FIRST=2
+} interlace_type;
+
+typedef struct stream_s stream_t;
+typedef struct frame_s frame_t;
+
+
+typedef struct {
+ FILE *f;
+ int eof;
+ stream_t **streams;
+ int num_streams;
+ int synced;
+ int seekable;
+ frame_t *f_head;
+ frame_t *f_tail;
+} y4o_in_t;
+
+struct frame_s {
+ double pts;
+ size_t len;
+ int streamno;
+ int presync;
+ int ticks;
+ int bos;
+ double duration;
+
+ unsigned char *data;
+ FILE *swap;
+ off_t swap_pos;
+
+ frame_t *next;
+ frame_t *prev;
+
+ frame_t *f_next;
+ frame_t *f_prev;
+
+ stream_t *s;
+};
+
+typedef struct {
+
+ FILE *f[2];
+ off_t head[2];
+ off_t tail[2];
+ int write;
+} swap_t;
+
+typedef struct {
+ int rate;
+ int ch;
+} audio_param_t;
+
+typedef struct {
+ int fps_n;
+ int fps_d;
+ int pa_n;
+ int pa_d;
+ int frame_n;
+ int frame_d;
+ int format;
+ int w;
+ int h;
+ interlace_type i;
+} video_param_t;
+
+typedef union {
+ audio_param_t audio;
+ video_param_t video;
+} stream_param_t;
+
+struct stream_s{
+ stream_type type;
+ int stream_num;
+ double tickduration;
+ long bytes_per_tick;
+ double tolerance;
+ int bos;
+
+ stream_param_t m;
+
+ frame_t *inq_head;
+ frame_t *inq_tail;
+ swap_t inswap;
+ long tick_depth;
+
+ stream_t *next;
+ stream_t *prev;
+ y4o_in_t *y;
+};
+
+extern y4o_in_t *y4o_open_in(FILE *f);
+extern void y4o_close_in(y4o_in_t *y);
+extern frame_t *y4o_read_frame(y4o_in_t *y);
+extern int y4o_lock_frame(frame_t *p);
+extern void y4o_pull_frame(frame_t *p);
+extern void y4o_free_frame(frame_t *p);
+
+typedef struct filter_queue_frame fq_frame_t;
+typedef struct filter_queue fq_t;
+
+struct filter_queue_frame {
+ unsigned char *body;
+ int body_size;
+
+ int synced;
+ int sn;
+ int sno;
+ stream_type st;
+ stream_param_t sp;
+ double pts;
+ int bos;
+ int eos;
+
+ fq_frame_t *prev;
+ fq_frame_t *next;
+} queue_frame_t;
+
+typedef struct {
+ char *name;
+ char *desc;
+ void (*process)(fq_t *);
+ int is_type_output_p;
+} filter_t;
+
+typedef enum {
+ QUEUE_INIT,
+ QUEUE_STARTUP,
+ QUEUE_PROCESS,
+ QUEUE_FLUSH,
+ QUEUE_FINISHED
+} fq_state;
+
+struct filter_queue {
+ int streams;
+ int *stream_depth;
+ fq_state state;
+ fq_frame_t *head;
+ fq_frame_t *tail;
+ fq_t *next;
+ filter_t *filter;
+ char **option_keys;
+ char **option_vals;
+ void *internal;
+};
+
+extern void filter_forward_frame(fq_t *fq);
+extern void filter_drop_frame(fq_t *fq);
+extern void filter_submit_frame(stream_t *s, unsigned char *b, int len, double pts);
+extern void filter_flush();
+extern int filter_append(char *cmd);
+extern int filter_is_last_output();
+
+extern int verbose;
+#ifndef NAME
+#define NAME queue->filter->name
+#endif
+
+extern char *lastprint;
+
+#define yprint(level, format, args...) { \
+ if(strcmp(format,"\n")){ \
+ if((lastprint&&!NAME) || (!lastprint&&NAME) || \
+ (lastprint&&NAME&&strcmp(lastprint,NAME?NAME:""))) \
+ fprintf(stderr,"\n"); \
+ lastprint=(char *)NAME; \
+ if(NAME){ \
+ if(verbose){ \
+ fprintf(stderr,"%s [%8s]: " format,level?level:" ", \
+ NAME?NAME:"",## args); \
+ }else{ \
+ fprintf(stderr,"%s%s" format,level?level:"",level?": ":"" \
+ ,## args); \
+ } \
+ }else{ \
+ fprintf(stderr,"%s%s" format,level?level:"", \
+ level?": ":"",## args); \
+ } \
+ }else{ \
+ fprintf(stderr,"\n"); \
+ } \
+ }
+
+#define ydebug(format, args...) { \
+ if(verbose==2) \
+ yprint(" debug",format,##args); \
+ }
+
+#define yverbose(format, args...) { \
+ if(verbose>0) \
+ yprint(NULL,format,##args); \
+ }
+
+#define yinfo(format, args...) { \
+ if(verbose>=0) \
+ yprint(NULL,format,##args); \
+ }
+
+#define ywarn(format, args...) { \
+ if(verbose>=0) \
+ yprint("WARNING",format,##args); \
+ }
+
+#define yerror(format, args...) { \
+ if(verbose>=0) \
+ yprint(" ERROR",format,##args); \
+ }
More information about the commits
mailing list