[xiph-commits] r16466 - in trunk: . y4oi
xiphmont at svn.xiph.org
xiphmont at svn.xiph.org
Tue Aug 11 20:48:28 PDT 2009
Author: xiphmont
Date: 2009-08-11 20:48:28 -0700 (Tue, 11 Aug 2009)
New Revision: 16466
Added:
trunk/y4oi/
trunk/y4oi/Makefile
trunk/y4oi/y4oi
trunk/y4oi/y4oi.c
trunk/y4oi/y4oi.d
Log:
Import yuv4ogg interchange tool
Added: trunk/y4oi/Makefile
===================================================================
--- trunk/y4oi/Makefile (rev 0)
+++ trunk/y4oi/Makefile 2009-08-12 03:48:28 UTC (rev 16466)
@@ -0,0 +1,53 @@
+# Fuck Automake
+# Fuck the horse it rode in on
+# and Fuck its little dog Libtool too
+
+TARGETS = y4oi
+CC = gcc
+LD = gcc
+export INSTALL = install
+STRIP = strip
+PREFIX = /usr/local
+BINDIR = $(PREFIX)/bin
+ETCDIR = /etc/$(TARGET)
+MANDIR = $(PREFIX)/man
+
+SRC = y4oi.c
+OBJ = y4oi.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)'
+ $(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)'
+
+clean:
+ rm -f $(OBJ) *.d *.d.* gmon.out $(TARGET)
+
+distclean: clean
+ rm -f *~
+
+%.d: %.c
+ $(CC) -M $(CFLAGS) $< > $@.$$$$; sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; rm -f $@.$$$$
+
+ifeq ($(MAKECMDGOALS),target)
+include $(SRC:.c=.d)
+endif
+
+ifeq ($(MAKECMDGOALS),static-target)
+include $(SRC:.c=.d)
+endif
+
+target: $(OBJ)
+ $(LD) y4oi.o $(CFLAGS) -o y4oi $(LDF)
+
+install: target
+ $(INSTALL) -d -m 0755 $(BINDIR)
+ $(INSTALL) -m 0755 $(TARGETS) $(BINDIR)
+
Added: trunk/y4oi/y4oi
===================================================================
(Binary files differ)
Property changes on: trunk/y4oi/y4oi
___________________________________________________________________
Added: svn:executable
+
Added: svn:mime-type
+ application/octet-stream
Added: trunk/y4oi/y4oi.c
===================================================================
--- trunk/y4oi/y4oi.c (rev 0)
+++ trunk/y4oi/y4oi.c 2009-08-12 03:48:28 UTC (rev 16466)
@@ -0,0 +1,2123 @@
+/*
+ *
+ * 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 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,length,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.
+// bound 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:sSv";
+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'},
+ {"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"
+ " -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 and PTS rewrite\n"
+ " even on unsynced 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=p;
+ }
+ 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);
+
+ /* 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 = (force_no_sync?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 '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(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)){
+ fprintf(stderr,"break\n");
+ break;
+ }
+ }else if (outoffsets[sno]>0){
+ trim_from_tail(ty,sno,outclock,outoffsets);
+ }else{
+ if(limited_prime(ty,sno)){
+ fprintf(stderr,"break\n");
+ break;
+ }
+ if(search_offset(ty,sync_stream,sno,outclock,outoffsets,lastframe)){
+ fprintf(stderr,"break\n");
+ 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.d
===================================================================
--- trunk/y4oi/y4oi.d (rev 0)
+++ trunk/y4oi/y4oi.d 2009-08-12 03:48:28 UTC (rev 16466)
@@ -0,0 +1,29 @@
+y4oi.o y4oi.d : y4oi.c /usr/include/unistd.h /usr/include/features.h \
+ /usr/include/sys/cdefs.h /usr/include/bits/wordsize.h \
+ /usr/include/gnu/stubs.h /usr/include/gnu/stubs-64.h \
+ /usr/include/bits/posix_opt.h /usr/include/bits/types.h \
+ /usr/include/bits/typesizes.h \
+ /usr/lib/gcc/x86_64-linux-gnu/4.3.2/include/stddef.h \
+ /usr/include/bits/confname.h /usr/include/getopt.h \
+ /usr/include/string.h /usr/include/bits/string.h \
+ /usr/include/bits/string2.h /usr/include/endian.h \
+ /usr/include/bits/endian.h /usr/include/bits/byteswap.h \
+ /usr/include/stdlib.h /usr/include/sys/types.h /usr/include/time.h \
+ /usr/include/sys/select.h /usr/include/bits/select.h \
+ /usr/include/bits/sigset.h /usr/include/bits/time.h \
+ /usr/include/sys/sysmacros.h /usr/include/bits/pthreadtypes.h \
+ /usr/include/alloca.h /usr/include/stdio.h /usr/include/libio.h \
+ /usr/include/_G_config.h /usr/include/wchar.h \
+ /usr/lib/gcc/x86_64-linux-gnu/4.3.2/include/stdarg.h \
+ /usr/include/bits/stdio_lim.h /usr/include/bits/sys_errlist.h \
+ /usr/include/bits/stdio.h /usr/include/errno.h \
+ /usr/include/bits/errno.h /usr/include/linux/errno.h \
+ /usr/include/asm/errno.h /usr/include/asm-generic/errno.h \
+ /usr/include/asm-generic/errno-base.h /usr/include/math.h \
+ /usr/include/bits/huge_val.h /usr/include/bits/mathdef.h \
+ /usr/include/bits/mathcalls.h /usr/include/bits/mathinline.h \
+ /usr/lib/gcc/x86_64-linux-gnu/4.3.2/include-fixed/limits.h \
+ /usr/lib/gcc/x86_64-linux-gnu/4.3.2/include-fixed/syslimits.h \
+ /usr/include/limits.h /usr/include/bits/posix1_lim.h \
+ /usr/include/bits/local_lim.h /usr/include/linux/limits.h \
+ /usr/include/bits/posix2_lim.h
More information about the commits
mailing list