/* This file is in the public domain. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "mp4.h" #define AUDIODEV "/dev/sound" extern const char *__progname; typedef struct aq AQ; typedef struct vq VQ; typedef struct aqe AQE; typedef struct vqe VQE; 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 w; int h; unsigned char (*data)[3]; double when; } ; static int argc; static char **argv; static MP4 *curfile; static int nifiles = 0; static char **ifiles = 0; static char *displayname = 0; static char *bordercstr = 0; static char *borderwstr = 0; static char *visualstr = 0; static char *xname = 0; static char *iconname = 0; static char *resname = 0; static char *resclass = 0; static char *xrmstr = 0; static int synch = 0; static char *gammastr = 0; static Display *disp; static Screen *scr; static int scrno; static int width; static int height; static Window rootwin; static Colormap defcmap; 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 Window topwin; static Window dispwin; static Colormap wincmap; static int prev_w; static int prev_h; static XrmDatabase db; 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 char *strdup0(const char *s) { return(s?strdup(s):0); } static void setstr(char **vp, const char *s) { free(*vp); *vp = strdup0(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;i 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,"-bordercolor") || !strcmp(*av,"-bd")) { WANTARG(); setstr(&bordercstr,av[skip]); continue; } if (!strcmp(*av,"-borderwidth") || !strcmp(*av,"-bw")) { WANTARG(); setstr(&borderwstr,av[skip]); continue; } if (!strcmp(*av,"-visual")) { WANTARG(); visualstr = av[skip]; continue; } if (!strcmp(*av,"-name")) { WANTARG(); setstr(&xname,av[skip]); continue; } if (!strcmp(*av,"-iconname")) { WANTARG(); setstr(&iconname,av[skip]); continue; } if (!strcmp(*av,"-resname")) { WANTARG(); resname = av[skip]; continue; } if (!strcmp(*av,"-resclass")) { WANTARG(); resclass = av[skip]; continue; } if (!strcmp(*av,"-xrm")) { WANTARG(); strappend(&xrmstr,"\n"); strappend(&xrmstr,av[skip]); continue; } if (!strcmp(*av,"-sync")) { synch = 1; continue; } if (!strcmp(*av,"-gamma")) { WANTARG(); setstr(&gammastr,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; int class; const char *classname; 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 != class) { fprintf(stderr,"%s: visual %s isn't %s\n",__progname,visualstr,classname); 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); defcmap = XDefaultColormapOfScreen(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 = (width - imgw) / 2; y = (height - imgh) / 2; 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.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.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,topwin,w,h); chg.width = w; chg.height = h; XReconfigureWMWindow(disp,dispwin,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 play_file(const char *fn) { int i; int ax; int vx; MP4TRK *a; MP4TRK *v; XImage *img; audio_info_t ai; int sample_bytes; AQ aq; VQ vq; int started; struct timeval start; struct timeval ntv; int audio_wthresh; printf("playing %s\n",fn); curfile = mp4_open(fn,0); mp4_scan(curfile); ax = -1; vx = -1; for (i=mp4_ntracks(curfile)-1;i>=0;i--) { switch (mp4_track_type(curfile,i)) { default: abort(); break; case MP4_TT_AUDIO: ax = i; break; case MP4_TT_VIDEO: vx = i; break; } } if (vx < 0) printf("*** no video found\n"); if (ax < 0) printf("*** no video found\n"); if ((vx < 0) || (ax < 0)) exit(1); a = mp4_open_audio(curfile,ax); v = mp4_open_video(curfile,vx); aq.head = 0; aq.tail = 0; vq.head = 0; vq.tail = 0; prev_w = -1; prev_h = -1; prev_rate = -1; prev_chan = -1; img = 0; started = 0; while (1) { int deca; int decv; int usea; int usev; double t; int n; fflush(0); if (!a && !v && !aq.head && !vq.head) break; deca = (a && (!aq.head || (aq.tail->when-aq.head->when < 1))); decv = (v && (!vq.head || (vq.tail->when-vq.head->when < 1))); if (started) { gettimeofday(&ntv,0); ntv = tvsub(ntv,start); t = ntv.tv_sec + (ntv.tv_usec * 1e-6); usev = (vq.head && (vq.head->when < t)); if (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; } } else { if (!deca && !decv) { started = 1; gettimeofday(&start,0); 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(a); if (ad->samples) { if (prev_rate < 0) { 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_wthresh = (sample_bytes * prev_rate) / 2; } 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; e->flink = 0; e->blink = aq.tail; if (aq.tail) aq.tail->flink = e; else aq.head = e; aq.tail = e; } else { mp4_close_track(a); 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(v); if (vd->data) { nb = vd->w * vd->h * 4; e = malloc(sizeof(VQE)+nb); e->w = vd->w; e->h = vd->h; e->data = (void *)(e+1); bcopy(vd->data,e->data,nb); e->when = vd->when; e->flink = 0; e->blink = vq.tail; if (vq.tail) vq.tail->flink = e; else vq.head = e; vq.tail = e; } else { mp4_close_track(v); v = 0; } } if (usea) { AQE *e; e = aq.head; aq.head = e->flink; if (aq.head) aq.head->blink = 0; else aq.tail = 0; write(audiofd,e->data,e->len); free(e); } if (usev) { VQE *e; e = vq.head; vq.head = e->flink; if (vq.head) vq.head->blink = 0; else vq.tail = 0; if ((e->w != prev_w) || (e->h != prev_h)) { if (prev_w < 0) { create_X_windows(e->w,e->h); } resize_X_window(e->w,e->h); prev_w = e->w; prev_h = e->h; if (img) XDestroyImage(img); img = XCreateImage(disp,visinfo.visual,24,ZPixmap,0,0,prev_w,prev_h,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_w * 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_w*prev_h*4); } bcopy(e->data,img->data,prev_w*prev_h*4); XPutImage(disp,dispwin,gc,img,0,0,0,0,prev_w,prev_h); XFlush(disp); free(e); } } mp4_close(curfile); ioctl(audiofd,AUDIO_DRAIN,0); } static void play_ifiles(void) { int i; for (i=0;i