/* This file is in the public domain. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "mp4.h" #include "scm-rights.h" #define AUDIODEV "/dev/sound" extern const char *__progname; // We support only the one audio format, so this is a constant. #define BYTES_PER_SAMPLE 2 // Progress values use a fixed font. #define NUMWIDTH 12 #define NUMHEIGHT 22 static const unsigned short int fontbits[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,0,0x020,0x060,0x0e0,0x1e0,0x360,0x060,0x060,0x060,0x060,0x060,0x060,0x060,0x060,0x060,0x3fc,0,0,0,0,0 }, // 1 { 0,0,0x1f0,0x3f8,0x61c,0x40c,0x00c,0x00c,0x00c,0x018,0x030,0x060,0x0c0,0x180,0x302,0x7fe,0x7fe,0,0,0,0,0 }, // 2 { 0,0,0x0f8,0x1fc,0x20e,0x406,0x006,0x00e,0x07c,0x0fc,0x00e,0x006,0x006,0x406,0x604,0x3f8,0x1f0,0,0,0,0,0 }, // 3 { 0,0,0x018,0x038,0x038,0x058,0x058,0x098,0x098,0x118,0x118,0x218,0x3fe,0x7fe,0x018,0x018,0x018,0,0,0,0,0 }, // 4 { 0,0,0x0fc,0x0fc,0x100,0x100,0x200,0x3f8,0x31c,0x00e,0x006,0x006,0x006,0x406,0x606,0x30c,0x1f8,0,0,0,0,0 }, // 5 { 0,0,0x070,0x0c0,0x180,0x300,0x300,0x600,0x678,0x6fc,0x70e,0x606,0x606,0x606,0x704,0x3f8,0x1f0,0,0,0,0,0 }, // 6 { 0,0,0x1fe,0x3fe,0x604,0x004,0x00c,0x008,0x008,0x018,0x010,0x010,0x030,0x020,0x020,0x060,0x040,0,0,0,0,0 }, // 7 { 0,0,0x0f0,0x118,0x30c,0x30c,0x30c,0x188,0x0d0,0x060,0x0b0,0x118,0x30c,0x30c,0x30c,0x188,0x0f0,0,0,0,0,0 }, // 8 { 0,0,0x0f8,0x11c,0x20e,0x606,0x606,0x606,0x70e,0x3f6,0x1e6,0x006,0x00c,0x00c,0x018,0x070,0x3c0,0,0,0,0,0 }, // 9 { 0,0,0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x0c0,0x1e0,0x1e0,0x0c0,0,0,0,0,0 } }; // . typedef unsigned long long int TIME; typedef enum { WT_VIDEO = 1, WT_AUDIO, WT_ZOOM, } WORKERTYPE; typedef struct worker WORKER; typedef struct shmregion SHMREGION; typedef struct audioblock AUDIOBLOCK; typedef struct videoframe VIDEOFRAME; typedef struct refcs REFCS; typedef struct adecbuf ADECBUF; typedef struct vdecbuf VDECBUF; struct adecbuf { unsigned int base; unsigned int len; } ; struct vdecbuf { unsigned int cue; unsigned int frame; double when; } ; struct refcs { char *s; int l; int refcount; } ; struct videoframe { VIDEOFRAME *link; unsigned int cue; unsigned int frameno; double when; } ; struct audioblock { AUDIOBLOCK *link; unsigned int base; unsigned int len; unsigned char *buf; } ; struct shmregion { unsigned int offset; unsigned int size; } ; struct worker { int n; int fd; int fdfd; AIO_OQ oq; int writeid; pid_t pid; int readid; WORKERTYPE type; } ; static int argc; static char **argv; static int nifiles = 0; static char **ifiles = 0; static int parallelism = 1; static char *geometryspec = 0; static char *displayname = 0; static char *visualstr = 0; static char *xrmstr = 0; static int synch = 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 Window topwin; static Window dispwin; static Colormap wincmap; static XImage *img; static XImage *progress_img; static MP4 *mp4; static MP4TRK *at; static MP4TRK *vt; static int n_workers; static WORKER *workers; static int audio_worker; static int zoom_worker; static int video_worker; static unsigned char *shm; static unsigned int shmsize; static const char *name; static unsigned int audio_rate; static unsigned int audio_channels; static MP4_AFMT audio_format; static unsigned int video_w; static unsigned int video_h; static unsigned int video_bpf; static SHMREGION audio_ring; static SHMREGION video_dec; static SHMREGION video_mag; static unsigned int file_playing; #define PLAYING_VREQ 0x00000001 #define PLAYING_VDATA 0x00000002 #define PLAYING_VDRAIN 0x00000004 #define PLAYING_ADATA 0x00000008 #define PLAYING_ADRAIN 0x00000010 static int cur_file; static int play_id; static int devnull; static int worker_fd; static int worker_fdfd; static AIO_OQ worker_oq; static int worker_ioid; static int worker_want; static unsigned char *worker_pend; static int worker_apend; static int worker_npend; static void (*worker_call)(const unsigned char *, int); static int worker_mp4fd; static char *worker_mp4fn; static int worker_mp4fnl; static MP4 *worker_mp4; static int worker_trkx; static MP4TRK *worker_trk; static double audio_maxtime; static double video_maxtime; static VDECBUF video_rbuf; static int video_rbfill; static int video_id; static int vdisp_id; static int last_progress_w; static int progress_need_init; static int audio_id; static ADECBUF audio_rbuf; static int audio_rbfill; static const MP4ADESC *audio_pend; static unsigned int audio_pendsize; static unsigned int audio_ringh; static unsigned int audio_ringt; static unsigned int audio_ringn; static unsigned int debug = 0; #define DEBUG_PARENT 0x00000001 #define DEBUG_VIDEO 0x00000002 #define DEBUG_AUDIO 0x00000004 #define DEBUG_ZOOM 0x00000008 static unsigned int audio_written; static AIO_OQ audio_oq; static int audio_oid; static AUDIOBLOCK *aq; static AUDIOBLOCK **aqtail; static double audio_gain; static unsigned int video_nframes; static unsigned int *video_ffree; static unsigned int video_nfree; static VIDEOFRAME *vq; static VIDEOFRAME **vqtail; //static VIDEOFRAME *zq; //static VIDEOFRAME **zqtail; static int mypid; static int X_id; static int paused; #define VERBOSE_ON() (verbfd >= 0) static int verbfd = -1; static FILE *verbdst; static int verb_nl = 1; static AIO_OQ verb_oq; static int verb_id = AIO_PL_NOID; static char *verbtag = 0; static int verbtagl; static TIME verbdt; static TIME lastverbdt = -(TIME)1; static REFCS *lastverbtext = 0; static TIME starttime; static int stdin_id; static unsigned char *cmdbuf; static int cmdba; static int cmdbl; static unsigned int startwait; #define START_AUDIO 0x00000001 #define START_VIDEO 0x00000002 #define Cisspace(x) isspace((unsigned char)(x)) static void panic(void) { (void)*(volatile char *)0; abort(); } static TIME gettime(void) { struct timeval tv; gettimeofday(&tv,0); return((tv.tv_sec*1000000ULL)+tv.tv_usec); } static void *deconst(const volatile void *arg) { return((char *)0+(((const volatile char *)arg)-(const volatile char *)0)); } static void set_nbio(int fd) { fcntl(fd,F_SETFL,fcntl(fd,F_GETFL,0)|O_NONBLOCK); } 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 256)) { fprintf(stderr,"%s: unreasonable level of parallelism %d\n",__progname,n); return(1); } parallelism = n; return(0); } static void set_debug(const char *s) { for (;*s;s++) { switch (*s) { case 'p': debug |= DEBUG_PARENT; break; case 'a': debug |= DEBUG_AUDIO; break; case 'v': debug |= DEBUG_VIDEO; break; case 'z': debug |= DEBUG_ZOOM; break; case '*': debug = ~0U; break; default: fprintf(stderr,"%s: unrecognized debug key character `%c' ignored\n",__progname,*s); break; } } } static int flush_verbose(void *arg __attribute__((__unused__))) { int nw; while (1) { if (aio_oq_empty(&verb_oq)) return(AIO_BLOCK_NIL); nw = aio_oq_writev(&verb_oq,verbfd,-1); if (nw < 0) { switch (errno) { case EINTR: case EWOULDBLOCK: return(AIO_BLOCK_NIL); break; } fprintf(stderr,"%s: verbosity write: %s\n",__progname,strerror(errno)); exit(1); } aio_oq_dropdata(&verb_oq,nw); } } static void refcs_deref(void *rv) { REFCS *r; if (! rv) return; r = rv; r->refcount --; if (r->refcount > 0) return; if (r->refcount < 0) panic(); free(r->s); free(r); } static int verbdst_wr(void *cookie __attribute__((__unused__)), const char *data, int len) { int o; int o2; const char *nl; if (! verbtag) panic(); o = 0; while (o < len) { if (verb_nl) { if (verbdt != lastverbdt) { refcs_deref(lastverbtext); lastverbtext = malloc(sizeof(REFCS)); lastverbtext->l = asprintf(&lastverbtext->s,"%llu.%06u ",verbdt/1000000ULL,(unsigned int)(verbdt%1000000ULL)); lastverbtext->refcount = 1; } lastverbtext->refcount ++; aio_oq_queue_cb(&verb_oq,lastverbtext->s,lastverbtext->l,&refcs_deref,lastverbtext); aio_oq_queue_point(&verb_oq,verbtag,verbtagl); } nl = memchr(data+o,'\n',len-o); if (nl) { o2 = (nl - data) + 1; aio_oq_queue_copy(&verb_oq,data+o,o2-o); o = o2; verb_nl = 1; } else { aio_oq_queue_copy(&verb_oq,data+o,len-o); o = len; verb_nl = 0; break; } } return(len); } static void verb(const char *, ...) __attribute__((__format__(__printf__,1,2))); static void verb(const char *fmt, ...) { va_list ap; if (verbfd < 0) return; if (verb_id == AIO_PL_NOID) { aio_oq_init(&verb_oq); verb_id = aio_add_block(&flush_verbose,0); verbdst = funopen(0,0,&verbdst_wr,0,0); } verbdt = gettime() - starttime; va_start(ap,fmt); vfprintf(verbdst,fmt,ap); va_end(ap); fflush(verbdst); } static void verb_set_tag(const char *tag) { verbtagl = asprintf(&verbtag,"%s: ",tag); verb("PID %d\n",mypid); } static void set_verbose(const char *dst) { if (verbfd >= 0) close(verbfd); if (dst) { verbfd = open(dst,O_WRONLY|O_APPEND|O_CREAT,0666); if (verbfd < 0) { fprintf(stderr,"%s: can't open %s: %s\n",__progname,dst,strerror(errno)); exit(1); } } else { verbfd = dup(1); } set_nbio(verbfd); } 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 = 1; 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,"-sync")) { synch = 1; continue; } if (!strcmp(*av,"-debug")) { WANTARG(); set_debug(av[skip]); continue; } if (!strcmp(*av,"-j")) { WANTARG(); if (set_parallelism(atoi(av[skip]))) errs = 1; continue; } if (!strcmp(*av,"-v")) { set_verbose(0); continue; } if (!strcmp(*av,"-V")) { WANTARG(); set_verbose(av[skip]); continue; } #undef WANTARG fprintf(stderr,"%s: unrecognized option `%s'\n",__progname,*av); errs = 1; } 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) { #if 0 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); } #endif } static void rd_X(void *arg __attribute__((__unused__))) { while (XEventsQueued(disp,QueuedAfterReading) > 0) { XEvent ev; XNextEvent(disp,&ev); } } 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); X_id = aio_add_poll(XConnectionNumber(disp),&aio_rwtest_always,&aio_rwtest_never,&rd_X,0,0); } 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); } set_nbio(audiofd); } #if 0 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); } #endif #if 0 static void copy_zoom_integer(const unsigned char (*from)[4], unsigned char (*to)[4], int fw, int fh, int zf) { if (zf == 1) { bcopy(from,to,fw*fh*4); } else { int x; int y; int i; for (y=fh;y>0;y--) { for (x=fw;x>0;x--) { 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; } for (i=zf-2;i>=0;i--) { bcopy(to-(zf*fw),to,fw*zf*sizeof(*to)); to += zf * fw; } } } } #endif #if 0 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)); } #endif #if 0 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); } #endif #if 0 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); } #endif static void fd_to_worker(int fd, WORKER *w, const char *fn, int fnl) { struct msghdr mh; struct cmsghdr cmh; struct iovec iov; unsigned char ctlbuf[CMSPACE(sizeof(int))]; unsigned char msg; msg = 0; aio_oq_queue_point(&w->oq,"f",1); aio_oq_queue_copy(&w->oq,&fnl,sizeof(int)); aio_oq_queue_copy(&w->oq,fn,fnl); cmh.cmsg_len = CMLEN(sizeof(int)); cmh.cmsg_level = SOL_SOCKET; cmh.cmsg_type = SCM_RIGHTS; bcopy(&cmh,&ctlbuf[0],sizeof(struct cmsghdr)); bcopy(&fd,&ctlbuf[CMSKIP(&cmh)],sizeof(int)); iov.iov_base = &msg; iov.iov_len = 1; mh.msg_name = 0; mh.msg_namelen = 0; mh.msg_iov = &iov; mh.msg_iovlen = 1; mh.msg_control = (void *) &ctlbuf[0]; mh.msg_controllen = CMLEN(sizeof(int)); mh.msg_flags = 0; if (sendmsg(w->fdfd,&mh,0) < 0) { fprintf(stderr,"%s: sendmsg to worker [%d]: %s\n",__progname,w->n,strerror(errno)); exit(1); } } static void fd_to_workers(int fd, const char *fn) { int l; l = strlen(fn); fd_to_worker(fd,workers+audio_worker,fn,l); fd_to_worker(fd,workers+video_worker,fn,l); } static void divide_shm(void) { audio_ring.offset = 0; audio_ring.size = 65536; video_dec.offset = video_mag.offset + video_mag.size; video_dec.size = ((shmsize - audio_ring.size) / 2) & ~65536U; video_mag.offset = audio_ring.offset + audio_ring.size; video_mag.size = shmsize - audio_ring.size - video_dec.size; video_nframes = video_dec.size / video_bpf; } static void tell_audio(const void *msg, int len) { aio_oq_queue_copy(&workers[audio_worker].oq,msg,len); } static void tell_video(const void *msg, int len) { aio_oq_queue_copy(&workers[video_worker].oq,msg,len); } static void tell_audio_values(void) { unsigned char buf[1+(2*sizeof(unsigned int))+sizeof(MP4_AFMT)]; int x; x = 0; buf[x++] = 'v'; bcopy(&audio_rate,&buf[x],sizeof(unsigned int)); x += sizeof(unsigned int); bcopy(&audio_channels,&buf[x],sizeof(unsigned int)); x += sizeof(unsigned int); bcopy(&audio_format,&buf[x],sizeof(MP4_AFMT)); x += sizeof(MP4_AFMT); tell_audio(&buf[0],sizeof(buf)); } static void tell_audio_start(void) { tell_audio("s",1); tell_audio(&audio_ring,sizeof(SHMREGION)); verb("file_playing |= PLAYING_ADATA; tell_audio_start\n"); file_playing |= PLAYING_ADATA; aio_oq_init(&audio_oq); } static void tell_video_values(void) { tell_video("v",1); tell_video(&video_w,sizeof(video_w)); tell_video(&video_h,sizeof(video_h)); } static void tell_video_start(void) { tell_video("s",1); tell_video(&video_dec,sizeof(video_dec)); verb("file_playing |= PLAYING_VDATA; tell_video_start\n"); file_playing |= PLAYING_VDATA; } static void audio_wroteblock(void *abv) { AUDIOBLOCK *ab; char msg[1+(2*sizeof(unsigned int))]; ab = abv; verb("wrote audio base=%u len=%u\n",ab->base,ab->len); msg[0] = 'b'; bcopy(&ab->base,&msg[1],sizeof(unsigned int)); bcopy(&ab->len,&msg[1+sizeof(unsigned int)],sizeof(unsigned int)); tell_audio(&msg[0],sizeof(msg)); if (ab != aq) panic(); aq = ab->link; if (! aq) aqtail = &aq; free(ab->buf); free(ab); } static void amplify_audio(const void *from, void *to, int bytes) { int i; if (bytes % (audio_channels*BYTES_PER_SAMPLE)) panic(); #if BYTES_PER_SAMPLE != 2 #error "Code assumes BYTES_PER_SAMPLE is 2 #endif for (i=0;ibase,ab->buf,ab->len); } else { amplify_audio(shm+audio_ring.offset+ab->base,ab->buf,ab->len); } } static void recopy_audio(void) { AUDIOBLOCK *ab; for (ab=aq;ab;ab=ab->link) copy_audio_data(ab); } static int audio_gotblock(unsigned int base, unsigned int len) { AUDIOBLOCK *ab; startwait &= ~START_AUDIO; if ((base == ~0U) && (len == ~0U)) return(1); verb("got audio base=%u len=%u\n",base,len); ab = malloc(sizeof(AUDIOBLOCK)); ab->base = base; ab->len = len; ab->buf = malloc(len); copy_audio_data(ab); ab->link = 0; *aqtail = ab; aqtail = &ab->link; aio_oq_queue_cb(&audio_oq,ab->buf,len,&audio_wroteblock,ab); return(0); } static void audio_rd(void *arg __attribute__((__unused__))) { ADECBUF rbuf[64]; struct iovec iov[2]; int niov; int nr; int bx; int done; if (audio_rbfill) { iov[0].iov_base = audio_rbfill + (char *)&audio_rbuf; iov[0].iov_len = sizeof(audio_rbuf) - audio_rbfill; niov = 1; } else { niov = 0; } iov[niov].iov_base = (void *) &rbuf; iov[niov].iov_len = sizeof(rbuf); niov ++; nr = readv(workers[audio_worker].fd,&iov[0],niov); if (nr < 0) { switch (errno) { case EINTR: case EWOULDBLOCK: return; break; } fprintf(stderr,"%s: audio worker read: %s\n",__progname,strerror(errno)); exit(1); } if (nr == 0) { fprintf(stderr,"%s: audio worker read unexpected EOF\n",__progname); exit(1); } if (audio_rbfill) { nr += audio_rbfill; if (nr < sizeof(audio_rbuf)) { audio_rbfill = nr; return; } done = audio_gotblock(audio_rbuf.base,audio_rbuf.len); nr -= sizeof(audio_rbuf); if (done && nr) panic(); } bx = 0; while (nr >= sizeof(ADECBUF)) { done = audio_gotblock(rbuf[bx].base,rbuf[bx].len); bx ++; nr -= sizeof(ADECBUF); if (done & nr) panic(); } if (nr > 0) bcopy(&rbuf[bx],&audio_rbuf,nr); audio_rbfill = nr; if (done) { aio_remove_poll(workers[audio_worker].readid); verb("file_playing |= PLAYING_ADRAIN; file_playing &= ~PLAYING_ADATA; audio_rd\n"); file_playing |= PLAYING_ADRAIN; file_playing &= ~PLAYING_ADATA; } } static void start_audio_reader(void) { audio_rbfill = 0; aq = 0; aqtail = &aq; workers[audio_worker].readid = aio_add_poll(workers[audio_worker].fd,&aio_rwtest_always,&aio_rwtest_never,&audio_rd,0,0); } static int wtest_audio_pad(void *arg __attribute__((__unused__))) { if (paused) return(0); return(1); } static void wr_audio_pad(void *arg __attribute__((__unused__))) { int nw; static const unsigned char zerobuf[8192] = { 0 }; nw = write(audiofd,&zerobuf[0],sizeof(zerobuf)); if (nw < 0) { switch (errno) { case EINTR: case EWOULDBLOCK: return; break; } fprintf(stderr,"%s: write to %s: %s\n",__progname,AUDIODEV,strerror(errno)); exit(1); } verb("wrote audio pad %d\n",nw); audio_written += nw; } static int wtest_audio_out(void *arg __attribute__((__unused__))) { if (paused) return(0); if (aio_oq_empty(&audio_oq)) return(0); return(1); } static void wr_audio(void *arg __attribute__((__unused__))) { int nw; nw = aio_oq_writev(&audio_oq,audiofd,-1); if (nw < 0) { switch (errno) { case EINTR: case EWOULDBLOCK: return; break; } fprintf(stderr,"%s: write to %s: %s\n",__progname,AUDIODEV,strerror(errno)); exit(1); } verb("wrote audio %d\n",nw); audio_written += nw; if (aio_oq_dropdata(&audio_oq,nw)) { if (! (file_playing & PLAYING_ADATA)) { verb("file_playing &= ~PLAYING_ADRAIN; wr_audio\n"); file_playing &= ~PLAYING_ADRAIN; aio_remove_poll(audio_oid); audio_oid = aio_add_poll(audiofd,&aio_rwtest_never,&wtest_audio_pad,0,&wr_audio_pad,0); } } } static void start_audio_writer(void) { audio_oid = aio_add_poll(audiofd,&aio_rwtest_never,&wtest_audio_out,0,&wr_audio,0); } static int video_gotframe(VDECBUF vdb) { VIDEOFRAME *vf; startwait &= ~START_VIDEO; if (vdb.frame == ~0U) return(1); verb("video_gotframe: cue %u frame %u when %g\n",vdb.cue,vdb.frame,vdb.when); if (vdb.frame >= video_nframes) panic(); vf = malloc(sizeof(VIDEOFRAME)); vf->cue = vdb.cue; vf->frameno = vdb.frame; vf->when = vdb.when; vf->link = 0; *vqtail = vf; vqtail = &vf->link; return(0); } static void video_rd(void *arg __attribute__((__unused__))) { VDECBUF rbuf[64]; struct iovec iov[2]; int niov; int nr; int bx; int done; if (video_rbfill) { iov[0].iov_base = video_rbfill + (char *)&video_rbuf; iov[0].iov_len = sizeof(video_rbuf) - video_rbfill; niov = 1; } else { niov = 0; } iov[niov].iov_base = (void *) &rbuf; iov[niov].iov_len = sizeof(rbuf); niov ++; nr = readv(workers[video_worker].fd,&iov[0],niov); if (nr < 0) { switch (errno) { case EINTR: case EWOULDBLOCK: return; break; } fprintf(stderr,"%s: video worker read: %s\n",__progname,strerror(errno)); exit(1); } if (nr == 0) { fprintf(stderr,"%s: video worker read unexpected EOF\n",__progname); exit(1); } if (video_rbfill) { nr += video_rbfill; if (nr < sizeof(video_rbuf)) { video_rbfill = nr; return; } done = video_gotframe(video_rbuf); nr -= sizeof(video_rbuf); if (done && nr) panic(); } bx = 0; while (nr >= sizeof(VDECBUF)) { done = video_gotframe(rbuf[bx]); bx ++; nr -= sizeof(VDECBUF); if (done & nr) panic(); } if (nr > 0) bcopy(&rbuf[bx],&video_rbuf,nr); video_rbfill = nr; if (done) { aio_remove_poll(workers[video_worker].readid); verb("file_playing |= PLAYING_VDRAIN; file_playing &= ~PLAYING_VDATA; video_rd\n"); file_playing |= PLAYING_VDRAIN; file_playing &= ~PLAYING_VDATA; } } static void start_video_reader(void) { video_rbfill = 0; vq = 0; vqtail = &vq; workers[video_worker].readid = aio_add_poll(workers[video_worker].fd,&aio_rwtest_always,&aio_rwtest_never,&video_rd,0,0); } static void gen_number(const char *text, int len, unsigned char *into, int stride) { int y; int x; int fx; unsigned short int row; int i; unsigned char pv; unsigned char *tp; stride *= 4; for (y=0;y> (NUMWIDTH-1-i)) & 1) ? 255 : 0; *tp++ = pv; *tp++ = pv; *tp++ = pv; *tp++ = pv; } } into += stride; } } static void init_progress(void) { unsigned char *dp; char *txt; int w; dp = progress_img->data; bzero(dp,video_w*NUMHEIGHT*4); w = asprintf(&txt,"%.2f",video_maxtime); gen_number(txt,w,dp+((video_w-(NUMWIDTH*w))*4),video_w); last_progress_w = 0; free(txt); } static void gen_progress(double ftime) { unsigned char *dp; char *txt; int w; int i; if (progress_need_init) { init_progress(); progress_need_init = 0; } dp = progress_img->data; w = asprintf(&txt,"%.2f",ftime); gen_number(txt,w,dp,video_w); free(txt); if (w < last_progress_w) { for (i=0;idata = shm + video_dec.offset + (fno * video_bpf); XPutImage(disp,dispwin,gc,img,0,0,0,NUMHEIGHT+2,video_w,video_h); XPutImage(disp,dispwin,gc,progress_img,0,0,0,0,video_w,NUMHEIGHT); XSetForeground(disp,gc,0); XDrawLine(disp,dispwin,gc,0,NUMHEIGHT+1,video_w-1,NUMHEIGHT+1); px = (ftime / video_maxtime) * video_w; XDrawLine(disp,dispwin,gc,px+1,NUMHEIGHT,video_w-1,NUMHEIGHT); XSetForeground(disp,gc,~0UL); XDrawLine(disp,dispwin,gc,0,NUMHEIGHT,px,NUMHEIGHT); XFlush(disp); } static int video_displayer(void *arg __attribute__((__unused__))) { unsigned long bq; double atime; int ms; VIDEOFRAME *vf; if (vq == 0) { if (file_playing & PLAYING_VDRAIN) { verb("file_playing &= ~PLAYING_VDRAIN; video_displayer\n"); file_playing &= ~PLAYING_VDRAIN; aio_remove_block(vdisp_id); } return(AIO_BLOCK_LOOP); } if (ioctl(audiofd,AUDIO_WSEEK,&bq) < 0) { fprintf(stderr,"%s: AUDIO_WSEEK on %s: %s\n",__progname,AUDIODEV,strerror(errno)); exit(1); } atime = (audio_written - bq) / (double) (audio_rate * audio_channels * BYTES_PER_SAMPLE); verb("vq->cue %u when %g atime %g in %u\n",vq->cue,vq->when,atime,vq->frameno); if (vq->when <= atime) { vf = vq; vq = vq->link; if (! vq) vqtail = &vq; verb("displaying cue %u when %g atime %g in %u\n",vf->cue,vf->when,atime,vf->frameno); display_frame(vf->frameno,vf->when); tell_video("b",1); tell_video(&vf->frameno,sizeof(unsigned int)); return(AIO_BLOCK_LOOP); } else { ms = (vq->when - atime) * 1000; if (ms < 0) ms = 0; verb("ms = %d\n",ms); return(ms+10); } } static XImage *puttable_image(int w, int h) { XImage *i; i = XCreateImage(disp,visinfo.visual,24,ZPixmap,0,0,w,h,32,0); i->bytes_per_line = w * 4; i->bits_per_pixel = 32; /* why do these work?? they don't look right. */ i->byte_order = LSBFirst; i->red_mask = 0x00ff0000; i->green_mask = 0x0000ff00; i->blue_mask = 0x000000ff; return(i); } static void create_X_windows(void) { 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 = video_w; h = NUMHEIGHT + video_h + 2; x = 0; y = 0; 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 = video_w; normal_hints->base_height = video_h; 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.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); img = puttable_image(video_w,video_h); progress_img = puttable_image(video_w,NUMHEIGHT); progress_img->data = malloc(video_w*NUMHEIGHT*4); } static void start_video_displayer(void) { create_X_windows(); vdisp_id = aio_add_block(&video_displayer,0); progress_need_init = 1; } static void config_audio(void) { audio_info_t ai; AUDIO_INITINFO(&ai); ai.play.sample_rate = audio_rate; ai.play.channels = audio_channels; ai.play.precision = 16; ai.play.encoding = AUDIO_ENCODING_SLINEAR; 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); } } static void start_playing_file(char *fn) { int fd; int ax; int vx; const MP4ADESC *aframe; const MP4VDESC *vframe; int i; fd = open(fn,O_RDONLY,0); if (fd < 0) { fprintf(stderr,"%s: can't open %s: %s\n",__progname,fn,strerror(errno)); exit(1); } name = fn; mp4 = mp4_open(fd,fn,0); mp4_scan(mp4); ax = -1; vx = -1; for (i=mp4_ntracks(mp4)-1;i>=0;i--) { switch (mp4_track_type(mp4,i)) { default: panic(); 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 audio found\n"); if ((vx < 0) || (ax < 0)) { mp4_close(mp4); close(fd); return; } at = mp4_open_audio(mp4,ax); vt = mp4_open_video(mp4,vx); aframe = mp4_get_audio(at); if (! aframe->samples) printf("*** no audio data available\n"); vframe = mp4_get_video(vt); if (! vframe->data) printf("*** no video data available\n"); audio_rate = aframe->rate; audio_channels = aframe->channels; audio_format = aframe->format; if (audio_format != MP4_AFMT_SLIN16N) { printf("unrecognized audio format (%d)\n",(int)audio_format); exit(1); } config_audio(); video_w = vframe->w; video_h = vframe->h; video_bpf = video_w * video_h * 4; audio_maxtime = mp4_index_time(at,mp4_max_index(at)); video_maxtime = mp4_index_time(vt,mp4_max_index(vt)); printf("Audio rate %u channels %u max time %g\n",audio_rate,audio_channels,audio_maxtime); printf("Video %ux%u max time %g\n",video_w,video_h,video_maxtime); divide_shm(); fd_to_workers(fd,fn); file_playing = 0; tell_audio_values(); tell_audio_start(); start_audio_reader(); start_audio_writer(); tell_video_values(); tell_video_start(); start_video_reader(); #if 0 start_video_zoomer(); #endif start_video_displayer(); audio_written = 0; audio_gain = 1; } static int play_block(void *arg __attribute__((__unused__))) { if (file_playing) return(AIO_BLOCK_NIL); cur_file ++; if (cur_file >= nifiles) exit(0); start_playing_file(ifiles[cur_file]); return(AIO_BLOCK_LOOP); } static void play_files(void) { file_playing = 0; cur_file = -1; paused = 0; play_id = aio_add_block(&play_block,0); } static int worker_wtest(void *arg __attribute__((__unused__))) { return(aio_oq_nonempty(&worker_oq)); } static void worker_wr(void *arg __attribute__((__unused__))) { int nw; nw = aio_oq_writev(&worker_oq,worker_fd,-1); if (nw < 0) { switch (errno) { case EINTR: case EWOULDBLOCK: return; break; } fprintf(stderr,"%s: worker write to master: %s\n",__progname,strerror(errno)); exit(1); } aio_oq_dropdata(&worker_oq,nw); } static void worker_pendroom(int n) { if (n > worker_apend) worker_pend = realloc(worker_pend,worker_apend=n); } static void rd_worker(void *arg __attribute__((__unused__))) { unsigned char rbuf[512]; int nr; int ro; int na; int nw; int want; nr = read(worker_fd,&rbuf,sizeof(rbuf)); if (nr < 0) { switch (errno) { case EINTR: case EWOULDBLOCK: return; break; } fprintf(stderr,"%s: worker read from master: %s\n",__progname,strerror(errno)); exit(1); } if (nr == 0) { fprintf(stderr,"%s: worker read EOF from master\n",__progname); exit(1); } verb("rd_worker read %d, want %d, npend %d\n",nr,worker_want,worker_npend); ro = 0; while (ro < nr) { want = worker_want; verb("rd_worker loop, offset %d of %d, want %d\n",ro,nr,want); na = nr - ro; if (worker_npend > 0) { worker_pendroom(want); nw = want - worker_npend; if (na < nw) { bcopy(&rbuf[ro],worker_pend+worker_npend,na); worker_npend += na; verb("rd_worker didn't fill pending, pend now %d\n",worker_npend); break; } verb("rd_worker filling pending\n"); bcopy(&rbuf[ro],worker_pend+worker_npend,nw); ro += nw; na -= nw; worker_npend = 0; (*worker_call)(worker_pend,want); want = worker_want; verb("rd_worker loop, filled pending and called, now want %d\n",want); } if (na < want) { worker_pendroom(want); bcopy(&rbuf[ro],worker_pend,na); worker_npend = na; verb("rd_worker saving partial %d\n",na); break; } verb("rd_worker calling %d out of read buffer\n",want); (*worker_call)(&rbuf[ro],want); ro += want; } } static void get_mp4_fd(void) { char junk; struct iovec iov; struct cmsghdr cmh; struct msghdr mh; int len; unsigned char ctlbuf[CMSPACE(sizeof(int))]; iov.iov_base = &junk; iov.iov_len = 1; mh.msg_name = 0; mh.msg_namelen = 0; mh.msg_iov = &iov; mh.msg_iovlen = 1; mh.msg_control = (void *)&ctlbuf[0]; mh.msg_controllen = CMSPACE(sizeof(int)); mh.msg_flags = 0; len = recvmsg(worker_fdfd,&mh,0); if (len < 0) { fprintf(stderr,"%s: worker recvmsg receiving fd: %s\n",__progname,strerror(errno)); exit(1); } if (len == 0) { fprintf(stderr,"%s: worker recvmsg receiving fd got EOF\n",__progname); exit(1); } if (len != 1) { fprintf(stderr,"%s: worker recvmsg receiving fd expected %d got %d\n",__progname,1,len); exit(1); } if (mh.msg_controllen < sizeof(struct cmsghdr)) { fprintf(stderr,"%s: worker receiving fd: controllen %d too small\n",__progname,(int)mh.msg_controllen); exit(1); } bcopy(&ctlbuf[0],&cmh,sizeof(struct cmsghdr)); if ((cmh.cmsg_level != SOL_SOCKET) || (cmh.cmsg_type != SCM_RIGHTS)) { fprintf(stderr,"%s: worker receiving fd: level/type wrong\n",__progname); exit(1); } if (cmh.cmsg_len < CMLEN(sizeof(int))) { fprintf(stderr,"%s: worker receiving fd: cmsg_len %d too small\n",__progname,(int)cmh.cmsg_len); exit(1); } bcopy(&ctlbuf[CMSKIP(&cmh)],&worker_mp4fd,sizeof(int)); worker_mp4 = mp4_open(worker_mp4fd,worker_mp4fn,0); } static void find_track(MP4TRKTYPE t) { int i; mp4_scan(worker_mp4); for (i=mp4_ntracks(worker_mp4);i>=0;i--) { if (mp4_track_type(worker_mp4,i) == t) { worker_trkx = i; return; } } panic(); } static void video_command(const unsigned char *, int); // forward static void video_cmd_f_2(const unsigned char *data, int len) { if (len != worker_mp4fnl) panic(); bcopy(data,worker_mp4fn,worker_mp4fnl); get_mp4_fd(); find_track(MP4_TT_VIDEO); worker_trk = mp4_open_video(worker_mp4,worker_trkx); worker_call = &video_command; worker_want = 1; } static void video_cmd_f_1(const unsigned char *data, int len) { if (len != sizeof(int)) panic(); bcopy(data,&worker_mp4fnl,sizeof(int)); worker_mp4fn = malloc(worker_mp4fnl); if (! worker_mp4fn) panic(); worker_call = &video_cmd_f_2; worker_want = worker_mp4fnl; } static void video_cmd_v(const unsigned char *data, int len) { if (len != sizeof(video_w)+sizeof(video_h)) panic(); bcopy(data,&video_w,sizeof(video_w)); bcopy(data+sizeof(video_w),&video_h,sizeof(video_h)); video_bpf = video_w * video_h * 4; worker_call = &video_command; worker_want = 1; } #if 0 static int video_seekx(int cue) { int l; int m; int h; l = -1; h = video_nseek; while (h-l > 1) { m = (h + l) / 2; if (cue < video_seek_cue[m]) { h = m; } else if (cue > video_seek_cue[m]) { l = m; } else { return(m); } } return(-1); } #endif #if 0 static void video_cmd_d_2(const unsigned char *data, int len) { int x; if (len != video_nframes*sizeof(unsigned int)) panic(); bcopy(data,video_frames,video_nframes*sizeof(unsigned int)); worker_call = &video_command; worker_want = 1; video_inx = 0; x = video_seekx(video_start); if (x < 0) panic(); verb("video_start %u seekx %d\n",video_start,x); mp4_seek(worker_trk,x); verb("video_cmd_d_2: have frames list, starting decode\n"); video_id = aio_add_block(&video_decode,0); } #endif static int video_decode(void *arg __attribute__((__unused__))) { const MP4VDESC *d; unsigned int fno; VDECBUF vdb; if (video_nfree < 1) return(AIO_BLOCK_NIL); fno = video_ffree[--video_nfree]; d = mp4_get_video(worker_trk); if (! d->data) { aio_remove_block(video_id); verb("video_decode: all done\n"); free(video_ffree); vdb.cue = ~0U; vdb.frame = ~0U; vdb.when = -1; } else if ((d->w != video_w) || (d->h != video_h)) { fprintf(stderr,"%s: %s: video size changed %dx%d -> %dx%d\n",__progname,worker_mp4fn,video_w,video_h,d->w,d->h); exit(1); } else { verb("video_decode: pushing to parent\n"); bcopy(d->data,shm+video_dec.offset+(fno*video_bpf),video_bpf); vdb.cue = d->cue; vdb.frame = fno; vdb.when = d->when; } aio_oq_queue_copy(&worker_oq,&vdb,sizeof(vdb)); return(AIO_BLOCK_LOOP); } static void video_cmd_s(const unsigned char *data, int len) { int i; if (len != sizeof(video_dec)) panic(); bcopy(data,&video_dec,sizeof(video_dec)); worker_call = &video_command; worker_want = 1; video_nframes = video_dec.size / video_bpf; video_ffree = malloc(video_nframes*sizeof(unsigned int)); video_nfree = video_nframes; for (i=video_nfree-1;i>=0;i--) video_ffree[i] = i; video_id = aio_add_block(&video_decode,0); } static void video_cmd_b(const unsigned char *data, int len) { unsigned int fno; if (len != sizeof(unsigned int)) panic(); bcopy(data,&fno,sizeof(unsigned int)); worker_call = &video_command; worker_want = 1; if (video_nfree >= video_nframes) panic(); video_ffree[video_nfree++] = fno; } static void video_command(const unsigned char *data, int len) { if (len != 1) panic(); switch (*data) { default: panic(); break; case 'f': worker_call = &video_cmd_f_1; worker_want = sizeof(int); break; case 'v': worker_call = &video_cmd_v; worker_want = sizeof(video_w) + sizeof(video_h); break; case 's': worker_call = &video_cmd_s; worker_want = sizeof(video_dec); break; case 'b': worker_call = &video_cmd_b; worker_want = sizeof(unsigned int); break; } } static void audio_command(const unsigned char *, int); // forward static void audio_cmd_f_2(const unsigned char *data, int len) { if (len != worker_mp4fnl) panic(); bcopy(data,worker_mp4fn,worker_mp4fnl); get_mp4_fd(); find_track(MP4_TT_AUDIO); worker_trk = mp4_open_audio(worker_mp4,worker_trkx); worker_call = &audio_command; worker_want = 1; } static void audio_cmd_f_1(const unsigned char *data, int len) { if (len != sizeof(int)) panic(); bcopy(data,&worker_mp4fnl,sizeof(int)); worker_mp4fn = malloc(worker_mp4fnl); if (! worker_mp4fn) panic(); worker_call = &audio_cmd_f_2; worker_want = worker_mp4fnl; } static void audio_cmd_v(const unsigned char *data, int len) { if (len != (2*sizeof(unsigned int))+sizeof(MP4_AFMT)) panic(); bcopy(data,&audio_rate,sizeof(unsigned int)); bcopy(data+sizeof(unsigned int),&audio_channels,sizeof(unsigned int)); bcopy(data+(2*sizeof(unsigned int)),&audio_format,sizeof(MP4_AFMT)); worker_call = &audio_command; worker_want = 1; } static void send_audio_data(const MP4ADESC *d) { unsigned int nb; unsigned int o; unsigned int c; ADECBUF adb; nb = d->samples * d->channels * BYTES_PER_SAMPLE; if (audio_ringh+nb > audio_ring.size) { c = audio_ring.size - audio_ringh; bcopy(d->data,shm+audio_ring.offset+audio_ringh,c); adb.base = audio_ringh; adb.len = c; aio_oq_queue_copy(&worker_oq,&adb,sizeof(adb)); o = c; c = nb - c; bcopy(o+(const char *)d->data,shm+audio_ring.offset,c); adb.base = 0; adb.len = c; aio_oq_queue_copy(&worker_oq,&adb,sizeof(adb)); } else { bcopy(d->data,shm+audio_ring.offset+audio_ringh,nb); adb.base = audio_ringh; adb.len = nb; aio_oq_queue_copy(&worker_oq,&adb,sizeof(adb)); } audio_ringh += nb; if (audio_ringh >= audio_ring.size) audio_ringh -= audio_ring.size; audio_ringn += nb; if (audio_ringn >= audio_ring.size) panic(); } static int audio_decode(void *arg __attribute__((__unused__))) { const MP4ADESC *d; if (audio_pend) { if (audio_pendsize >= audio_ring.size-audio_ringn) return(AIO_BLOCK_NIL); verb("audio_decode: sending %u at %u\n",audio_pendsize,audio_ringh); send_audio_data(audio_pend); audio_pend = 0; } d = mp4_get_audio(worker_trk); if (! d->samples) { ADECBUF adb; adb.base = ~0U; adb.len = ~0U; aio_oq_queue_copy(&worker_oq,&adb,sizeof(adb)); aio_remove_block(audio_id); verb("audio_decode: all done\n"); } else if ((d->rate != audio_rate) || (d->channels != audio_channels) || (d->format != audio_format)) { printf("audio parameters changed (rate %u->%u, channels %u->%u, format %d->%d)\n", audio_rate,d->rate,audio_channels,d->channels,(int)audio_format,(int)d->format); exit(1); } else { audio_pend = d; audio_pendsize = d->samples * d->channels * BYTES_PER_SAMPLE; verb("audio_decode: got data, pendsize %u\n",audio_pendsize); } return(AIO_BLOCK_LOOP); } static void audio_cmd_s(const unsigned char *data, int len) { if (len != sizeof(SHMREGION)) panic(); bcopy(data,&audio_ring,sizeof(SHMREGION)); worker_call = &audio_command; worker_want = 1; audio_pend = 0; audio_ringh = 0; audio_ringt = 0; audio_ringn = 0; audio_id = aio_add_block(&audio_decode,0); } static void audio_cmd_b(const unsigned char *data, int len) { unsigned int base; unsigned int size; if (len != 2 * sizeof(unsigned int)) panic(); bcopy(data,&base,sizeof(unsigned int)); bcopy(data+sizeof(unsigned int),&size,sizeof(unsigned int)); worker_call = &audio_command; worker_want = 1; if (base != audio_ringt) panic(); if (size > audio_ringn) panic(); if (base+size > audio_ring.size) panic(); audio_ringt += size; audio_ringn -= size; if (audio_ringt >= audio_ring.size) audio_ringt -= audio_ring.size; } static void audio_command(const unsigned char *data, int len) { if (len != 1) panic(); switch (*data) { default: panic(); break; case 'f': worker_call = &audio_cmd_f_1; worker_want = sizeof(int); break; case 'v': worker_call = &audio_cmd_v; worker_want = (2 * sizeof(unsigned int)) + sizeof(MP4_AFMT); break; case 's': worker_call = &audio_cmd_s; worker_want = sizeof(SHMREGION); break; case 'b': worker_call = &audio_cmd_b; worker_want = 2 * sizeof(unsigned int); break; } } static void debugwait(void) { volatile int go = 0; while (! go) ; } static void worker_main_video(void) { if (debug & DEBUG_VIDEO) debugwait(); aio_oq_init(&worker_oq); worker_want = 1; worker_pend = 0; worker_apend = 0; worker_npend = 0; worker_call = &video_command; worker_ioid = aio_add_poll(worker_fd,&aio_rwtest_always,&worker_wtest,&rd_worker,&worker_wr,0); aio_event_loop(); } static void worker_main_audio(void) { if (debug & DEBUG_AUDIO) debugwait(); aio_oq_init(&worker_oq); worker_want = 1; worker_pend = 0; worker_apend = 0; worker_npend = 0; worker_call = &audio_command; worker_ioid = aio_add_poll(worker_fd,&aio_rwtest_always,&worker_wtest,&rd_worker,&worker_wr,0); aio_event_loop(); } static void worker_main_zoom(void) { if (debug & DEBUG_ZOOM) debugwait(); // XXX XXX XXX } static int parent_wtest_worker(void *wv) { return(aio_oq_nonempty(&((WORKER *)wv)->oq)); } static void parent_write_worker(void *wv) { WORKER *w; int nw; w = wv; nw = aio_oq_writev(&w->oq,w->fd,-1); switch (nw) { case AIO_WRITEV_ERROR: switch (errno) { case EINTR: case EWOULDBLOCK: return; } fprintf(stderr,"%s: write to worker [%d]: %s\n",__progname,w->n,strerror(errno)); exit(1); break; } if (nw < 0) { fprintf(stderr,"%s: impossible return %d from aio_oq_writev\n",__progname,nw); exit(1); } aio_oq_dropdata(&w->oq,nw); } static void open_dev_null(void) { devnull = open("/dev/null",O_RDWR,0); if (devnull < 0) { fprintf(stderr,"%s: open /dev/null: %s\n",__progname,strerror(errno)); exit(1); } } static void setup_shm(void) { void *mmrv; shmsize = 0x70000000; mmrv = mmap(0,shmsize,PROT_READ|PROT_WRITE,MAP_ANON|MAP_SHARED,-1,0); if (mmrv == MAP_FAILED) { fprintf(stderr,"%s: mmap shared memory for decoders: %s\n",__progname,strerror(errno)); exit(1); } shm = mmrv; } static void fork_workers(void) { int i; int j; int p[2]; int pfd[2]; pid_t parent; pid_t kid; parent = getpid(); fflush(0); n_workers = 3; workers = malloc(n_workers*sizeof(WORKER)); video_worker = 0; workers[0].type = WT_VIDEO; audio_worker = 1; workers[1].type = WT_AUDIO; zoom_worker = 2; workers[2].type = WT_ZOOM; for (i=n_workers-1;i>=0;i--) { workers[i].n = i; aio_oq_init(&workers[i].oq); if (socketpair(AF_LOCAL,SOCK_STREAM,0,&p[0]) < 0) { fprintf(stderr,"%s: socketpair: %s\n",__progname,strerror(errno)); exit(1); } if (socketpair(AF_LOCAL,SOCK_STREAM,0,&pfd[0]) < 0) { fprintf(stderr,"%s: socketpair: %s\n",__progname,strerror(errno)); exit(1); } kid = fork(); if (kid < 0) { fprintf(stderr,"%s: fork: %s\n",__progname,strerror(errno)); exit(1); } if (kid == 0) { for (j=n_workers-1;j>=0;j--) { if (workers[j].fd >= 0) { close(workers[j].fd); close(workers[j].fdfd); } } close(p[0]); close(pfd[0]); worker_fd = p[1]; worker_fdfd = pfd[1]; mypid = getpid(); switch (workers[i].type) { default: panic(); break; case WT_VIDEO: verb_set_tag("video"); setproctitle("worker[%d] for %lu (video)",i,(unsigned long int)parent); write(devnull,"video",5); write(devnull,&i,sizeof(int)); worker_main_video(); break; case WT_AUDIO: verb_set_tag("audio"); setproctitle("worker[%d] for %lu (audio)",i,(unsigned long int)parent); write(devnull,"audio",5); write(devnull,&i,sizeof(int)); worker_main_audio(); break; case WT_ZOOM: verb_set_tag("zoom"); setproctitle("worker[%d] for %lu (zoom)",i,(unsigned long int)parent); write(devnull,"zoom",4); write(devnull,&i,sizeof(int)); worker_main_zoom(); break; } _exit(0); } close(p[1]); close(pfd[1]); set_nbio(p[0]); workers[i].fd = p[0]; workers[i].fdfd = pfd[0]; workers[i].pid = kid; } mypid = getpid(); verb_set_tag("parent"); if (debug & DEBUG_PARENT) debugwait(); for (i=n_workers-1;i>=0;i--) workers[i].writeid = aio_add_poll(workers[i].fd,&aio_rwtest_never,&parent_wtest_worker,0,&parent_write_worker,workers+i); } static void prompt(void) { printf("play> "); fflush(stdout); } static void cmd_save(const void *data, int len) { if (cmdbl+len > cmdba) { cmdbuf = realloc(cmdbuf,cmdba=cmdbl+len); } bcopy(data,cmdbuf+cmdbl,len); cmdbl += len; } static void print_help(void) { printf("? print this help\n"); printf("p pause play\n"); printf("go unpause play\n"); #if 0 printf(". print current state\n"); printf("zN set zoom to N\n"); #endif printf("vol N set volume to N dB\n"); #if 0 printf("seek T set to time T in the current file\n"); #endif } static void pause_play(void) { if (paused) { printf("Already paused.\n"); } else { audio_info_t ai; AUDIO_INITINFO(&ai); ai.play.pause = 1; if (ioctl(audiofd,AUDIO_SETINFO,&ai) < 0) { fprintf(stderr,"%s: AUDIO_SETINFO to pause %s: %s\n",__progname,AUDIODEV,strerror(errno)); exit(1); } paused = 1; printf("Paused.\n"); } } static void unpause_play(void) { if (paused) { audio_info_t ai; AUDIO_INITINFO(&ai); ai.play.pause = 0; if (ioctl(audiofd,AUDIO_SETINFO,&ai) < 0) { fprintf(stderr,"%s: AUDIO_SETINFO to unpause %s: %s\n",__progname,AUDIODEV,strerror(errno)); exit(1); } paused = 0; printf("Resumed.\n"); } else { printf("Not paused.\n"); } } static void set_volume(double dB) { audio_gain = pow(10,dB/20); recopy_audio(); } static void quit(void) { fflush(0); exit(0); } static void command_line(const char *txt, int len) { int i; int i0; // int n; for (i=0;(izf==1)?-1:1); } else if ((i-i0 == 2) && !bcmp(&txt[i0],"zf",2)) { set_zoom(ps,-1); } else if ((i-i0 > 1) && (txt[i0] == 'z') && parsenumber(txt+i0+1,i-i0-1,&n)) { set_zoom(ps,n); } #endif else { printf("unrecognized command `%.*s'\n",i-i0,&txt[i0]); } } #define CIF_ECHO 0x00000001 #define CIF_PROMPT 0x00000002 static void cmd_input(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(line,linelen); } buf = input; bx = 0; while (bx < len) { nl = memchr(&buf[bx],'\n',len-bx); if (nl == 0) { cmd_save(&buf[bx],len-bx); return; } else { if (cmdbl == 0) { cmdline(&buf[bx],nl-&buf[bx]); } else { cmd_save(&buf[bx],nl-&buf[bx]); cmdline(cmdbuf,cmdbl); } cmdbl = 0; bx = (nl+1) - &buf[0]; if (flags & CIF_PROMPT) prompt(); } } } static void rd_stdin(void *arg __attribute__((__unused__))) { unsigned char ibuf[512]; int n; n = read(0,&ibuf[0],sizeof(ibuf)); if (n < 0) { switch (errno) { case EINTR: case EWOULDBLOCK: return; break; } fprintf(stderr,"%s: read from stdin: %s\n",__progname,strerror(errno)); exit(1); } cmd_input(&ibuf[0],n,CIF_PROMPT); } static int await_ready(void *arg __attribute__((__unused__))) { if (startwait) return(AIO_BLOCK_NIL); aio_remove_block(stdin_id); prompt(); stdin_id = aio_add_poll(0,&aio_rwtest_always,&aio_rwtest_never,&rd_stdin,0,0); return(AIO_BLOCK_LOOP); } static void start_cmdline(void) { startwait = START_AUDIO | START_VIDEO; stdin_id = aio_add_block(&await_ready,0); } int main(int, char **); int main(int ac, char **av) { saveargv(ac,av); handleargs(ac-1,av+1); starttime = gettime(); open_dev_null(); aio_poll_init(); setup_shm(); fork_workers(); setup_X(); setup_sound(); start_cmdline(); play_files(); aio_event_loop(); return(0); }