/* This file is in the public domain. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "mp4.h" #define AUDIODEV "/dev/sound" /* * AUDIO_DELAY is in seconds. It needs to be low enough that it * doesn't exceed the kernel's willingness to buffer audio data; it * needs to be high enough that no video frame decode takes long * enough for the data queued in the kernel to completely drain. */ #define AUDIO_DELAY .5 extern const char *__progname; typedef enum { STEREO_NONE = 1, STEREO_RED_GREEN, STEREO_GREEN_RED, } STEREOKIND; typedef struct aq AQ; typedef struct vq VQ; typedef struct aqe AQE; typedef struct vqe VQE; typedef struct playstate PLAYSTATE; typedef struct strlist STRLIST; struct strlist { STRLIST *link; char *str; } ; struct aq { AQE *head; AQE *tail; } ; struct vq { VQE *head; VQE *tail; } ; struct aqe { AQE *flink; AQE *blink; int len; unsigned char *data; double when; } ; struct vqe { VQE *flink; VQE *blink; int dw; int dh; int ow; int oh; unsigned char (*data)[4]; double when; } ; struct playstate { int ax; int vx; const char *curname; MP4 *curfile; MP4TRK *a; MP4TRK *v; AQ aq; VQ vq; int started; int paused; int zf; // +ve -> magnify, -ve -> shrink struct timeval start; struct timeval pausetime; double timebase; double vidrate; double maxt; double dB; double vol; unsigned char (*bar)[4]; int barw; unsigned char *cmdbuf; int cmdba; int cmdbl; } ; static int argc; static char **argv; static int nifiles = 0; static char **ifiles = 0; static char *geometryspec = 0; static char *displayname = 0; static char *visualstr = 0; static char *xrmstr = 0; static int synch = 0; static STRLIST *stuff = 0; static STRLIST **stufftail; static Display *disp; static Screen *scr; static int scrno; static int width; static int height; static Window rootwin; static GC gc; static XVisualInfo visinfo; static int (*preverr)(Display *, XErrorEvent *); static int (*prevIOerr)(Display *); static int audiofd; static int prev_rate; static int prev_chan; static MP4_AFMT prev_fmt; static int sample_bytes; static int audio_Bps; static int audio_wthresh; static Window topwin; static Window dispwin; static Colormap wincmap; static int prev_dw; static int prev_dh; static int prev_ow; static int prev_oh; static XImage *img; static XrmDatabase db; static STEREOKIND stereo = STEREO_NONE; #define Cisspace(x) isspace((unsigned char)(x)) static void *deconst_(int dummy, ...) { va_list ap; void *rv; va_start(ap,dummy); rv = va_arg(ap,void *); va_end(ap); return(rv); } static void *deconst(const volatile void *s) { return(deconst_(0,s)); } static void strappend(char **strp, const char *str) { char *new; int oldl; int sl; if (! *strp) { *strp = malloc(1); **strp = '\0'; } oldl = strlen(*strp); sl = strlen(str); new = malloc(oldl+sl+1); bcopy(*strp,new,oldl); bcopy(str,new+oldl,sl); new[oldl+sl] = '\0'; free(*strp); *strp = new; } static void saveargv(int ac, char **av) { int i; int nc; char *abuf; argc = ac; argv = malloc((ac+1)*sizeof(char *)); nc = 1; for (i=0;istr = strdup(s); if (stuff) *stufftail = sl; else stuff = sl; stufftail = &sl->link; } static void handleargs(int ac, char **av) { int skip; int errs; skip = 0; errs = 0; for (;ac;ac--,av++) { if (skip > 0) { skip --; continue; } if (**av != '-') { ifiles = realloc(ifiles,(nifiles+1)*sizeof(*ifiles)); ifiles[nifiles] = *av; nifiles ++; continue; } if (0) { needarg:; fprintf(stderr,"%s: %s needs a following argument\n",__progname,*av); errs ++; continue; } #define WANTARG() do { if (++skip >= ac) goto needarg; } while (0) if (!strcmp(*av,"-display")) { WANTARG(); displayname = av[skip]; continue; } if (!strcmp(*av,"-geometry")) { WANTARG(); geometryspec = av[skip]; continue; } if (!strcmp(*av,"-visual")) { WANTARG(); visualstr = av[skip]; continue; } if (!strcmp(*av,"-xrm")) { WANTARG(); strappend(&xrmstr,"\n"); strappend(&xrmstr,av[skip]); continue; } if (!strcmp(*av,"-rg") || !strcmp(*av,"-redgreen")) { stereo = STEREO_RED_GREEN; continue; } if (!strcmp(*av,"-gr") || !strcmp(*av,"-greenred")) { stereo = STEREO_GREEN_RED; continue; } if (!strcmp(*av,"-sync")) { synch = 1; continue; } if (!strcmp(*av,"-stuff")) { WANTARG(); append_stuff(av[skip]); continue; } #undef WANTARG fprintf(stderr,"%s: unrecognized option `%s'\n",__progname,*av); errs ++; } if (errs) exit(1); } static Display *open_display(const char *disp) { Display *rv; rv = XOpenDisplay(disp); if (rv == 0) { fprintf(stderr,"%s: can't open display %s\n",__progname,XDisplayName(disp)); exit(1); } return(rv); } static int err(Display *d, XErrorEvent *ee) { return((*preverr)(d,ee)); } static int ioerr(Display *d) { return((*prevIOerr)(d)); } static void setup_visual(void) { XVisualInfo *xvi; int nvi; XVisualInfo template; if (visualstr) { unsigned long int id; char *cp; id = strtol(visualstr,&cp,0); if (*cp) { fprintf(stderr,"%s: %s: invalid visual option\n",__progname,visualstr); exit(1); } template.visualid = (VisualID) id; xvi = XGetVisualInfo(disp,VisualIDMask,&template,&nvi); if (xvi == 0) { fprintf(stderr,"%s: no such visual found\n",__progname); exit(1); } if (nvi == 0) { fprintf(stderr,"%s: what? XGetVisualInfo returned non-nil but zero count?\n",__progname); exit(1); } visinfo = xvi[0]; if (visinfo.class != TrueColor) { fprintf(stderr,"%s: visual %s isn't TrueColor\n",__progname,visualstr); exit(1); } if (visinfo.screen != XDefaultScreen(disp)) { fprintf(stderr,"%s: warning: visual %s is on screen %d\n",__progname,visualstr,(int)visinfo.screen); } } else { int best_onscr; int best; int i; template.class = TrueColor; xvi = XGetVisualInfo(disp,VisualClassMask,&template,&nvi); best = -1; best_onscr = -1; for (i=0;i xvi[best_onscr].depth) || ( (xvi[i].depth == xvi[best_onscr].depth) && (xvi[i].visual == XDefaultVisual(disp,xvi[i].screen)) ) ) { best_onscr = i; } } if ( (best < 0) || (xvi[i].depth > xvi[best].depth) || ( (xvi[i].depth == xvi[best].depth) && (xvi[i].visual == XDefaultVisual(disp,xvi[i].screen)) ) ) { best = i; } } if (best < 0) { fprintf(stderr,"%s: no TrueColor visual available\n",__progname); exit(1); } if (best_onscr < 0) { fprintf(stderr,"%s: no TrueColor visual on screen %d, using screen %d\n",__progname,XDefaultScreen(disp),xvi[best].screen); } else { best = best_onscr; } visinfo = xvi[best]; } } static void setup_db(void) { char *str; XrmDatabase db2; db = XrmGetStringDatabase(""); str = XResourceManagerString(disp); if (str) { db2 = XrmGetStringDatabase(str); XrmMergeDatabases(db2,&db); } #if defined(XlibSpecificationRelease) && (XlibSpecificationRelease >= 5) str = XScreenResourceString(scr); if (str) { db2 = XrmGetStringDatabase(str); XrmMergeDatabases(db2,&db); } #endif if (xrmstr) { db2 = XrmGetStringDatabase(xrmstr); XrmMergeDatabases(db2,&db); } } static void setup_X(void) { XrmInitialize(); disp = open_display(displayname); if (synch) XSynchronize(disp,True); preverr = XSetErrorHandler(err); prevIOerr = XSetIOErrorHandler(ioerr); setup_visual(); scr = XScreenOfDisplay(disp,visinfo.screen); scrno = visinfo.screen; width = XWidthOfScreen(scr); height = XHeightOfScreen(scr); rootwin = XRootWindowOfScreen(scr); setup_db(); wincmap = XCreateColormap(disp,rootwin,visinfo.visual,AllocNone); } static void setup_sound(void) { audiofd = open(AUDIODEV,O_WRONLY,0); if (audiofd < 0) { fprintf(stderr,"%s: %s: %s\n",__progname,AUDIODEV,strerror(errno)); exit(1); } } static void create_X_windows(int imgw, int imgh) { int x; int y; int w; int h; int bits; unsigned long int attrmask; XSetWindowAttributes attr; XTextProperty wn_prop; XTextProperty in_prop; XWMHints *wm_hints; XSizeHints *normal_hints; XClassHint *class_hints; wm_hints = XAllocWMHints(); normal_hints = XAllocSizeHints(); class_hints = XAllocClassHint(); w = imgw; h = imgh; x = 0; // (width - imgw) / 2; y = 0; // (height - imgh) / 2; bits = XParseGeometry(geometryspec,&x,&y,&w,&h); if (bits & XNegative) x = width + x - w; if (bits & YNegative) y = height + y - h; wm_hints->flags = InputHint; wm_hints->input = False; normal_hints->min_width = 1; normal_hints->min_height = 1; normal_hints->width_inc = 1; normal_hints->height_inc = 1; normal_hints->base_width = imgw; normal_hints->base_height = imgh; normal_hints->flags = PMinSize | PBaseSize | PResizeInc; class_hints->res_name = deconst("play"); class_hints->res_class = deconst("Play"); normal_hints->x = x; normal_hints->y = y; normal_hints->width = w; normal_hints->height = h; if (bits & (XValue|YValue)) normal_hints->flags |= USPosition; normal_hints->flags |= (bits & (WidthValue|HeightValue)) ? USSize : PSize; attrmask = 0; attr.border_pixel = 0; attrmask |= CWBorderPixel; attr.background_pixmap = None; attrmask |= CWBackPixmap; attr.backing_store = NotUseful; attrmask |= CWBackingStore; attr.event_mask = StructureNotifyMask | ButtonPressMask; attrmask |= CWEventMask; attr.colormap = wincmap; attrmask |= CWColormap; topwin = XCreateWindow(disp,rootwin,x,y,w,h,0,visinfo.depth,InputOutput,visinfo.visual,attrmask,&attr); wn_prop.value = deconst("xplaymp4"); wn_prop.encoding = XA_STRING; wn_prop.format = 8; wn_prop.nitems = strlen((char *)wn_prop.value); in_prop.value = deconst("xplaymp4"); in_prop.encoding = XA_STRING; in_prop.format = 8; in_prop.nitems = strlen((char *)in_prop.value); XSetWMProperties(disp,topwin,&wn_prop,&in_prop,argv,argc,normal_hints,wm_hints,class_hints); gc = XCreateGC(disp,topwin,0,0); attrmask = 0; attr.background_pixmap = None; attrmask |= CWBackPixmap; attr.backing_store = NotUseful; attrmask |= CWBackingStore; attr.event_mask = ExposureMask; attrmask |= CWEventMask; attr.colormap = wincmap; attrmask |= CWColormap; dispwin = XCreateWindow(disp,topwin,0,0,w,h,0,visinfo.depth,InputOutput,CopyFromParent,attrmask,&attr); XMapRaised(disp,dispwin); XRaiseWindow(disp,topwin); XMapWindow(disp,topwin); XFree((char *)wm_hints); XFree((char *)normal_hints); XFree((char *)class_hints); } static void resize_X_window(int w, int h) { XWindowChanges chg; XResizeWindow(disp,dispwin,w,h); chg.width = w; chg.height = h; XReconfigureWMWindow(disp,topwin,scrno,CWWidth|CWHeight,&chg); } #if 0 static int compute_shift(unsigned long int m) { int n; if (! m) abort(); m &= ~ (m >> 1); if (m & (m-1)) abort(); n = 0; if (m & 0xffff0000) { m >>= 16; n += 16; } if (m & 0x0000ff00) { m >>= 8; n += 8; } if (m & 0x000000f0) { m >>= 4; n += 4; } if (m & 0x0000000c) { m >>= 2; n += 2; } if (m & 0x00000002) { m >>= 1; n ++; } return(n); } #endif static struct timeval tvsub(struct timeval a, struct timeval b) { if (a.tv_usec >= b.tv_usec) { return((struct timeval){ .tv_sec = a.tv_sec - b.tv_sec, .tv_usec = a.tv_usec - b.tv_usec }); } else { return((struct timeval){ .tv_sec = a.tv_sec - b.tv_sec - 1, .tv_usec = a.tv_usec + 1000000 - b.tv_usec }); } } static void prompt(void) { printf("play> "); } static void init_play(void) { prev_dw = -1; prev_dh = -1; prev_ow = -1; prev_oh = -1; prev_rate = -1; prev_chan = -1; img = 0; } static void zoom_samesize(const unsigned char (*from)[4], unsigned char (*to)[4], int fw, int fh) { int y; int x; switch (stereo) { case STEREO_NONE: bcopy(from,to,fw*fh*4); break; case STEREO_RED_GREEN: for (y=fh;y>0;y--) { for (x=fw;x>0;x--) { to[0][0] = from[0][0]; to[0][1] = from[0][0]; to[0][2] = from[0][0]; to[0][3] = from[0][3]; to[fw][0] = from[0][1]; to[fw][1] = from[0][1]; to[fw][2] = from[0][1]; to[fw][3] = from[0][3]; to ++; from ++; } to += fw; } break; case STEREO_GREEN_RED: for (y=fh;y>0;y--) { for (x=fw;x>0;x--) { to[0][0] = from[0][1]; to[0][1] = from[0][1]; to[0][2] = from[0][1]; to[0][3] = from[0][3]; to[fw][0] = from[0][0]; to[fw][1] = from[0][0]; to[fw][2] = from[0][0]; to[fw][3] = from[0][3]; to ++; from ++; } to += fw; } break; } } static void copy_zoom_magnify(const unsigned char (*from)[4], unsigned char (*to)[4], int fw, int fh, int zf) { if (zf == 1) { zoom_samesize(from,to,fw,fh); } else { int x; int y; int i; int sd; int szf; if (stereo != STEREO_NONE) { sd = fw * zf; szf = 2 * zf; } else { szf = zf; } for (y=fh;y>0;y--) { for (x=fw;x>0;x--) { switch (stereo) { case STEREO_NONE: for (i=zf-1;i>=0;i--) { to[i][0] = from[0][0]; to[i][1] = from[0][1]; to[i][2] = from[0][2]; to[i][3] = from[0][3]; } from ++; to += zf; break; case STEREO_RED_GREEN: for (i=zf-1;i>=0;i--) { to[i][0] = from[0][0]; to[i][1] = from[0][0]; to[i][2] = from[0][0]; to[i][3] = from[0][3]; to[sd+i][0] = from[0][1]; to[sd+i][1] = from[0][1]; to[sd+i][2] = from[0][1]; to[sd+i][3] = from[0][3]; } from ++; to += zf; break; case STEREO_GREEN_RED: for (i=zf-1;i>=0;i--) { to[i][0] = from[0][1]; to[i][1] = from[0][1]; to[i][2] = from[0][1]; to[i][3] = from[0][3]; to[sd+i][0] = from[0][0]; to[sd+i][1] = from[0][0]; to[sd+i][2] = from[0][0]; to[sd+i][3] = from[0][3]; } from ++; to += zf; break; } } if (stereo != STEREO_NONE) { to += sd; } for (i=zf-2;i>=0;i--) { bcopy(to-(fw*szf),to,fw*szf*sizeof(*to)); to += fw * szf; } } } } static void copy_zoom_shrink(const unsigned char (*from)[4], unsigned char (*to)[4], int fw, int fh, int zf) { if (zf == 1) { zoom_samesize(from,to,fw,fh); } else { int x; int y; int xx; int yy; int tw; int th; int dv; const unsigned char (*fp)[4]; const unsigned char (*frp)[4]; static unsigned int (*sv)[4] = 0; static int svn = 0; unsigned int (*svp)[4]; unsigned char v; dv = zf * zf; tw = fw / zf; th = fh / zf; if (tw > svn) { free(sv); svn = tw; sv = malloc(svn*sizeof(*sv)); } frp = from; for (y=th;y>0;y--) { bzero(&sv[0][0],tw*4*sizeof(unsigned int)); for (yy=zf;yy>0;yy--) { svp = sv; fp = frp; for (x=tw;x>0;x--) { for (xx=zf;xx>0;xx--) { svp[0][0] += fp[0][0]; svp[0][1] += fp[0][1]; svp[0][2] += fp[0][2]; svp[0][3] += fp[0][3]; fp ++; } svp ++; } frp += fw; } svp = sv; for (x=tw;x>0;x--) { switch (stereo) { case STEREO_NONE: to[0][0] = svp[0][0] / dv; to[0][1] = svp[0][1] / dv; to[0][2] = svp[0][2] / dv; to[0][3] = svp[0][3] / dv; break; case STEREO_RED_GREEN: v = svp[0][0] / dv; to[0][0] = v; to[0][1] = v; to[0][2] = v; to[0][3] = svp[0][3] / dv; v = svp[0][1] / dv; to[tw][0] = v; to[tw][1] = v; to[tw][2] = v; to[tw][3] = to[0][3]; break; case STEREO_GREEN_RED: v = svp[0][1] / dv; to[0][0] = v; to[0][1] = v; to[0][2] = v; to[0][3] = svp[0][3] / dv; v = svp[0][0] / dv; to[tw][0] = v; to[tw][1] = v; to[tw][2] = v; to[tw][3] = to[0][3]; break; } to ++; svp ++; } if (stereo != STEREO_NONE) { to += tw; } } } } static void playstate_init(PLAYSTATE *ps) { ps->aq.head = 0; ps->aq.tail = 0; ps->vq.head = 0; ps->vq.tail = 0; ps->started = 0; ps->paused = 0; ps->zf = 1; ps->timebase = 0; ps->vidrate = 1; ps->maxt = 0; ps->dB = 0; ps->vol = 1; ps->bar = 0; ps->barw = 0; ps->cmdbuf = 0; ps->cmdba = 0; ps->cmdbl = 0; } static void print_help(void) { printf("? print this help\n"); printf("p pause play\n"); printf("go unpause play\n"); printf(". print current state\n"); printf("zoom toggle x2 zoom\n"); printf("vol N set volume to N dB\n"); printf("seek T set to time T in the current file\n"); } static void print_state(PLAYSTATE *ps) { double maxt; maxt = mp4_index_time(ps->v,mp4_max_index(ps->v)) * ps->vidrate; printf("%s\n%dx%d (%dx%d), time %g of %g (%.3f%%)\n", ps->curname, prev_ow, prev_oh, prev_dw, prev_dh, ps->vq.head->when, maxt, (ps->vq.head->when*100)/maxt ); } static void pause_play(PLAYSTATE *ps) { if (ps->paused) { printf("Already paused.\n"); } else { audio_info_t ai; AUDIO_INITINFO(&ai); ai.play.pause = 1; ioctl(audiofd,AUDIO_SETINFO,&ai); ps->paused = 1; gettimeofday(&ps->pausetime,0); printf("Paused.\n"); } } static void unpause_play(PLAYSTATE *ps) { if (ps->paused) { audio_info_t ai; struct timeval tv; AUDIO_INITINFO(&ai); ai.play.pause = 0; ioctl(audiofd,AUDIO_SETINFO,&ai); ps->paused = 0; gettimeofday(&tv,0); if (tv.tv_usec >= ps->pausetime.tv_usec) { tv.tv_usec -= ps->pausetime.tv_usec; tv.tv_sec -= ps->pausetime.tv_sec; } else { tv.tv_usec += 1000000 - ps->pausetime.tv_usec; tv.tv_sec -= ps->pausetime.tv_sec + 1; } ps->start.tv_usec += tv.tv_usec; ps->start.tv_sec += tv.tv_sec; if (ps->start.tv_usec > 1000000) { ps->start.tv_usec -= 1000000; ps->start.tv_sec ++; } printf("Resumed.\n"); } else { printf("Not paused.\n"); } } static void set_volume(PLAYSTATE *ps, double dB) { ps->dB = dB; ps->vol = pow(2,dB/10); } static double seek_track(MP4TRK *trk, double t) { int l; int h; int m; l = -1; h = mp4_max_seekx(trk) + 1; while (h-l > 1) { m = (h + l) / 2; if (mp4_seekx_time(trk,m) < t) l = m; else h = m; } if (l < 0) l = 0; mp4_seek(trk,l); return(mp4_seekx_time(trk,l)); } static void free_aqe(AQE *e) { free(e); } static void free_vqe(VQE *e) { free(e->data); free(e); } static void seek_play(PLAYSTATE *ps, double t) { double oldt; double newt; int us; struct timeval tv; oldt = ps->vq.head->when; newt = seek_track(ps->v,t/ps->vidrate) * ps->vidrate; seek_track(ps->a,newt); printf("%g -> %g\n",oldt,newt); if (newt > oldt) { newt -= oldt; tv.tv_sec = newt; us = 1e6 * (newt - tv.tv_sec); if (us < 0) { us += 1000000; tv.tv_sec --; } else if (us > 1000000) { us -= 1000000; tv.tv_sec ++; } if (us > ps->start.tv_usec) { tv.tv_usec = ps->start.tv_usec + 1000000 - us; tv.tv_sec = ps->start.tv_sec - tv.tv_sec - 1; } else { tv.tv_usec = ps->start.tv_usec - us; tv.tv_sec = ps->start.tv_sec - tv.tv_sec; } } else { oldt -= newt; tv.tv_sec = oldt; us = ps->start.tv_usec + (int) (1e6 * (oldt - tv.tv_sec)); tv.tv_sec += ps->start.tv_sec; if (us < 0) { us += 1000000; tv.tv_sec --; } while (us >= 1000000) { us -= 1000000; tv.tv_sec ++; } tv.tv_usec = us; } /* printf("start changing from %lu.%06lu to %lu.%06lu\n", (unsigned long int) ps->start.tv_sec, (unsigned long int) ps->start.tv_usec, (unsigned long int) tv.tv_sec, (unsigned long int) tv.tv_usec ); */ ps->start = tv; while (ps->aq.head) { AQE *e; e = ps->aq.head; ps->aq.head = e->flink; free_aqe(e); } ps->aq.tail = 0; while (ps->vq.head) { VQE *e; e = ps->vq.head; ps->vq.head = e->flink; free_vqe(e); } ps->vq.tail = 0; } static void set_zoom(PLAYSTATE *ps, int z) { if (z == 0) z = 1; ps->zf = z; } static void set_vidrate(PLAYSTATE *ps, double z) { ps->vidrate = 1 + (z * 1e-6); } static int parsenumber(const char *s, int l, int *vp) { int v; v = 0; for (;l>0;l--) { switch (*s++) { case '0': v = v * 10 ; break; case '1': v = (v * 10) + 1; break; case '2': v = (v * 10) + 2; break; case '3': v = (v * 10) + 3; break; case '4': v = (v * 10) + 4; break; case '5': v = (v * 10) + 5; break; case '6': v = (v * 10) + 6; break; case '7': v = (v * 10) + 7; break; case '8': v = (v * 10) + 8; break; case '9': v = (v * 10) + 9; break; default: return(0); break; } } *vp = v; return(1); } static void set_stereo(PLAYSTATE *ps, const char *line, int len) { int i; int i0; (void)ps; for (i=0;(i 1) && (txt[i0] == 'z') && parsenumber(txt+i0+1,i-i0-1,&n)) { set_zoom(ps,n); } else if ((i-i0 > 1) && (txt[i0] == 'z') && (txt[i0+1] == '-') && parsenumber(txt+i0+2,i-i0-2,&n)) { set_zoom(ps,-n); } else { printf("unrecognized command `%.*s'\n",i-i0,&txt[i0]); } } static void cmd_save(PLAYSTATE *ps, const void *data, int len) { if (ps->cmdbl+len > ps->cmdba) { ps->cmdbuf = realloc(ps->cmdbuf,ps->cmdba=ps->cmdbl+len); } bcopy(data,ps->cmdbuf+ps->cmdbl,len); ps->cmdbl += len; } #define CIF_ECHO 0x00000001 #define CIF_PROMPT 0x00000002 static void cmd_input(PLAYSTATE *ps, const void *input, int len, unsigned int flags) { const unsigned char *buf; unsigned char *nl; int bx; void cmdline(const char *line, int linelen) { if (flags & CIF_ECHO) printf("%.*s\n",linelen,line); command_line(ps,line,linelen); } buf = input; bx = 0; while (bx < len) { nl = memchr(&buf[bx],'\n',len-bx); if (nl == 0) { cmd_save(ps,&buf[bx],len-bx); return; } else { if (ps->cmdbl == 0) { cmdline(&buf[bx],nl-&buf[bx]); } else { cmd_save(ps,&buf[bx],nl-&buf[bx]); cmdline(ps->cmdbuf,ps->cmdbl); } ps->cmdbl = 0; bx = (nl+1) - &buf[0]; if (flags & CIF_PROMPT) prompt(); } } } static void cmd_read(PLAYSTATE *ps) { unsigned char ibuf[256]; int n; n = read(0,&ibuf[0],sizeof(ibuf)); if (n < 0) { switch (errno) { case EINTR: case EWOULDBLOCK: return; break; } fprintf(stderr,"%s: stdin read: %s\n",__progname,strerror(errno)); exit(1); } cmd_input(ps,&ibuf[0],n,CIF_PROMPT); } static void handle_X_expose(void) { XEvent ev; XNextEvent(disp,&ev); if ((ev.type == Expose) && (ev.xexpose.window == dispwin)) { XPutImage(disp,dispwin,gc,img,ev.xexpose.x,ev.xexpose.y,ev.xexpose.x,ev.xexpose.y,ev.xexpose.width,ev.xexpose.height); } } static void apply_volume(MP4_AFMT fmt, double vol, void *data, int bytes) { if (vol == 1) return; switch (fmt) { case MP4_AFMT_SLIN16N: { signed short int *dp; int ns; dp = data; for (ns=bytes/2;ns>0;ns--) *dp++ *= vol; } break; default: abort(); break; } } static void compute_scale(PLAYSTATE *ps) { ps->vidrate = ps->a ? mp4_index_time(ps->a,mp4_max_index(ps->a)) / mp4_index_time(ps->v,mp4_max_index(ps->v)) : 1; } #define NUMWIDTH 12 #define NUMHEIGHT 22 static void gen_progress(unsigned char (*data)[4], int w, int h, double at, double of) { int x; int i; unsigned char (*dp)[4]; static const unsigned short int fbits[11][NUMHEIGHT] = { { 0,0,0x070,0x0f8,0x118,0x10c,0x30c,0x30c,0x30c,0x30c,0x30c,0x30c,0x30c,0x30c,0x188,0x1f0,0x0e0,0,0,0,0,0 }, { 0,0,0x020,0x060,0x0e0,0x1e0,0x360,0x060,0x060,0x060,0x060,0x060,0x060,0x060,0x060,0x060,0x3fc,0,0,0,0,0 }, { 0,0,0x1f0,0x3f8,0x61c,0x40c,0x00c,0x00c,0x00c,0x018,0x030,0x060,0x0c0,0x180,0x302,0x7fe,0x7fe,0,0,0,0,0 }, { 0,0,0x0f8,0x1fc,0x20e,0x406,0x006,0x00e,0x07c,0x0fc,0x00e,0x006,0x006,0x406,0x604,0x3f8,0x1f0,0,0,0,0,0 }, { 0,0,0x018,0x038,0x038,0x058,0x058,0x098,0x098,0x118,0x118,0x218,0x3fe,0x7fe,0x018,0x018,0x018,0,0,0,0,0 }, { 0,0,0x0fc,0x0fc,0x100,0x100,0x200,0x3f8,0x31c,0x00e,0x006,0x006,0x006,0x406,0x606,0x30c,0x1f8,0,0,0,0,0 }, { 0,0,0x070,0x0c0,0x180,0x300,0x300,0x600,0x678,0x6fc,0x70e,0x606,0x606,0x606,0x704,0x3f8,0x1f0,0,0,0,0,0 }, { 0,0,0x1fe,0x3fe,0x604,0x004,0x00c,0x008,0x008,0x018,0x010,0x010,0x030,0x020,0x020,0x060,0x040,0,0,0,0,0 }, { 0,0,0x0f0,0x118,0x30c,0x30c,0x30c,0x188,0x0d0,0x060,0x0b0,0x118,0x30c,0x30c,0x30c,0x188,0x0f0,0,0,0,0,0 }, { 0,0,0x0f8,0x11c,0x20e,0x606,0x606,0x606,0x70e,0x3f6,0x1e6,0x006,0x00c,0x00c,0x018,0x070,0x3c0,0,0,0,0,0 }, { 0,0,0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x0c0,0x1e0,0x1e0,0x0c0,0,0,0,0,0 } }; char *tbuf; void number_at(const char *txt, int x0, int y0) { int i; int x; int y; unsigned char (*d)[4]; unsigned char *dc; int cx; d = data + x0 + (y0 * w); for (i=0;txt[i];i++) { switch (tbuf[i]) { case '0': cx = 0; break; case '1': cx = 1; break; case '2': cx = 2; break; case '3': cx = 3; break; case '4': cx = 4; break; case '5': cx = 5; break; case '6': cx = 6; break; case '7': cx = 7; break; case '8': cx = 8; break; case '9': cx = 9; break; case '.': cx = 10; break; default: abort(); break; } for (y=0;y> x)) { dc[0] = 255; dc[1] = 255; dc[2] = 255; dc[3] = 255; } else { dc[0] = 0; dc[1] = 0; dc[2] = 0; dc[3] = 0; } } } } } dp = data + (w * (h - 1)); x = (w * at) / of; if (x < 0) x = 0; else if (x > w) x = w; for (i=0;i 0) && (tbuf[i-1] == '0')) i --; if ((i > 0) && (tbuf[i-1] == '.')) i --; tbuf[i] = '\0'; number_at(tbuf,0,0); free(tbuf); i = asprintf(&tbuf,"%.2f",of); while ((i > 0) && (tbuf[i-1] == '0')) i --; if ((i > 0) && (tbuf[i-1] == '.')) i --; tbuf[i] = '\0'; number_at(tbuf,w-(strlen(tbuf)*NUMWIDTH),0); free(tbuf); } static void stuffed_cmd(PLAYSTATE *ps) { STRLIST *sl; sl = stuff; stuff = sl->link; cmd_input(ps,sl->str,strlen(sl->str),CIF_ECHO|CIF_PROMPT); cmd_input(ps,"\n",1,CIF_ECHO|CIF_PROMPT); free(sl->str); free(sl); } static void play_files(char **fnv, int nfn) { PLAYSTATE ps; int fno; int i; struct timeval ntv; struct timeval nextin; struct pollfd pfd[2]; if (stuff) *stufftail = 0; playstate_init(&ps); prompt(); gettimeofday(&nextin,0); for (fno=0;fno { ps.curname = fnv[fno]; ps.curfile = mp4_open(fnv[fno],0); mp4_scan(ps.curfile); ps.ax = -1; ps.vx = -1; for (i=mp4_ntracks(ps.curfile)-1;i>=0;i--) { switch (mp4_track_type(ps.curfile,i)) { default: abort(); break; case MP4_TT_AUDIO: ps.ax = i; break; case MP4_TT_VIDEO: ps.vx = i; break; } } if (ps.vx < 0) printf("*** no video found\n"); if (ps.ax < 0) printf("*** no audio found\n"); if (ps.vx < 0) break <"nextfile">; ps.a = (ps.ax < 0) ? 0 : mp4_open_audio(ps.curfile,ps.ax); ps.v = mp4_open_video(ps.curfile,ps.vx); compute_scale(&ps); printf("duration %g, vidrate %g\n",mp4_index_time(ps.v,mp4_max_index(ps.v)),ps.vidrate); while (1) { int deca; int decv; int usea; int usev; double t; int n; fflush(0); if (!ps.a && !ps.v && !ps.aq.head && !ps.vq.head) break; deca = (ps.a && (!ps.aq.head || (ps.aq.tail->when-ps.aq.head->when < .25))); decv = (ps.v && (!ps.vq.head || (ps.vq.tail->when-ps.vq.head->when < .25))); if (stuff) { stuffed_cmd(&ps); } else { pfd[0].fd = 0; pfd[0].events = POLLIN | POLLRDNORM; if (ps.paused) { while (XEventsQueued(disp,QueuedAfterFlush) > 0) handle_X_expose(); pfd[1].fd = XConnectionNumber(disp); pfd[1].events = POLLIN | POLLRDNORM; n = poll(&pfd[0],2,INFTIM); if ((n > 0) && pfd[0].revents) cmd_read(&ps); continue; } n = poll(&pfd[0],1,0); if (n > 0) { cmd_read(&ps); continue; } } gettimeofday(&ntv,0); if (ps.started) { struct timeval delta; delta = tvsub(ntv,ps.start); t = delta.tv_sec + (delta.tv_usec * 1e-6); ps.maxt = t; if (ps.aq.head) { /* Doc lies. AUDIO_WSEEK works in bytes, not samples. */ ioctl(audiofd,AUDIO_WSEEK,&n); usea = (n < audio_wthresh) || (prev_rate < 0); } else { usea = 0; } usev = (ps.vq.head && (!ps.aq.head || (ps.vq.head->when+AUDIO_DELAY < ps.aq.head->when))); } else { if (!deca && !decv) { ps.started = 1; ps.start = ntv; continue; } usea = 0; usev = 0; } #ifdef VERBOSE if (started) { printf("Loop, t %g",t); } else { printf("Preload,"); } if (aq.head) { printf(" a %g..%g",aq.head->when,aq.tail->when); } else { printf(" no a"); } if (vq.head) { printf(" v %g..%g",vq.head->when,vq.tail->when); } else { printf(" no v"); } printf(":"); if (deca) printf(" deca"); if (decv) printf(" decv"); if (usea) printf(" usea"); if (usev) printf(" usev"); printf("\n"); #endif if (deca) { const MP4ADESC *ad; AQE *e; int nb; ad = mp4_get_audio(ps.a); if (ad->samples) { if (prev_rate < 0) { audio_info_t ai; AUDIO_INITINFO(&ai); ai.play.sample_rate = ad->rate; prev_rate = ad->rate; ai.play.channels = ad->channels; prev_chan = ad->channels; switch (ad->format) { case MP4_AFMT_SLIN16N: ai.play.precision = 16; ai.play.encoding = AUDIO_ENCODING_SLINEAR; sample_bytes = 2; break; default: abort(); break; } prev_fmt = ad->format; ai.play.pause = 0; ai.blocksize = 0; ai.mode = AUMODE_PLAY_ALL; if (ioctl(audiofd,AUDIO_SETINFO,&ai) < 0) { fprintf(stderr,"%s: AUDIO_SETINFO on %s: %s\n",__progname,AUDIODEV,strerror(errno)); exit(1); } audio_Bps = sample_bytes * prev_chan * prev_rate; audio_wthresh = audio_Bps * AUDIO_DELAY; } if ( (prev_rate != ad->rate) || (prev_chan != ad->channels) || (prev_fmt != ad->format) ) { printf("*** audio format changed\n"); exit(1); } nb = ad->samples * ad->channels * sample_bytes; e = malloc(sizeof(AQE)+nb); e->len = nb; e->data = (void *)(e+1); bcopy(ad->data,e->data,nb); e->when = ad->when + ps.timebase; e->flink = 0; e->blink = ps.aq.tail; if (ps.aq.tail) ps.aq.tail->flink = e; else ps.aq.head = e; ps.aq.tail = e; } else { mp4_close_track(ps.a); ps.a = 0; } } if (!deca && !decv && !usea && !usev) { poll(0,0,10); continue; } if (decv) { const MP4VDESC *vd; VQE *e; int nb; vd = mp4_get_video(ps.v); if (vd->data) { e = malloc(sizeof(VQE)); e->ow = vd->w; e->oh = vd->h; if (ps.zf < 0) { e->dw = vd->w / -ps.zf; if (stereo != STEREO_NONE) e->dw *= 2; e->dh = NUMHEIGHT + (vd->h / -ps.zf) + 1; nb = e->dw * e->dh * 4; e->data = malloc(nb); bzero(e->data,NUMHEIGHT*e->dw*4); copy_zoom_shrink(vd->data,e->data+(NUMHEIGHT*e->dw),vd->w,vd->h,-ps.zf); } else { e->dw = vd->w * ps.zf; if (stereo != STEREO_NONE) e->dw *= 2; e->dh = NUMHEIGHT + (vd->h * ps.zf) + 1; nb = e->dw * e->dh * 4; e->data = malloc(nb); bzero(e->data,NUMHEIGHT*e->dw*4); copy_zoom_magnify(vd->data,e->data+(NUMHEIGHT*e->dw),vd->w,vd->h,ps.zf); } gen_progress(e->data,e->dw,e->dh,vd->when,mp4_index_time(ps.v,mp4_max_index(ps.v))*ps.vidrate); e->when = (vd->when * ps.vidrate) + ps.timebase; e->flink = 0; e->blink = ps.vq.tail; if (ps.vq.tail) ps.vq.tail->flink = e; else ps.vq.head = e; ps.vq.tail = e; } else { mp4_close_track(ps.v); ps.v = 0; } } if (usea) { AQE *e; e = ps.aq.head; ps.aq.head = e->flink; if (ps.aq.head) ps.aq.head->blink = 0; else ps.aq.tail = 0; apply_volume(prev_fmt,ps.vol,e->data,e->len); write(audiofd,e->data,e->len); free_aqe(e); } if (usev) { VQE *e; e = ps.vq.head; ps.vq.head = e->flink; if (ps.vq.head) ps.vq.head->blink = 0; else ps.vq.tail = 0; prev_ow = e->ow; prev_oh = e->oh; if ((e->dw != prev_dw) || (e->dh != prev_dh)) { if (prev_dw < 0) { create_X_windows(e->dw,e->dh); } resize_X_window(e->dw,e->dh); prev_dw = e->dw; prev_dh = e->dh; if (img) XDestroyImage(img); img = XCreateImage(disp,visinfo.visual,24,ZPixmap,0,0,prev_dw,prev_dh,32,0); #ifdef VERBOSE printf("bytes_per_line %d\n",img->bytes_per_line); printf("bits_per_pixel %d\n",img->bits_per_pixel); switch (img->byte_order) { case LSBFirst: printf("byte_order LSBFirst\n"); break; case MSBFirst: printf("byte_order MSBFirst\n"); break; default: printf("byte_order %d\n",(int)img->byte_order); break; } printf("red_mask %08lx\n",img->red_mask); printf("green_mask %08lx\n",img->green_mask); printf("blue_mask %08lx\n",img->blue_mask); #endif img->bytes_per_line = prev_dw * 4; img->bits_per_pixel = 32; /* why do these work?? they don't look right. */ img->byte_order = LSBFirst; img->red_mask = 0x00ff0000; img->green_mask = 0x0000ff00; img->blue_mask = 0x000000ff; img->data = malloc(prev_dw*prev_dh*4); } bcopy(e->data,img->data,prev_dw*prev_dh*4); XPutImage(disp,dispwin,gc,img,0,0,0,0,prev_dw,prev_dh); while (XEventsQueued(disp,QueuedAfterReading) > 0) { XEvent ev; XNextEvent(disp,&ev); } XFlush(disp); free_vqe(e); } } } while (0); mp4_close(ps.curfile); ps.timebase = ps.maxt; } ioctl(audiofd,AUDIO_DRAIN,0); } int main(int, char **); int main(int ac, char **av) { saveargv(ac,av); handleargs(ac-1,av+1); setup_X(); setup_sound(); init_play(); play_files(ifiles,nifiles); return(0); }