1 /**
2 \brief VDPAU filters Deinterlacer
3 \author mean (C) 2010
4 This is slow as we copy back and forth data to/from the video cards
5
6 On a Q6600
7
8 FullHD:
9 Readback ~ 5 ms, RGB 2 YUV ~ 20 ms : 100% CPU
10 720
11 Readback ~ 2 ms, RGB2YUV ~ 10 ms : 50% CPU
12
13
14
15 */
16 #include "ADM_cpp.h"
17 #include <list>
18 #include "ADM_default.h"
19 #ifdef USE_VDPAU
20 extern "C" {
21 #include "libavcodec/avcodec.h"
22 #include "libavcodec/vdpau.h"
23 }
24
25 #include "ADM_coreVideoFilter.h"
26 #include "ADM_videoFilterCache.h"
27 #include "DIA_factory.h"
28 #include "ADM_vidMisc.h"
29 #include "vdpauFilterDeint.h"
30 #include "vdpauFilterDeint_desc.cpp"
31
32 #include "ADM_coreVdpau.h"
33 //
34 #define ADM_INVALID_FRAME_NUM 0x80000000
35 #define ADM_NB_SURFACES 5
36
37 //#define DO_BENCHMARK
38 #define NB_BENCH 100
39
40 #if 0
41 #define aprintf printf
42 #else
43 #define aprintf(...) {}
44 #endif
45
46 enum
47 {
48 ADM_KEEP_TOP=0,
49 ADM_KEEP_BOTTOM=1,
50 ADM_KEEP_BOTH=2
51 };
52 /**
53 \class VDPSlot
54 */
55 class VDPSlot
56 {
57 public:
58 VDPSlot() ;
59 ~VDPSlot();
60 VdpVideoSurface surface;
61 bool isExternal;
62 uint64_t pts;
63 uint32_t frameNumber;
64 ADMImage *image;
65 };
66
VDPSlot()67 VDPSlot::VDPSlot()
68 {
69 surface=VDP_INVALID_HANDLE;
70 image=NULL;
71 }
~VDPSlot()72 VDPSlot::~VDPSlot()
73 {
74 if(image) delete image;
75 image=NULL;
76 if(surface!=VDP_INVALID_HANDLE)
77 {
78 // will be freed by the pool..
79 }
80 surface=VDP_INVALID_HANDLE;
81 }
82
83 /**
84 \class vdpauVideoFilterDeint
85 */
86 class vdpauVideoFilterDeint : public ADM_coreVideoFilterCached
87 {
88 protected:
89 VDPSlot slots[3];
90 bool eof;
91 bool secondField;
92 uint64_t nextPts;
93 ADMColorScalerSimple *scaler;
94 bool passThrough;
95 bool setupVdpau(void);
96 bool cleanupVdpau(void);
97 bool updateConf(void);
98 uint8_t *tempBuffer;
99 vdpauFilterDeint configuration;
100 VdpOutputSurface outputSurface;
101 std::list <VdpVideoSurface> freeSurface;
102 VdpVideoSurface surfacePool[ADM_NB_SURFACES];
103 VdpVideoMixer mixer;
104 protected:
105 bool rotateSlots(void);
106 bool clearSlots(void);
107 bool uploadImage(ADMImage *next,const VdpVideoSurface surface) ;
108 bool fillSlot(int slot,ADMImage *image);
109 bool getResult(ADMImage *image);
110 bool sendField(bool topField);
111 bool setIdentityCSC(void);
112
113 public:
114 virtual bool goToTime(uint64_t usSeek);
115 vdpauVideoFilterDeint(ADM_coreVideoFilter *previous,CONFcouple *conf);
116 ~vdpauVideoFilterDeint();
117
118 virtual const char *getConfiguration(void); /// Return current configuration as a human readable string
119 virtual bool getNextFrame(uint32_t *fn,ADMImage *image); /// Return the next image
120 virtual bool getCoupledConf(CONFcouple **couples) ; /// Return the current filter configuration
121 virtual void setCoupledConf(CONFcouple *couples);
122 virtual bool configure(void) ; /// Start graphical user interface
123 };
124
125 // Add the hook to make it valid plugin
126 DECLARE_VIDEO_FILTER( vdpauVideoFilterDeint, // Class
127 1,0,0, // Version
128 ADM_UI_GTK+ADM_UI_QT4+ADM_FEATURE_VDPAU, // We need a display for VDPAU; so no cli...
129 VF_INTERLACING, // Category
130 "vdpauDeint", // internal name (must be uniq!)
131 QT_TRANSLATE_NOOP("vdpaudeint","vdpauDeint"), // Display name
132 QT_TRANSLATE_NOOP("vdpaudeint","VDPAU deinterlacer (+resize).") // Description
133 );
134
135 //
136
137 /**
138 \fn updateConf
139 */
updateConf(void)140 bool vdpauVideoFilterDeint::updateConf(void)
141 {
142 memcpy(&info,previousFilter->getInfo(),sizeof(info));
143 if(passThrough)
144 {
145 ADM_warning("PassThrough mode\n");
146 return true;
147 }
148 if(configuration.resizeToggle)
149 {
150 info.width=configuration.targetWidth;
151 info.height=configuration.targetHeight;
152 }
153 if(configuration.deintMode==ADM_KEEP_BOTH)
154 {
155 info.frameIncrement/=2;
156 if(info.timeBaseNum && info.timeBaseDen)
157 {
158 if(info.timeBaseDen<=30000 && (info.timeBaseNum & 1))
159 info.timeBaseDen*=2;
160 else
161 info.timeBaseNum/=2;
162 }
163 }
164 return true;
165 }
166 /**
167 \fn goToTime
168 \brief called when seeking. Need to cleanup our stuff.
169 */
goToTime(uint64_t usSeek)170 bool vdpauVideoFilterDeint::goToTime(uint64_t usSeek)
171 {
172 secondField=false;
173 eof=false;
174 clearSlots();
175 uint32_t oldFrameIncrement=info.frameIncrement;
176 if(configuration.deintMode==ADM_KEEP_BOTH)
177 info.frameIncrement*=2;
178 bool r=ADM_coreVideoFilterCached::goToTime(usSeek);
179 info.frameIncrement=oldFrameIncrement;
180 return r;
181 }
182 /**
183 \fn setIdentityCSC
184 \brief set the RGB/YUV matrix to identity so that data are still YUV at the end
185 Should not work, but it does.
186 */
setIdentityCSC(void)187 bool vdpauVideoFilterDeint::setIdentityCSC(void)
188 {
189 ADM_info("Setting custom CSC\n");
190 const VdpCSCMatrix matrix={{1.,0,0,0},{0,1.,0,0},{0,0,1.,0}};
191 VdpVideoMixerAttribute attributes_key[]={VDP_VIDEO_MIXER_ATTRIBUTE_CSC_MATRIX};
192 const void * attribute_values[] = {&matrix};
193
194 VdpStatus st = admVdpau::mixerSetAttributesValue(mixer, 1,attributes_key, (void * const *)attribute_values);
195 if(st!=VDP_STATUS_OK)
196 ADM_error("Cannot set custom matrix (CSC)\n");
197 return true;
198 }
199 /**
200 \fn resetVdpau
201 */
setupVdpau(void)202 bool vdpauVideoFilterDeint::setupVdpau(void)
203 {
204 scaler=NULL;
205 secondField=false;
206 nextFrame=0;
207 int paddedHeight=(previousFilter->getInfo()->height+15)&~15;
208 if(!admVdpau::isOperationnal())
209 {
210 ADM_warning("Vdpau not operationnal\n");
211 return false;
212 }
213 if(VDP_STATUS_OK!=admVdpau::outputSurfaceCreate(VDP_RGBA_FORMAT_B8G8R8A8,
214 info.width,info.height,&outputSurface))
215 {
216 ADM_error("Cannot create outputSurface0\n");
217 return false;
218 }
219 for(int i=0;i<ADM_NB_SURFACES;i++) surfacePool[i]=VDP_INVALID_HANDLE;
220 for(int i=0;i<ADM_NB_SURFACES;i++)
221 {
222 if(VDP_STATUS_OK!=admVdpau::surfaceCreate( previousFilter->getInfo()->width,
223 previousFilter->getInfo()->height,
224 &(surfacePool[i])))
225 {
226 ADM_error("Cannot create input Surface %d\n",i);
227 goto badInit;
228 }
229 aprintf("Created surface %d\n",(int)surfacePool[i]);
230 }
231 // allocate our (dummy) images
232 for(int i=0;i<3;i++)
233 slots[i].image=new ADMImageDefault( previousFilter->getInfo()->width,
234 previousFilter->getInfo()->height);
235
236 if(VDP_STATUS_OK!=admVdpau::mixerCreate(previousFilter->getInfo()->width,
237 paddedHeight,&mixer,true,configuration.enableIvtc))
238 {
239 ADM_error("Cannot create mixer\n");
240 goto badInit;
241 }
242 tempBuffer=new uint8_t[info.width*info.height*4];
243 scaler=new ADMColorScalerSimple( info.width,info.height, ADM_COLOR_BGR32A,ADM_COLOR_YV12);
244
245 freeSurface.clear();
246 for(int i=0;i<ADM_NB_SURFACES;i++)
247 freeSurface.push_back(surfacePool[i]);
248 setIdentityCSC();
249 ADM_info("VDPAU setup ok\n");
250 return true;
251 badInit:
252 cleanupVdpau();
253 passThrough=true;
254 return false;
255 }
256 /**
257 \fn cleanupVdpau
258 */
cleanupVdpau(void)259 bool vdpauVideoFilterDeint::cleanupVdpau(void)
260 {
261 for(int i=0;i<ADM_NB_SURFACES;i++)
262 {
263 if(surfacePool[i]!=VDP_INVALID_HANDLE)
264 {
265 admVdpau::surfaceDestroy(surfacePool[i]);
266 surfacePool[i]=VDP_INVALID_HANDLE;
267 }
268 }
269 if(outputSurface!=VDP_INVALID_HANDLE) admVdpau::outputSurfaceDestroy(outputSurface);
270 outputSurface=VDP_INVALID_HANDLE;
271 if(mixer!=VDP_INVALID_HANDLE) admVdpau::mixerDestroy(mixer);
272 mixer=VDP_INVALID_HANDLE;
273 if(tempBuffer) delete [] tempBuffer;
274 tempBuffer=NULL;
275 if(scaler) delete scaler;
276 scaler=NULL;
277 for(int i=0;i<3;i++)
278 if(slots[i].image)
279 {
280 delete slots[i].image;
281 slots[i].image=NULL;
282 }
283
284 return true;
285 }
286
287 /**
288 \fn constructor
289 */
vdpauVideoFilterDeint(ADM_coreVideoFilter * in,CONFcouple * setup)290 vdpauVideoFilterDeint::vdpauVideoFilterDeint(ADM_coreVideoFilter *in, CONFcouple *setup):
291 ADM_coreVideoFilterCached(5,in,setup)
292 {
293 eof=false;
294 for(int i=0;i<ADM_NB_SURFACES;i++)
295 surfacePool[i]=VDP_INVALID_HANDLE;
296 mixer=VDP_INVALID_HANDLE;
297 outputSurface=VDP_INVALID_HANDLE;
298 if(!setup || !ADM_paramLoad(setup,vdpauFilterDeint_param,&configuration))
299 {
300 // Default value
301 configuration.resizeToggle=false;
302 configuration.deintMode=ADM_KEEP_TOP;
303 configuration.targetWidth=info.width;
304 configuration.targetHeight=info.height;
305 configuration.enableIvtc=false;
306 }
307
308 myName="vdpauDeint";
309 tempBuffer=NULL;
310 passThrough=false;
311 updateConf();
312 passThrough=!setupVdpau();
313 nextPts=0;
314 }
315 /**
316 \fn destructor
317 */
~vdpauVideoFilterDeint()318 vdpauVideoFilterDeint::~vdpauVideoFilterDeint()
319 {
320 cleanupVdpau();
321 }
322 /**
323 \fn updateInfo
324 */
configure(void)325 bool vdpauVideoFilterDeint::configure( void)
326 {
327
328 diaMenuEntry tMode[]={
329 {ADM_KEEP_TOP, QT_TRANSLATE_NOOP("vdpaudeint","Keep Top Field"),NULL},
330 {ADM_KEEP_BOTTOM, QT_TRANSLATE_NOOP("vdpaudeint","Keep Bottom Field"),NULL},
331 {ADM_KEEP_BOTH, QT_TRANSLATE_NOOP("vdpaudeint","Double framerate"),NULL}
332
333 };
334 bool doResize=configuration.resizeToggle;
335 bool doIvtc=configuration.enableIvtc;
336 diaElemToggle tIvtc(&(doIvtc), QT_TRANSLATE_NOOP("vdpaudeint","_IVTC"));
337 diaElemToggle tResize(&(doResize), QT_TRANSLATE_NOOP("vdpaudeint","_Resize"));
338 diaElemMenu mMode(&(configuration.deintMode), QT_TRANSLATE_NOOP("vdpaudeint","_Deint Mode:"), 3,tMode);
339 diaElemUInteger tWidth(&(configuration.targetWidth),QT_TRANSLATE_NOOP("vdpaudeint","Width:"),16,MAXIMUM_SIZE);
340 diaElemUInteger tHeight(&(configuration.targetHeight),QT_TRANSLATE_NOOP("vdpaudeint","Height:"),16,MAXIMUM_SIZE);
341
342 diaElem *elems[]={&mMode,&tIvtc,&tResize,&tWidth,&tHeight};
343
344 if(diaFactoryRun(QT_TRANSLATE_NOOP("vdpaudeint","vdpau"),sizeof(elems)/sizeof(diaElem *),elems))
345 {
346 configuration.resizeToggle=(bool)doResize;
347 configuration.enableIvtc=doIvtc;
348 updateConf();
349 if(doResize)
350 ADM_info("New dimension : %d x %d\n",info.width,info.height);
351 cleanupVdpau();
352 passThrough=!setupVdpau();
353
354 return 1;
355 }
356 return 0;
357
358 }
359 /**
360 \fn getCoupledConf
361 \brief Return our current configuration as couple name=value
362 */
getCoupledConf(CONFcouple ** couples)363 bool vdpauVideoFilterDeint::getCoupledConf(CONFcouple **couples)
364 {
365 return ADM_paramSave(couples, vdpauFilterDeint_param,&configuration);
366 }
367
setCoupledConf(CONFcouple * couples)368 void vdpauVideoFilterDeint::setCoupledConf(CONFcouple *couples)
369 {
370 ADM_paramLoad(couples, vdpauFilterDeint_param, &configuration);
371 }
372
373 /**
374 \fn getConfiguration
375 \brief Return current setting as a string
376 */
getConfiguration(void)377 const char *vdpauVideoFilterDeint::getConfiguration(void)
378 {
379 static char conf[80];
380 sprintf(conf,"Vdpau Deinterlace mode=%d, %d x %d",configuration.deintMode,info.width,info.height);
381 conf[79]=0;
382 return conf;
383 }
384 /**
385 \fn uploadImage
386 \brief upload an image to a vdpau surface
387 */
uploadImage(ADMImage * next,VdpVideoSurface surface)388 bool vdpauVideoFilterDeint::uploadImage(ADMImage *next,VdpVideoSurface surface)
389 {
390 if(!next) // empty image
391 {
392 ADM_warning("VdpauDeint:No image to upload\n");
393 return true;
394 }
395 if(surface==VDP_INVALID_HANDLE)
396 {
397 ADM_error("Surface provided is invalid\n");
398 return false;
399 }
400 // Blit our image to surface
401 int ipitches[3];
402 uint32_t pitches[3];
403 uint8_t *planes[3];
404 next->GetPitches(ipitches);
405 next->GetReadPlanes(planes);
406
407 for(int i=0;i<3;i++) pitches[i]=(uint32_t)ipitches[i];
408
409 aprintf("Putting image in surface %d\n",(int)surface);
410 // Put out stuff in input...
411 #if VDP_DEBUG
412 printf("Uploading image to surface %d\n",surfaceIndex%ADM_NB_SURFACES);
413 #endif
414 if(VDP_STATUS_OK!=admVdpau::surfacePutBits(
415 surface,
416 planes,pitches))
417 {
418 ADM_warning("[Vdpau] video surface : Cannot putbits\n");
419 return false;
420 }
421 return true;
422 }
423 /**
424 \fn fillSlot
425 \brief upload the image to the slot.
426 */
fillSlot(int slot,ADMImage * image)427 bool vdpauVideoFilterDeint::fillSlot(int slot,ADMImage *image)
428 {
429 VdpVideoSurface tgt;
430 bool external=false;
431 if(image->refType!=ADM_HW_VDPAU)
432 { // Need to allocate a surface
433 ADM_assert(freeSurface.size());
434 tgt=freeSurface.front();
435 freeSurface.pop_front();
436 aprintf("FillSlot : Popped %d\n",tgt);
437 //
438 if(false==uploadImage(image,tgt))
439 {
440 return false;
441 }
442 external=false;
443 }else
444 { // use the provided surface
445 aprintf("Deint Image is already vdpau, slot %d \n",slot);
446 ADMImage *img=slots[slot].image;
447 img->duplicateFull(image); // increment ref count
448 // get surface
449 img->hwDownloadFromRef();
450 ADM_vdpauRenderState *render=(ADM_vdpauRenderState *)img->refDescriptor.refHwImage;
451 ADM_assert(render->refCount);
452 tgt=render->surface;
453 external=true;
454 }
455 slots[slot].pts=image->Pts;
456 slots[slot].surface=tgt;
457 slots[slot].isExternal=external;
458 return true;
459 }
460
461 /**
462 \fn rotateSlots
463 */
rotateSlots(void)464 bool vdpauVideoFilterDeint::rotateSlots(void)
465 {
466 VDPSlot *s=&(slots[0]);
467 ADMImage *img=slots[0].image;
468 if(s->surface!=VDP_INVALID_HANDLE)
469 {
470 if(!s->isExternal)
471 {
472 freeSurface.push_back(s->surface);
473 s->surface=VDP_INVALID_HANDLE;
474 }else
475 {
476 // Ref couting dec..
477 s->image->hwDecRefCount();
478 s->surface=VDP_INVALID_HANDLE;
479 }
480 }
481 slots[0]=slots[1];
482 slots[1]=slots[2];
483 slots[2].surface=VDP_INVALID_HANDLE;
484 slots[2].image=img;
485 return true;
486 }
487 /**
488 \fn clearSlots
489 */
clearSlots(void)490 bool vdpauVideoFilterDeint::clearSlots(void)
491 {
492 for(int i=0;i<3;i++)
493 {
494 VDPSlot *s=&(slots[i]);
495 if(s->surface!=VDP_INVALID_HANDLE)
496 {
497 if(s->isExternal)
498 {
499 s->image->hwDecRefCount();
500 }else
501 {
502 freeSurface.push_back(s->surface);
503 }
504 }
505 s->surface=VDP_INVALID_HANDLE;
506 }
507 return true;
508 }
509 /**
510 \fn sendField
511 \brief Process a field (top or bottom). If null the next param means there is no successor (next image)
512 */
sendField(bool topField)513 bool vdpauVideoFilterDeint::sendField(bool topField)
514 {
515 // Call mixer...
516 VdpVideoSurface in[3];
517 bool r=true;
518 // PREVIOUS
519 for(int i=0;i<3;i++)
520 {
521 in[i]=slots[i].surface;
522 aprintf("Mixing %d %d\n",i,(int)in[i]);
523 }
524 if(in[0]==VDP_INVALID_HANDLE)
525 in[0]=in[1];
526 //
527
528 #ifdef DO_BENCHMARK
529 ADMBenchmark bmark;
530 for(int i=0;i<NB_BENCH;i++)
531 {
532 bmark.start();
533 #endif
534
535
536 // ---------- Top field ------------
537 if(VDP_STATUS_OK!=admVdpau::mixerRenderFieldWithPastAndFuture(topField,
538 mixer,
539 in,
540 outputSurface,
541 getInfo()->width,getInfo()->height,
542 previousFilter->getInfo()->width,previousFilter->getInfo()->height))
543
544 {
545 ADM_warning("[Vdpau] Cannot mixerRender\n");
546 r= false;
547 }
548
549 #ifdef DO_BENCHMARK
550 bmark.end();
551 }
552 ADM_warning("Mixer Benchmark\n");
553 bmark.printResult();
554 #endif
555 return r;
556 }
557 /**
558 \fn getResult
559 \brief Convert the output surface into an ADMImage
560 */
getResult(ADMImage * image)561 bool vdpauVideoFilterDeint::getResult(ADMImage *image)
562 {
563
564 ADM_assert(image->GetWidth(PLANAR_Y)==info.width);
565 ADM_assert(image->GetHeight(PLANAR_Y)==info.height);
566 if(VDP_STATUS_OK!=admVdpau::outputSurfaceGetBitsNative(outputSurface,
567 tempBuffer,
568 info.width,info.height))
569 {
570 ADM_warning("[Vdpau] Cannot copy back data from output surface\n");
571 return false;
572 }
573 return image->convertFromYUV444(tempBuffer);
574 }
575 /**
576 \fn getNextFrame
577 \brief
578
579 */
getNextFrame(uint32_t * fn,ADMImage * image)580 bool vdpauVideoFilterDeint::getNextFrame(uint32_t *fn,ADMImage *image)
581 {
582 bool r=true;
583 if(eof)
584 {
585 ADM_warning("[VdpauDeint] End of stream\n");
586 return false;
587 }
588 #define FAIL {r=false;goto endit;}
589 if(passThrough) return previousFilter->getNextFrame(fn,image);
590 // top field has already been sent, grab bottom field
591 if((secondField)&&(configuration.deintMode==ADM_KEEP_BOTH))
592 {
593 secondField=false;
594 *fn=nextFrame*2+1;
595 if(false==getResult(image)) return false;
596 if(ADM_NO_PTS==nextPts) image->Pts=ADM_NO_PTS;
597 else
598 {
599 aprintf("2nd field: nextPts = %s - frame increment = %d ms =",ADM_us2plain(nextPts),(int)(info.frameIncrement/1000LL));
600 image->Pts=nextPts-info.frameIncrement;
601 aprintf(" %s\n",ADM_us2plain(image->Pts));
602 }
603 return true;
604 }
605 // shift frames;... free slot[0]
606 rotateSlots();
607
608 // our first frame, we need to preload one frame
609 if(!nextFrame)
610 {
611 aprintf("This is our first image, filling slot 1\n");
612 ADMImage *prev= vidCache->getImageAs(ADM_HW_VDPAU,0);
613 if(!prev || false==fillSlot(1,prev))
614 {
615 vidCache->unlockAll();
616 return false;
617 }
618 nextPts=prev->Pts;
619 }
620 // regular image, in fact we get the next image here
621 ADMImage *next= vidCache->getImageAs(ADM_HW_VDPAU,nextFrame+1);
622 if(next)
623 {
624 if(false==fillSlot(2,next))
625 {
626 vidCache->unlockAll();
627 FAIL
628 }
629 }
630 if(!next) eof=true; // End of stream
631
632 // now we have slot 0 : prev Image, slot 1=current image, slot 2=next image
633
634 // Now get our image back from surface...
635 sendField(true); // always send top field
636 if(configuration.deintMode==ADM_KEEP_TOP || configuration.deintMode==ADM_KEEP_BOTH)
637 {
638 if(false==getResult(image))
639 {
640 FAIL
641 }
642 aprintf("TOP/BOTH : Pts=%s\n",ADM_us2plain(image->Pts));
643 }
644 // Send 2nd field
645 sendField(false);
646 if(configuration.deintMode==ADM_KEEP_BOTTOM)
647 {
648 if(false==getResult(image))
649 {
650 FAIL
651 }
652 aprintf("BOTTOM : Pts=%s\n",ADM_us2plain(image->Pts));
653 }
654 // Top Field..
655 endit:
656 vidCache->unlockAll();
657 if(configuration.deintMode==ADM_KEEP_BOTH)
658 {
659 *fn=nextFrame*2;
660 secondField=true;
661 }
662 else *fn=nextFrame;
663 nextFrame++;
664 image->Pts=nextPts;
665 if(next) nextPts=next->Pts;
666 aprintf("VDPAU OUT PTS: %s\n",ADM_us2plain(image->Pts));
667 return r;
668 }
669 #else // USE_VDPAU
dummy_fun(void)670 static void dummy_fun(void)
671 {
672 return ;
673 }
674 #endif // use VDPAU
675
676 //****************
677 // EOF
678