3 * Copyright (C) 2016-2020 William Morrow
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License as published
7 * by the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * General Public License for more details.
15 * You should have received a copy of the GNU General Public
16 * License along with this program; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
34 #include <X11/Xatom.h>
36 #include <X11/Xutil.h>
37 #include <X11/extensions/XShm.h>
40 #include "libavfilter/buffersrc.h"
41 #include "libavfilter/buffersink.h"
42 #include "libavformat/avformat.h"
43 #include "libavformat/avio.h"
44 #include "libavcodec/avcodec.h"
45 #include "libavfilter/avfilter.h"
46 #include "libavutil/avutil.h"
47 #include "libavutil/imgutils.h"
48 #include "libavutil/opt.h"
49 #include "libavutil/pixdesc.h"
50 #include "libswresample/swresample.h"
51 #include "libswscale/swscale.h"
57 #define av_register_all(s)
58 #define avfilter_register_all(s)
62 int64_t tm = 0, tn = 0;
72 gg_window(Display *display, int x, int y, int w, int h);
82 gg_window::gg_window(Display *display, int x, int y, int w, int h)
84 this->display = display;
85 this->x = x; this->y = y;
86 this->w = w; this->h = h;
88 Window root = DefaultRootWindow(display);
89 Screen *screen = DefaultScreenOfDisplay(display);
90 Visual *visual = DefaultVisualOfScreen(screen);
91 int depth = DefaultDepthOfScreen(screen);
93 unsigned long gcmask = GCGraphicsExposures;
95 gcvalues.graphics_exposures = 0;
96 gc = XCreateGC(display, root, gcmask, &gcvalues);
98 XSetWindowAttributes attributes;
99 attributes.background_pixel = BlackPixel(display, DefaultScreen(display));
100 attributes.border_pixel = WhitePixel(display, DefaultScreen(display));
101 attributes.event_mask =
102 EnterWindowMask | LeaveWindowMask |
103 ButtonPressMask | ButtonReleaseMask |
104 PointerMotionMask | FocusChangeMask;
105 int valueMask = CWBackPixel | CWBorderPixel | CWEventMask;
106 this->win = XCreateWindow(display, root, x, y, w, h, border, depth,
107 InputOutput, visual, valueMask, &attributes);
110 gg_window::~gg_window()
112 XFreeGC(display, gc);
113 XDestroyWindow(display, win);
116 void gg_window::show()
118 XMapWindow(display,win);
121 void gg_window::hide()
123 XUnmapWindow(display,win);
131 XShmSegmentInfo shm_info;
137 gg_ximage(Display *display, int w, int h, int shm);
140 void put_image(gg_window &gw);
143 gg_ximage::gg_ximage(Display *display, int w, int h, int shm)
145 this->display = display;
146 this->w = w; this->h = h;
149 ximage = 0; sz = 0; data = 0;
150 Screen *screen = DefaultScreenOfDisplay(display);
151 Visual *visual = DefaultVisualOfScreen(screen);
152 int depth = DefaultDepthOfScreen(screen);
155 ximage = XShmCreateImage(display, visual, depth, ZPixmap, (char*)NULL, &shm_info, w, h);
156 // Create shared memory
157 sz = h * ximage->bytes_per_line;
158 shm_info.shmid = shmget(IPC_PRIVATE, sz + 8, IPC_CREAT | 0777);
159 if(shm_info.shmid < 0) perror("shmget");
160 data = (unsigned char *)shmat(shm_info.shmid, NULL, 0);
161 // This causes it to automatically delete when the program exits.
162 shmctl(shm_info.shmid, IPC_RMID, 0);
163 ximage->data = shm_info.shmaddr = (char*)data;
164 shm_info.readOnly = 0;
165 // Get the real parameters
166 if(!XShmAttach(display, &shm_info)) perror("XShmAttach");
169 ximage = XCreateImage(display, visual, depth, ZPixmap, 0, (char*)data, w, h, 8, 0);
170 sz = h * ximage->bytes_per_line;
171 data = new unsigned char[sz+8];
174 ximage->data = (char*) data;
175 lsb[0] = ximage->red_mask & ~(ximage->red_mask<<1);
176 lsb[1] = ximage->green_mask & ~(ximage->green_mask<<1);
177 lsb[2] = ximage->blue_mask & ~(ximage->blue_mask<<1);
180 gg_ximage::~gg_ximage()
185 XDestroyImage(ximage);
186 XShmDetach(display, &shm_info);
188 shmdt(shm_info.shmaddr);
194 XDestroyImage(ximage);
198 void gg_ximage::put_image(gg_window &gw)
200 Display *display = gw.display;
204 XShmPutImage(display, win, gc, ximage, 0,0, 0,0,w,h, 0);
206 XPutImage(display, win, gc, ximage, 0,0, 0,0,w,h);
215 gg_ximage *imgs[2], *img;
217 gg_thread(gg_window &gw, int shm) ;
220 static void *entry(void *t);
224 void post(gg_ximage *ip);
225 gg_ximage *next_img();
227 pthread_mutex_t draw;
228 void draw_lock() { pthread_mutex_lock(&draw); }
229 void draw_unlock() { pthread_mutex_unlock(&draw); }
232 gg_thread::gg_thread(gg_window &gw, int shm)
235 imgs[0] = new gg_ximage(gw.display, gw.w, gw.h, shm);
236 imgs[1] = new gg_ximage(gw.display, gw.w, gw.h, shm);
239 pthread_mutex_init(&draw, 0);
242 gg_thread::~gg_thread()
246 pthread_mutex_destroy(&draw);
249 void *gg_thread::entry(void *t)
251 return ((gg_thread*)t)->run();
254 void gg_thread::start()
257 pthread_attr_init(&attr);
258 pthread_attr_setinheritsched(&attr, PTHREAD_INHERIT_SCHED);
260 pthread_create(&tid, &attr, &entry, this);
261 pthread_attr_destroy(&attr);
263 void gg_thread::join()
266 pthread_join(tid, 0);
269 void *gg_thread::run()
272 if( XPending(gw.display) ) {
274 XNextEvent(gw.display, &xev);
285 if( !img ) { usleep(10000); continue; }
293 gg_ximage *gg_thread::next_img()
295 gg_ximage *ip = imgs[active];
300 void gg_thread::post(gg_ximage *ip)
311 AVFormatContext *fmt_ctx;
315 AVPixelFormat pix_fmt;
318 int need_packet, eof;
319 int open_decoder(const char *filename, int vid_no);
320 void close_decoder();
321 AVFrame *read_frame();
326 av_init_packet(&this->ipkt);
331 this->frame_rate = 0;
332 this->need_packet = 0;
334 this->pix_fmt = AV_PIX_FMT_NONE;
338 void ffcmpr::close_decoder()
340 av_packet_unref(&ipkt);
341 if( !fmt_ctx ) return;
342 if( ctx ) avcodec_free_context(&ctx);
343 avformat_close_input(&fmt_ctx);
344 av_frame_free(&ipic);
352 int ffcmpr::open_decoder(const char *filename, int vid_no)
355 if( stat(filename, &fst) ) return 1;
357 AVDictionary *fopts = 0;
359 av_dict_set(&fopts, "formatprobesize", "5000000", 0);
360 av_dict_set(&fopts, "scan_all_pmts", "1", 0);
361 av_dict_set(&fopts, "threads", "auto", 0);
362 int ret = avformat_open_input(&fmt_ctx, filename, NULL, &fopts);
363 av_dict_free(&fopts);
365 fprintf(stderr,"file open failed: %s\n", filename);
368 ret = avformat_find_stream_info(fmt_ctx, NULL);
370 fprintf(stderr,"file probe failed: %s\n", filename);
375 for( int i=0; !this->st && ret>=0 && i<(int)fmt_ctx->nb_streams; ++i ) {
376 AVStream *fst = fmt_ctx->streams[i];
377 AVMediaType type = fst->codecpar->codec_type;
378 if( type != AVMEDIA_TYPE_VIDEO ) continue;
379 if( --vid_no < 0 ) this->st = fst;
382 AVCodecID codec_id = st->codecpar->codec_id;
383 AVDictionary *copts = 0;
384 //av_dict_copy(&copts, opts, 0);
385 AVCodec *decoder = avcodec_find_decoder(codec_id);
386 ctx = avcodec_alloc_context3(decoder);
387 avcodec_parameters_to_context(ctx, st->codecpar);
388 if( avcodec_open2(ctx, decoder, &copts) < 0 ) {
389 fprintf(stderr,"codec open failed: %s\n", filename);
392 av_dict_free(&copts);
393 ipic = av_frame_alloc();
397 AVRational framerate = av_guess_frame_rate(fmt_ctx, st, 0);
398 this->frame_rate = !framerate.den ? 0 : (double)framerate.num / framerate.den;
399 this->pix_fmt = (AVPixelFormat)st->codecpar->format;
400 this->width = st->codecpar->width;
401 this->height = st->codecpar->height;
405 AVFrame *ffcmpr::read_frame()
407 av_frame_unref(ipic);
409 for( int retrys=1000; --retrys>=0; ) {
412 AVPacket *pkt = &ipkt;
413 av_packet_unref(pkt);
414 int ret = av_read_frame(fmt_ctx, pkt);
416 if( ret != AVERROR_EOF ) return 0;
417 ret = 0; eof = 1; pkt = 0;
420 if( pkt->stream_index != st->index ) continue;
421 if( !pkt->data || !pkt->size ) continue;
423 avcodec_send_packet(ctx, pkt);
426 int ret = avcodec_receive_frame(ctx, ipic);
427 if( ret >= 0 ) return ipic;
428 if( ret != AVERROR(EAGAIN) ) {
429 eof = 1; need_packet = 0;
437 static inline int get_depth(AVPixelFormat pix_fmt)
440 const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(pix_fmt);
442 for( int i=desc->nb_components; --i>=0; ) {
443 int bits = desc->comp[i].depth;
444 if( depth < bits ) depth = bits;
450 static int diff_frame(AVFrame *afrm, AVFrame *bfrm,
451 gg_ximage *ximg, int w, int h, int s, int a1)
454 uint8_t *arow = afrm->data[0];
455 uint8_t *brow = bfrm->data[0];
456 uint8_t *frow = ximg->data;
457 int asz = afrm->linesize[0], bsz = afrm->linesize[0];
458 XImage *ximage = ximg->ximage;
459 int fsz = ximage->bytes_per_line;
460 int rsz = w, bpp = (ximage->bits_per_pixel+7)/8;
461 uint32_t *lsb = ximg->lsb;
463 for( int y=h; --y>=0; arow+=asz, brow+=bsz, frow+=fsz ) {
464 uint16_t *ap = (uint16_t*)arow + a1;
465 uint16_t *bp = (uint16_t*)brow + a1;
467 for( int x=rsz; --x>=0; ) {
468 uint32_t rgb = 0; uint8_t *rp = fp;
469 for( int i=0; i<3; ++i ) {
470 int d = *ap++ - *bp++;
472 else if( s < 0 ) d <<= -s;
474 if( v > 255 ) v = 255;
475 else if( v < 0 ) v = 0;
481 if( ximage->byte_order == MSBFirst )
482 for( int i=3; --i>=0; ) *rp++ = rgb>>(8*i);
484 for( int i=0; i<3; ++i ) *rp++ = rgb>>(8*i);
485 ++ap; ++bp; fp += bpp;
489 printf("%d %d %d %f", sz, m, n, (double)n/sz);
494 int main(int ac, char **av)
498 Display *display = XOpenDisplay(getenv("DISPLAY"));
500 fprintf(stderr,"Unable to open display\n");
504 printf("usage: %s a.fmt b.fmt <frm0> <s>\n"
505 " a = src media, b = src media, frm0 = a/b skew\n"
506 " s = shift <0:lt, =0:none(dft), >0:rt\n"
507 " env var GG_LOG_LEVEL=q/f/e/v/d/<nbr>\n", av[0]);
510 const char *cp = getenv("GG_LOG_LEVEL");
514 case 'q': lvl = AV_LOG_QUIET; break;
515 case 'f': lvl = AV_LOG_FATAL; break;
516 case 'e': lvl = AV_LOG_ERROR; break;
517 case 'v': lvl = AV_LOG_VERBOSE; break;
518 case 'd': lvl = AV_LOG_DEBUG; break;
519 case '0'...'9': lvl = atoi(cp); break;
522 av_log_set_level(lvl);
526 if( a.open_decoder(av[1],0) ) return 1;
527 if( b.open_decoder(av[2],0) ) return 1;
531 int frm0 = ac>3 ? atoi(av[3]) : 0;
532 int s = ac>4 ? atoi(av[4]) : 0;
534 printf("file a:%s\n", av[1]);
535 printf(" id 0x%06x:", a.ctx->codec_id);
536 const AVCodecDescriptor *adesc = avcodec_descriptor_get(a.ctx->codec_id);
537 printf(" video %s\n", adesc ? adesc->name : " (unkn)");
538 printf(" %dx%d %5.2f", a.width, a.height, a.frame_rate);
539 const char *apix = av_get_pix_fmt_name(a.pix_fmt);
540 int ad = get_depth(a.pix_fmt);
541 printf(" pix %s, depth=%d\n", apix ? apix : "(unkn)", ad);
543 printf("file b:%s\n", av[2]);
544 printf(" id 0x%06x:", b.ctx->codec_id);
545 const AVCodecDescriptor *bdesc = avcodec_descriptor_get(b.ctx->codec_id);
546 printf(" video %s\n", bdesc ? bdesc->name : " (unkn)");
547 printf(" %dx%d %5.2f", b.width, b.height, b.frame_rate);
548 const char *bpix = av_get_pix_fmt_name(b.pix_fmt);
549 int bd = get_depth(b.pix_fmt);
550 printf(" pix %s, depth=%d\n", bpix ? bpix : "(unkn)", bd);
551 int d = ad>bd ? ad : bd;
553 int lsb = s, msb = lsb + 7;
554 if( lsb < 0 ) lsb = 0;
555 if( msb > 15 ) msb = 15;
556 printf("shift: %d, msb..lsb: %d..%d of uint16\n", s, msb, lsb);
558 // if( a.ctx->codec_id != b.ctx->codec_id ) { printf("codec mismatch\n"); return 1;}
559 if( a.width != b.width ) { printf("width mismatch\n"); return 1;}
560 if( a.height != b.height ) { printf("height mismatch\n"); return 1;}
561 // if( a.frame_rate != b.frame_rate ) { printf("framerate mismatch\n"); return 1;}
562 // if( a.pix_fmt != b.pix_fmt ) { printf("format mismatch\n"); return 1;}
564 signal(SIGINT,sigint);
565 const AVPixFmtDescriptor *afmt = av_pix_fmt_desc_get(a.pix_fmt);
566 AVPixelFormat a_pix_fmt = afmt->flags & AV_PIX_FMT_FLAG_RGB ?
567 AV_PIX_FMT_RGBA64LE : AV_PIX_FMT_AYUV64LE ;
568 const AVPixFmtDescriptor *bfmt = av_pix_fmt_desc_get(b.pix_fmt);
569 AVPixelFormat b_pix_fmt = bfmt->flags & AV_PIX_FMT_FLAG_RGB ?
570 AV_PIX_FMT_RGBA64LE : AV_PIX_FMT_AYUV64LE ;
571 if( a_pix_fmt != b_pix_fmt ) {
572 printf(" a/b yuv/rgb mismatched, using a = %s\n", apix);
573 b_pix_fmt = a_pix_fmt;
575 int a1 = a_pix_fmt == AV_PIX_FMT_AYUV64LE ? 1 : 0; // alpha 1st chan
577 struct SwsContext *a_cvt = sws_getCachedContext(0, a.width, a.height, a.pix_fmt,
578 a.width, a.height, a_pix_fmt, SWS_POINT, 0, 0, 0);
579 struct SwsContext *b_cvt = sws_getCachedContext(0, b.width, b.height, b.pix_fmt,
580 b.width, b.height, b_pix_fmt, SWS_POINT, 0, 0, 0);
581 if( !a_cvt || !b_cvt ) {
582 printf("sws_getCachedContext() failed\n");
586 AVFrame *afrm = av_frame_alloc();
587 av_image_alloc(afrm->data, afrm->linesize,
588 a.width, a.height, a_pix_fmt, 1);
590 AVFrame *bfrm = av_frame_alloc();
591 av_image_alloc(bfrm->data, bfrm->linesize,
592 b.width, b.height, b_pix_fmt, 1);
593 { gg_window gw(display, 10,10, a.width,a.height);
595 gg_thread thr(gw, 1);
598 while( frm0 > 0 ) { a.read_frame(); --frm0; }
599 while( frm0 < 0 ) { b.read_frame(); ++frm0; }
602 AVFrame *ap = a.read_frame();
604 AVFrame *bp = b.read_frame();
606 sws_scale(a_cvt, ap->data, ap->linesize, 0, ap->height,
607 afrm->data, afrm->linesize);
608 sws_scale(b_cvt, bp->data, bp->linesize, 0, bp->height,
609 bfrm->data, bfrm->linesize);
611 gg_ximage *fimg = thr.next_img();
612 int ret = diff_frame(afrm, bfrm, fimg, ap->width, ap->height, s, a1);
614 err += ret; ++frm_no;
615 printf(" %d\n",frm_no);
618 av_freep(&afrm->data);
619 av_frame_free(&afrm);
620 av_freep(&bfrm->data);
621 av_frame_free(&bfrm);
628 XCloseDisplay(display);
629 printf("\n%jd %jd\n", tm, tn);