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