00001 #include "mythcontext.h"
00002 #include "mythplayer.h"
00003 #include "videooutbase.h"
00004 #include "videoout_vdpau.h"
00005 #include "videodisplayprofile.h"
00006 #include "osd.h"
00007 #include "mythxdisplay.h"
00008 #include "mythmainwindow.h"
00009 #include "mythuihelper.h"
00010 #include "mythpainter_vdpau.h"
00011
00012 #define LOC QString("VidOutVDPAU: ")
00013
00014 #define MIN_REFERENCE_FRAMES 2
00015 #define MAX_REFERENCE_FRAMES 16
00016 #define MIN_PROCESS_BUFFER 6
00017 #define MAX_PROCESS_BUFFER 50
00018 #define DEF_PROCESS_BUFFER 12
00019
00020 #define CHECK_ERROR(Loc) \
00021 if (m_render && m_render->IsErrored()) \
00022 errorState = kError_Unknown; \
00023 if (IsErrored()) \
00024 { \
00025 LOG(VB_GENERAL, LOG_ERR, LOC + QString("IsErrored() in %1").arg(Loc)); \
00026 return; \
00027 } while(0)
00028
00029 void VideoOutputVDPAU::GetRenderOptions(render_opts &opts)
00030 {
00031 opts.renderers->append("vdpau");
00032 (*opts.osds)["vdpau"].append("vdpau");
00033 if (opts.decoders->contains("vdpau"))
00034 (*opts.safe_renderers)["vdpau"].append("vdpau");
00035 if (opts.decoders->contains("ffmpeg"))
00036 (*opts.safe_renderers)["ffmpeg"].append("vdpau");
00037 if (opts.decoders->contains("crystalhd"))
00038 (*opts.safe_renderers)["crystalhd"].append("vdpau");
00039 (*opts.safe_renderers)["dummy"].append("vdpau");
00040 (*opts.safe_renderers)["nuppel"].append("vdpau");
00041
00042 opts.priorities->insert("vdpau", 120);
00043 QStringList deints;
00044 deints += "none";
00045 deints += "vdpauonefield";
00046 deints += "vdpaubobdeint";
00047 deints += "vdpaubasic";
00048 deints += "vdpauadvanced";
00049 deints += "vdpaubasicdoublerate";
00050 deints += "vdpauadvanceddoublerate";
00051 opts.deints->insert("vdpau", deints);
00052 }
00053
00054 VideoOutputVDPAU::VideoOutputVDPAU()
00055 : m_win(0), m_render(NULL),
00056 m_decoder_buffer_size(MAX_REFERENCE_FRAMES),
00057 m_process_buffer_size(DEF_PROCESS_BUFFER), m_pause_surface(0),
00058 m_need_deintrefs(false), m_video_mixer(0), m_mixer_features(kVDPFeatNone),
00059 m_checked_surface_ownership(false),
00060 m_checked_output_surfaces(false),
00061 m_decoder(0), m_pix_fmt(-1),
00062 m_lock(QMutex::Recursive), m_pip_layer(0), m_pip_surface(0),
00063 m_pip_ready(false), m_osd_painter(NULL),
00064 m_skip_chroma(false), m_denoise(0.0f),
00065 m_sharpen(0.0f),
00066 m_colorspace(VDP_COLOR_STANDARD_ITUR_BT_601)
00067 {
00068 if (gCoreContext->GetNumSetting("UseVideoModes", 0))
00069 display_res = DisplayRes::GetDisplayRes(true);
00070 }
00071
00072 VideoOutputVDPAU::~VideoOutputVDPAU()
00073 {
00074 QMutexLocker locker(&m_lock);
00075 TearDown();
00076 }
00077
00078 void VideoOutputVDPAU::TearDown(void)
00079 {
00080 QMutexLocker locker(&m_lock);
00081 DeinitPIPS();
00082 DeinitPIPLayer();
00083 DeleteBuffers();
00084 RestoreDisplay();
00085 DeleteRender();
00086 }
00087
00088 bool VideoOutputVDPAU::Init(int width, int height, float aspect,
00089 WId winid, const QRect &win_rect,
00090 MythCodecID codec_id)
00091 {
00092
00093
00094 MythPainter *painter = GetMythPainter();
00095 if (painter)
00096 painter->FreeResources();
00097
00098 m_win = winid;
00099 QMutexLocker locker(&m_lock);
00100 window.SetNeedRepaint(true);
00101 bool ok = VideoOutput::Init(width, height, aspect, winid, win_rect,codec_id);
00102 if (db_vdisp_profile)
00103 db_vdisp_profile->SetVideoRenderer("vdpau");
00104
00105 InitDisplayMeasurements(width, height, true);
00106 ParseOptions();
00107 if (ok) ok = InitRender();
00108 if (ok) ok = InitBuffers();
00109 if (!ok)
00110 {
00111 TearDown();
00112 return ok;
00113 }
00114
00115 InitPictureAttributes();
00116 MoveResize();
00117 LOG(VB_PLAYBACK, LOG_INFO, LOC +
00118 QString("Created VDPAU context (%1 decode)")
00119 .arg(codec_is_std(video_codec_id) ? "software" : "GPU"));
00120
00121 return ok;
00122 }
00123
00124 bool VideoOutputVDPAU::InitRender(void)
00125 {
00126 QMutexLocker locker(&m_lock);
00127
00128 const QSize size = window.GetDisplayVisibleRect().size();
00129 const QRect rect = QRect(QPoint(0,0), size);
00130 m_render = new MythRenderVDPAU();
00131
00132 if (m_render && m_render->Create(size, m_win))
00133 {
00134 m_osd_painter = new MythVDPAUPainter(m_render);
00135 if (m_osd_painter)
00136 {
00137 m_osd_painter->SetSwapControl(false);
00138 LOG(VB_PLAYBACK, LOG_INFO, LOC +
00139 QString("Created VDPAU osd (%1x%2)")
00140 .arg(size.width()).arg(size.height()));
00141 }
00142 else
00143 LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to create VDPAU osd.");
00144 return true;
00145 }
00146
00147 LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to initialise VDPAU");
00148
00149 return false;
00150 }
00151
00152 void VideoOutputVDPAU::DeleteRender(void)
00153 {
00154 QMutexLocker locker(&m_lock);
00155
00156 if (m_osd_painter)
00157 delete m_osd_painter;
00158
00159 if (m_render)
00160 {
00161 if (m_decoder)
00162 m_render->DestroyDecoder(m_decoder);
00163
00164 delete m_render;
00165 }
00166
00167 m_checked_output_surfaces = false;
00168 m_osd_painter = NULL;
00169 m_decoder = 0;
00170 m_render = NULL;
00171 m_pix_fmt = -1;
00172 }
00173
00174 bool VideoOutputVDPAU::InitBuffers(void)
00175 {
00176 QMutexLocker locker(&m_lock);
00177 if (!m_render)
00178 return false;
00179
00180 uint buffer_size = m_decoder_buffer_size + m_process_buffer_size;
00181 const QSize video_dim = codec_is_std(video_codec_id) ?
00182 window.GetVideoDim() : window.GetActualVideoDim();
00183
00184 vbuffers.Init(buffer_size, false, 2, 1, 4, 1);
00185
00186 bool ok = false;
00187 if (codec_is_vdpau(video_codec_id))
00188 {
00189 ok = CreateVideoSurfaces(buffer_size);
00190 if (ok)
00191 {
00192 for (int i = 0; i < m_video_surfaces.size(); i++)
00193 ok &= vbuffers.CreateBuffer(video_dim.width(),
00194 video_dim.height(), i,
00195 m_render->GetRender(m_video_surfaces[i]),
00196 FMT_VDPAU);
00197 }
00198 }
00199 else if (codec_is_std(video_codec_id))
00200 {
00201 ok = CreateVideoSurfaces(NUM_REFERENCE_FRAMES);
00202 if (ok)
00203 ok = vbuffers.CreateBuffers(FMT_YV12,
00204 video_dim.width(), video_dim.height());
00205 }
00206
00207 if (!ok)
00208 {
00209 LOG(VB_GENERAL, LOG_ERR, LOC + "Unable to create VDPAU buffers");
00210 }
00211 else
00212 {
00213 m_video_mixer = m_render->CreateVideoMixer(video_dim, 2,
00214 m_mixer_features);
00215 ok = m_video_mixer;
00216 m_pause_surface = m_video_surfaces[0];
00217
00218 if (ok && (m_mixer_features & kVDPFeatSharpness))
00219 m_render->SetMixerAttribute(m_video_mixer,
00220 kVDPAttribSharpness,
00221 m_sharpen);
00222 if (ok && (m_mixer_features & kVDPFeatDenoise))
00223 m_render->SetMixerAttribute(m_video_mixer,
00224 kVDPAttribNoiseReduction,
00225 m_denoise);
00226 if (ok && m_skip_chroma)
00227 m_render->SetMixerAttribute(m_video_mixer,
00228 kVDPAttribSkipChroma, 1);
00229
00230 if (ok && (db_letterbox_colour == kLetterBoxColour_Gray25))
00231 m_render->SetMixerAttribute(m_video_mixer,
00232 kVDPAttribBackground, 0x7F7F7FFF);
00233 }
00234
00235 if (!ok)
00236 {
00237 LOG(VB_GENERAL, LOG_ERR, LOC + "Unable to create VDPAU mixer");
00238 DeleteBuffers();
00239 }
00240
00241 return ok;
00242 }
00243
00244 bool VideoOutputVDPAU::CreateVideoSurfaces(uint num)
00245 {
00246 if (!m_render || num < 1)
00247 return false;
00248
00249 bool ret = true;
00250 const QSize size = codec_is_std(video_codec_id) ?
00251 window.GetVideoDim() : window.GetActualVideoDim();
00252 for (uint i = 0; i < num; i++)
00253 {
00254 uint tmp = m_render->CreateVideoSurface(size);
00255 if (tmp)
00256 {
00257 m_video_surfaces.push_back(tmp);
00258 m_render->ClearVideoSurface(tmp);
00259 }
00260 else
00261 {
00262 ret = false;
00263 break;
00264 }
00265 }
00266 return ret;
00267 }
00268
00269 void VideoOutputVDPAU::DeleteVideoSurfaces(void)
00270 {
00271 if (!m_render || !m_video_surfaces.size())
00272 return;
00273
00274 for (int i = 0; i < m_video_surfaces.size(); i++)
00275 m_render->DestroyVideoSurface(m_video_surfaces[i]);
00276 m_video_surfaces.clear();
00277 }
00278
00279 void VideoOutputVDPAU::DeleteBuffers(void)
00280 {
00281 QMutexLocker locker(&m_lock);
00282 if (m_render && m_video_mixer)
00283 m_render->DestroyVideoMixer(m_video_mixer);
00284 m_video_mixer = 0;
00285 m_checked_surface_ownership = false;
00286 DiscardFrames(true);
00287 DeleteVideoSurfaces();
00288 vbuffers.Reset();
00289 vbuffers.DeleteBuffers();
00290 }
00291
00292 void VideoOutputVDPAU::RestoreDisplay(void)
00293 {
00294 QMutexLocker locker(&m_lock);
00295
00296 const QRect tmp_display_visible_rect =
00297 window.GetTmpDisplayVisibleRect();
00298 if (window.GetPIPState() == kPIPStandAlone &&
00299 !tmp_display_visible_rect.isEmpty())
00300 {
00301 window.SetDisplayVisibleRect(tmp_display_visible_rect);
00302 }
00303 const QRect display_visible_rect = window.GetDisplayVisibleRect();
00304
00305 if (m_render)
00306 m_render->DrawDisplayRect(display_visible_rect);
00307 }
00308
00309 bool VideoOutputVDPAU::SetDeinterlacingEnabled(bool interlaced)
00310 {
00311 if ((interlaced && m_deinterlacing) ||
00312 (!interlaced && !m_deinterlacing))
00313 return m_deinterlacing;
00314
00315 return SetupDeinterlace(interlaced);
00316 }
00317
00318 bool VideoOutputVDPAU::SetupDeinterlace(bool interlaced,
00319 const QString &override)
00320 {
00321 m_lock.lock();
00322 if (!m_render)
00323 return false;
00324
00325 bool enable = interlaced;
00326 if (enable)
00327 {
00328 m_deintfiltername = db_vdisp_profile->GetFilteredDeint(override);
00329 if (m_deintfiltername.contains("vdpau"))
00330 {
00331 uint features = kVDPFeatNone;
00332 bool spatial = m_deintfiltername.contains("advanced");
00333 bool temporal = m_deintfiltername.contains("basic") || spatial;
00334 m_need_deintrefs = spatial || temporal;
00335
00336 if (temporal)
00337 features += kVDPFeatTemporal;
00338
00339 if (spatial)
00340 features += kVDPFeatSpatial;
00341
00342 enable = m_render->SetDeinterlacing(m_video_mixer, features);
00343 if (enable)
00344 {
00345 m_deinterlacing = true;
00346 LOG(VB_PLAYBACK, LOG_INFO, LOC + "Enabled deinterlacing.");
00347 }
00348 else
00349 {
00350 enable = false;
00351 LOG(VB_PLAYBACK, LOG_INFO, LOC +
00352 "Failed to enable deinterlacing.");
00353 }
00354 }
00355 else
00356 {
00357 enable = false;
00358 }
00359 }
00360
00361 if (!enable)
00362 {
00363 ClearReferenceFrames();
00364 m_render->SetDeinterlacing(m_video_mixer);
00365 m_deintfiltername = QString();
00366 m_deinterlacing = false;
00367 m_need_deintrefs = false;
00368 }
00369 m_lock.unlock();
00370 return enable;
00371 }
00372
00373 bool VideoOutputVDPAU::ApproveDeintFilter(const QString &filtername) const
00374 {
00375 return filtername.contains("vdpau");
00376 }
00377
00378 void VideoOutputVDPAU::ProcessFrame(VideoFrame *frame, OSD *osd,
00379 FilterChain *filterList,
00380 const PIPMap &pipPlayers,
00381 FrameScanType scan)
00382 {
00383 QMutexLocker locker(&m_lock);
00384 CHECK_ERROR("ProcessFrame");
00385
00386 if (!m_checked_surface_ownership && codec_is_std(video_codec_id))
00387 ClaimVideoSurfaces();
00388
00389 m_pip_ready = false;
00390 ShowPIPs(frame, pipPlayers);
00391 }
00392
00393 void VideoOutputVDPAU::ClearDummyFrame(VideoFrame *frame)
00394 {
00395 if (frame && m_render && !codec_is_std(video_codec_id))
00396 {
00397 struct vdpau_render_state *render =
00398 (struct vdpau_render_state *)frame->buf;
00399 if (render)
00400 m_render->ClearVideoSurface(render->surface);
00401 }
00402 VideoOutput::ClearDummyFrame(frame);
00403 }
00404
00405 void VideoOutputVDPAU::PrepareFrame(VideoFrame *frame, FrameScanType scan,
00406 OSD *osd)
00407 {
00408 QMutexLocker locker(&m_lock);
00409 (void)osd;
00410 CHECK_ERROR("PrepareFrame");
00411
00412 if (!m_render)
00413 return;
00414
00415 if (!m_checked_output_surfaces &&
00416 !(!codec_is_std(video_codec_id) && !m_decoder))
00417 {
00418 m_render->CheckOutputSurfaces();
00419 m_checked_output_surfaces = true;
00420 }
00421
00422 bool new_frame = false;
00423 bool dummy = false;
00424 if (frame)
00425 {
00426
00427 if ((abs(frame->frameNumber - framesPlayed) > 8))
00428 ClearReferenceFrames();
00429 new_frame = (framesPlayed != frame->frameNumber + 1);
00430 framesPlayed = frame->frameNumber + 1;
00431 dummy = frame->dummy;
00432 }
00433
00434 uint video_surface = m_video_surfaces[0];
00435 bool deint = (m_deinterlacing && m_need_deintrefs &&
00436 frame && !dummy);
00437
00438 if (deint)
00439 {
00440 if (new_frame)
00441 UpdateReferenceFrames(frame);
00442 if (m_reference_frames.size() != NUM_REFERENCE_FRAMES)
00443 deint = false;
00444 }
00445
00446 if (!codec_is_std(video_codec_id) && frame)
00447 {
00448 struct vdpau_render_state *render =
00449 (struct vdpau_render_state *)frame->buf;
00450 if (!render)
00451 return;
00452 video_surface = m_render->GetSurfaceOwner(render->surface);
00453 }
00454 else if (new_frame && frame && !dummy)
00455 {
00456
00457 if (deint)
00458 video_surface = m_video_surfaces[(framesPlayed + 1) %
00459 NUM_REFERENCE_FRAMES];
00460
00461 uint32_t pitches[3] = {
00462 frame->pitches[0],
00463 frame->pitches[2],
00464 frame->pitches[1]
00465 };
00466 void* const planes[3] = {
00467 frame->buf,
00468 frame->buf + frame->offsets[2],
00469 frame->buf + frame->offsets[1]
00470 };
00471
00472 if (!m_render->UploadYUVFrame(video_surface, planes, pitches))
00473 return;
00474 }
00475 else if (!frame)
00476 {
00477 deint = false;
00478 video_surface = m_pause_surface;
00479 }
00480
00481 VdpVideoMixerPictureStructure field =
00482 VDP_VIDEO_MIXER_PICTURE_STRUCTURE_FRAME;
00483
00484 if (scan == kScan_Interlaced && m_deinterlacing && frame)
00485 {
00486 field = frame->top_field_first ?
00487 VDP_VIDEO_MIXER_PICTURE_STRUCTURE_TOP_FIELD :
00488 VDP_VIDEO_MIXER_PICTURE_STRUCTURE_BOTTOM_FIELD;
00489 }
00490 else if (scan == kScan_Intr2ndField && m_deinterlacing && frame)
00491 {
00492 field = frame->top_field_first ?
00493 VDP_VIDEO_MIXER_PICTURE_STRUCTURE_BOTTOM_FIELD :
00494 VDP_VIDEO_MIXER_PICTURE_STRUCTURE_TOP_FIELD;
00495 }
00496 else if (!frame && m_deinterlacing)
00497 {
00498 field = VDP_VIDEO_MIXER_PICTURE_STRUCTURE_TOP_FIELD;
00499 }
00500
00501 m_render->WaitForFlip();
00502
00503 QSize size = window.GetDisplayVisibleRect().size();
00504 if (size != m_render->GetSize())
00505 LOG(VB_GENERAL, LOG_ERR, LOC + "Unexpected display size.");
00506
00507 if (dummy)
00508 {
00509 m_render->DrawBitmap(0, 0, NULL, NULL, kVDPBlendNormal, 255);
00510 }
00511 else
00512 {
00513 if (!m_render->MixAndRend(m_video_mixer, field, video_surface, 0,
00514 deint ? &m_reference_frames : NULL,
00515 scan == kScan_Interlaced,
00516 window.GetVideoRect(),
00517 QRect(QPoint(0,0), size),
00518 vsz_enabled ? vsz_desired_display_rect :
00519 window.GetDisplayVideoRect(),
00520 0, 0))
00521 {
00522 LOG(VB_PLAYBACK, LOG_ERR, LOC + "Prepare frame failed.");
00523 }
00524 }
00525
00526 if (m_pip_ready)
00527 m_render->DrawLayer(m_pip_layer, 0);
00528 if (m_visual)
00529 m_visual->Draw(GetTotalOSDBounds(), m_osd_painter, NULL);
00530
00531 if (osd && m_osd_painter && !window.IsEmbedding())
00532 osd->DrawDirect(m_osd_painter, GetTotalOSDBounds().size(), true);
00533
00534 if (!frame)
00535 {
00536 VideoFrame *buf = GetLastShownFrame();
00537 if (buf)
00538 buf->timecode = 0;
00539 }
00540 }
00541
00542 void VideoOutputVDPAU::ClaimVideoSurfaces(void)
00543 {
00544 if (!m_render)
00545 return;
00546
00547 QVector<uint>::iterator it;
00548 for (it = m_video_surfaces.begin(); it != m_video_surfaces.end(); ++it)
00549 m_render->ChangeVideoSurfaceOwner(*it);
00550 m_checked_surface_ownership = true;
00551 }
00552
00553 void VideoOutputVDPAU::DrawSlice(VideoFrame *frame, int x, int y, int w, int h)
00554 {
00555 (void)x;
00556 (void)y;
00557 (void)w;
00558 (void)h;
00559
00560 CHECK_ERROR("DrawSlice");
00561
00562 if (codec_is_std(video_codec_id) || !m_render)
00563 return;
00564
00565 if (!m_checked_surface_ownership)
00566 ClaimVideoSurfaces();
00567
00568 struct vdpau_render_state *render = (struct vdpau_render_state *)frame->buf;
00569 if (!render)
00570 {
00571 LOG(VB_GENERAL, LOG_ERR, LOC + "No video surface to decode to.");
00572 errorState = kError_Unknown;
00573 return;
00574 }
00575
00576 if (frame->pix_fmt != m_pix_fmt)
00577 {
00578 if (m_decoder)
00579 {
00580 LOG(VB_GENERAL, LOG_ERR, LOC + "Picture format has changed.");
00581 errorState = kError_Unknown;
00582 return;
00583 }
00584
00585 uint max_refs = MIN_REFERENCE_FRAMES;
00586 if (frame->pix_fmt == PIX_FMT_VDPAU_H264)
00587 {
00588 max_refs = render->info.h264.num_ref_frames;
00589 if (max_refs < 1 || max_refs > MAX_REFERENCE_FRAMES)
00590 {
00591 uint32_t round_width = (frame->width + 15) & ~15;
00592 uint32_t round_height = (frame->height + 15) & ~15;
00593 uint32_t surf_size = (round_width * round_height * 3) / 2;
00594 max_refs = (12 * 1024 * 1024) / surf_size;
00595 }
00596 if (max_refs > MAX_REFERENCE_FRAMES)
00597 max_refs = MAX_REFERENCE_FRAMES;
00598
00599
00600 int needed = max_refs - m_decoder_buffer_size;
00601 if (needed > 0)
00602 {
00603 QMutexLocker locker(&m_lock);
00604 const QSize size = window.GetActualVideoDim();
00605 uint created = 0;
00606 for (int i = 0; i < needed; i++)
00607 {
00608 uint tmp = m_render->CreateVideoSurface(size);
00609 if (tmp)
00610 {
00611 m_video_surfaces.push_back(tmp);
00612 m_render->ClearVideoSurface(tmp);
00613 if (vbuffers.AddBuffer(size.width(), size.height(),
00614 m_render->GetRender(tmp),
00615 FMT_VDPAU))
00616 {
00617 created++;
00618 }
00619 }
00620 }
00621 m_decoder_buffer_size += created;
00622 LOG(VB_GENERAL, LOG_INFO, LOC +
00623 QString("Added %1 new buffers. New buffer size %2 "
00624 "(%3 decode and %4 process)")
00625 .arg(created).arg(vbuffers.Size())
00626 .arg(m_decoder_buffer_size)
00627 .arg(m_process_buffer_size));
00628 }
00629 }
00630
00631 VdpDecoderProfile vdp_decoder_profile;
00632 switch (frame->pix_fmt)
00633 {
00634 case PIX_FMT_VDPAU_MPEG1:
00635 vdp_decoder_profile = VDP_DECODER_PROFILE_MPEG1;
00636 break;
00637 case PIX_FMT_VDPAU_MPEG2:
00638 vdp_decoder_profile = VDP_DECODER_PROFILE_MPEG2_MAIN;
00639 break;
00640 case PIX_FMT_VDPAU_MPEG4:
00641 vdp_decoder_profile = VDP_DECODER_PROFILE_MPEG4_PART2_ASP;
00642 break;
00643 case PIX_FMT_VDPAU_H264:
00644 vdp_decoder_profile = VDP_DECODER_PROFILE_H264_HIGH;
00645 break;
00646 case PIX_FMT_VDPAU_WMV3:
00647 vdp_decoder_profile = VDP_DECODER_PROFILE_VC1_MAIN;
00648 break;
00649 case PIX_FMT_VDPAU_VC1:
00650 vdp_decoder_profile = VDP_DECODER_PROFILE_VC1_ADVANCED;
00651 break;
00652 default:
00653 LOG(VB_GENERAL, LOG_ERR, LOC +
00654 "Picture format is not supported.");
00655 errorState = kError_Unknown;
00656 return;
00657 }
00658
00659 m_decoder = m_render->CreateDecoder(window.GetActualVideoDim(),
00660 vdp_decoder_profile, max_refs);
00661 if (m_decoder)
00662 {
00663 m_pix_fmt = frame->pix_fmt;
00664 LOG(VB_PLAYBACK, LOG_INFO, LOC +
00665 QString("Created VDPAU decoder (%1 ref frames)")
00666 .arg(max_refs));
00667 }
00668 else
00669 {
00670 LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to create decoder.");
00671 errorState = kError_Unknown;
00672 return;
00673 }
00674 }
00675 else if (!m_decoder)
00676 {
00677 LOG(VB_GENERAL, LOG_ERR, LOC +
00678 "Pix format already set but no VDPAU decoder.");
00679 errorState = kError_Unknown;
00680 return;
00681 }
00682
00683 m_render->Decode(m_decoder, render);
00684 }
00685
00686 void VideoOutputVDPAU::Show(FrameScanType scan)
00687 {
00688 QMutexLocker locker(&m_lock);
00689 CHECK_ERROR("Show");
00690
00691 if (window.IsRepaintNeeded())
00692 DrawUnusedRects(false);
00693
00694 if (m_render)
00695 m_render->Flip();
00696 CheckFrameStates();
00697 }
00698
00699 void VideoOutputVDPAU::ClearAfterSeek(void)
00700 {
00701 m_lock.lock();
00702 LOG(VB_PLAYBACK, LOG_INFO, LOC + "ClearAfterSeek()");
00703 DiscardFrames(false);
00704 m_lock.unlock();
00705 }
00706
00707 bool VideoOutputVDPAU::InputChanged(const QSize &input_size,
00708 float aspect,
00709 MythCodecID av_codec_id,
00710 void *codec_private,
00711 bool &aspect_only)
00712 {
00713 LOG(VB_PLAYBACK, LOG_INFO, LOC +
00714 QString("InputChanged(%1,%2,%3) '%4'->'%5'")
00715 .arg(input_size.width()).arg(input_size.height()).arg(aspect)
00716 .arg(toString(video_codec_id)).arg(toString(av_codec_id)));
00717
00718 QMutexLocker locker(&m_lock);
00719
00720
00721
00722
00723 bool wasembedding = window.IsEmbedding();
00724 QRect oldrect;
00725 if (wasembedding)
00726 {
00727 oldrect = window.GetEmbeddingRect();
00728 StopEmbedding();
00729 }
00730
00731 bool cid_changed = (video_codec_id != av_codec_id);
00732 bool res_changed = input_size != window.GetActualVideoDim();
00733 bool asp_changed = aspect != window.GetVideoAspect();
00734
00735 if (!res_changed && !cid_changed)
00736 {
00737 aspect_only = true;
00738 if (asp_changed)
00739 {
00740 VideoAspectRatioChanged(aspect);
00741 MoveResize();
00742 }
00743 if (wasembedding)
00744 EmbedInWidget(oldrect);
00745 return true;
00746 }
00747
00748 TearDown();
00749 QRect disp = window.GetDisplayVisibleRect();
00750 if (Init(input_size.width(), input_size.height(),
00751 aspect, m_win, disp, av_codec_id))
00752 {
00753 if (wasembedding)
00754 EmbedInWidget(oldrect);
00755 BestDeint();
00756 return true;
00757 }
00758
00759 LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to re-initialise video output.");
00760 errorState = kError_Unknown;
00761
00762 return false;
00763 }
00764
00765 void VideoOutputVDPAU::Zoom(ZoomDirection direction)
00766 {
00767 QMutexLocker locker(&m_lock);
00768 VideoOutput::Zoom(direction);
00769 MoveResize();
00770 }
00771
00772 void VideoOutputVDPAU::VideoAspectRatioChanged(float aspect)
00773 {
00774 QMutexLocker locker(&m_lock);
00775 VideoOutput::VideoAspectRatioChanged(aspect);
00776 }
00777
00778 void VideoOutputVDPAU::EmbedInWidget(const QRect &rect)
00779 {
00780 QMutexLocker locker(&m_lock);
00781 if (!window.IsEmbedding())
00782 {
00783 VideoOutput::EmbedInWidget(rect);
00784 MoveResize();
00785 window.SetDisplayVisibleRect(window.GetTmpDisplayVisibleRect());
00786 }
00787 }
00788
00789 void VideoOutputVDPAU::StopEmbedding(void)
00790 {
00791 if (!window.IsEmbedding())
00792 return;
00793 QMutexLocker locker(&m_lock);
00794 VideoOutput::StopEmbedding();
00795 MoveResize();
00796 }
00797
00798 void VideoOutputVDPAU::MoveResizeWindow(QRect new_rect)
00799 {
00800 m_lock.lock();
00801 if (m_render)
00802 m_render->MoveResizeWin(new_rect);
00803 m_lock.unlock();
00804 }
00805
00806 void VideoOutputVDPAU::DrawUnusedRects(bool sync)
00807 {
00808 m_lock.lock();
00809 if (window.IsRepaintNeeded() && m_render)
00810 {
00811 const QRect dvr = window.GetDisplayVisibleRect();
00812 m_render->DrawDisplayRect(dvr, true);
00813 window.SetNeedRepaint(false);
00814 if (sync)
00815 m_render->SyncDisplay();
00816 }
00817 m_lock.unlock();
00818 }
00819
00820 void VideoOutputVDPAU::UpdatePauseFrame(int64_t &disp_timecode)
00821 {
00822 QMutexLocker locker(&m_lock);
00823
00824 LOG(VB_PLAYBACK, LOG_INFO, LOC + "UpdatePauseFrame() " +
00825 vbuffers.GetStatus());
00826
00827 vbuffers.begin_lock(kVideoBuffer_used);
00828
00829 if (vbuffers.size(kVideoBuffer_used) && m_render)
00830 {
00831 VideoFrame *frame = vbuffers.head(kVideoBuffer_used);
00832 disp_timecode = frame->disp_timecode;
00833 if (codec_is_std(video_codec_id))
00834 {
00835 m_pause_surface = m_video_surfaces[0];
00836 uint32_t pitches[3] = { frame->pitches[0],
00837 frame->pitches[2],
00838 frame->pitches[1] };
00839 void* const planes[3] = { frame->buf,
00840 frame->buf + frame->offsets[2],
00841 frame->buf + frame->offsets[1] };
00842 m_render->UploadYUVFrame(m_video_surfaces[0], planes, pitches);
00843 }
00844 else
00845 {
00846 struct vdpau_render_state *render =
00847 (struct vdpau_render_state *)frame->buf;
00848 if (render)
00849 m_pause_surface = m_render->GetSurfaceOwner(render->surface);
00850 }
00851 }
00852 else
00853 LOG(VB_PLAYBACK, LOG_WARNING, LOC +
00854 "Could not update pause frame - no used frames.");
00855
00856 vbuffers.end_lock();
00857 }
00858
00859 void VideoOutputVDPAU::InitPictureAttributes(void)
00860 {
00861 videoColourSpace.SetSupportedAttributes((PictureAttributeSupported)
00862 (kPictureAttributeSupported_Brightness |
00863 kPictureAttributeSupported_Contrast |
00864 kPictureAttributeSupported_Colour |
00865 kPictureAttributeSupported_Hue |
00866 kPictureAttributeSupported_StudioLevels));
00867
00868 m_lock.lock();
00869 if (m_render && m_video_mixer)
00870 {
00871 if (m_colorspace < 0)
00872 {
00873 QSize size = window.GetVideoDim();
00874 m_colorspace = (size.width() > 720 || size.height() > 576) ?
00875 VDP_COLOR_STANDARD_ITUR_BT_709 :
00876 VDP_COLOR_STANDARD_ITUR_BT_601;
00877 LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("Using ITU %1 colorspace")
00878 .arg((m_colorspace == VDP_COLOR_STANDARD_ITUR_BT_601) ?
00879 "BT.601" : "BT.709"));
00880 }
00881
00882 if (m_colorspace != VDP_COLOR_STANDARD_ITUR_BT_601)
00883 {
00884 videoColourSpace.SetColourSpace((m_colorspace == VDP_COLOR_STANDARD_ITUR_BT_601)
00885 ? kCSTD_ITUR_BT_601 : kCSTD_ITUR_BT_709);
00886 }
00887 m_render->SetCSCMatrix(m_video_mixer, videoColourSpace.GetMatrix());
00888 }
00889 m_lock.unlock();
00890 }
00891
00892 int VideoOutputVDPAU::SetPictureAttribute(PictureAttribute attribute,
00893 int newValue)
00894 {
00895 if (!m_render || !m_video_mixer)
00896 return -1;
00897
00898 m_lock.lock();
00899 newValue = videoColourSpace.SetPictureAttribute(attribute, newValue);
00900 if (newValue >= 0)
00901 m_render->SetCSCMatrix(m_video_mixer, videoColourSpace.GetMatrix());
00902 m_lock.unlock();
00903 return newValue;
00904 }
00905
00906 QStringList VideoOutputVDPAU::GetAllowedRenderers(
00907 MythCodecID myth_codec_id, const QSize &video_dim)
00908 {
00909 (void) video_dim;
00910 QStringList list;
00911 if ((codec_is_std(myth_codec_id) || codec_is_vdpau_hw(myth_codec_id)) &&
00912 !getenv("NO_VDPAU"))
00913 {
00914 list += "vdpau";
00915 }
00916
00917 return list;
00918 }
00919
00920 MythCodecID VideoOutputVDPAU::GetBestSupportedCodec(
00921 uint width, uint height, const QString &decoder,
00922 uint stream_type, bool no_acceleration)
00923 {
00924 bool use_cpu = no_acceleration;
00925
00926 MythCodecID test_cid = (MythCodecID)(kCodec_MPEG1_VDPAU + (stream_type-1));
00927 use_cpu |= !codec_is_vdpau_hw(test_cid);
00928 if (test_cid == kCodec_MPEG4_VDPAU)
00929 use_cpu |= !MythRenderVDPAU::IsMPEG4Available();
00930 if (test_cid == kCodec_H264_VDPAU)
00931 use_cpu |= !MythRenderVDPAU::H264DecoderSizeSupported(width, height);
00932 if ((decoder != "vdpau") || getenv("NO_VDPAU") || use_cpu)
00933 return (MythCodecID)(kCodec_MPEG1 + (stream_type-1));
00934
00935 return test_cid;
00936 }
00937
00938 void VideoOutputVDPAU::UpdateReferenceFrames(VideoFrame *frame)
00939 {
00940 while (m_reference_frames.size() > (NUM_REFERENCE_FRAMES - 1))
00941 m_reference_frames.pop_front();
00942
00943 uint ref = m_video_surfaces[(framesPlayed +1) % NUM_REFERENCE_FRAMES];
00944 if (!codec_is_std(video_codec_id))
00945 {
00946 struct vdpau_render_state *render =
00947 (struct vdpau_render_state *)frame->buf;
00948 if (render)
00949 ref = m_render->GetSurfaceOwner(render->surface);
00950 }
00951
00952 m_reference_frames.push_back(ref);
00953 }
00954
00955 bool VideoOutputVDPAU::FrameIsInUse(VideoFrame *frame)
00956 {
00957 if (!frame || codec_is_std(video_codec_id))
00958 return false;
00959
00960 uint ref = 0;
00961 struct vdpau_render_state *render = (struct vdpau_render_state *)frame->buf;
00962 if (render)
00963 ref = m_render->GetSurfaceOwner(render->surface);
00964 return m_reference_frames.contains(ref);
00965 }
00966
00967 void VideoOutputVDPAU::ClearReferenceFrames(void)
00968 {
00969 m_lock.lock();
00970 m_reference_frames.clear();
00971 m_lock.unlock();
00972 }
00973
00974 void VideoOutputVDPAU::DiscardFrame(VideoFrame *frame)
00975 {
00976 if (!frame)
00977 return;
00978
00979 m_lock.lock();
00980 if (FrameIsInUse(frame))
00981 vbuffers.safeEnqueue(kVideoBuffer_displayed, frame);
00982 else
00983 {
00984 vbuffers.DoneDisplayingFrame(frame);
00985 }
00986 m_lock.unlock();
00987 }
00988
00989 void VideoOutputVDPAU::DiscardFrames(bool next_frame_keyframe)
00990 {
00991 m_lock.lock();
00992 LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("DiscardFrames(%1)")
00993 .arg(next_frame_keyframe));
00994 CheckFrameStates();
00995 ClearReferenceFrames();
00996 vbuffers.DiscardFrames(next_frame_keyframe);
00997 LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("DiscardFrames() 3: %1 -- done()")
00998 .arg(vbuffers.GetStatus()));
00999 m_lock.unlock();
01000 }
01001
01002 void VideoOutputVDPAU::DoneDisplayingFrame(VideoFrame *frame)
01003 {
01004 m_lock.lock();
01005 if (vbuffers.contains(kVideoBuffer_used, frame))
01006 DiscardFrame(frame);
01007 CheckFrameStates();
01008 m_lock.unlock();
01009 }
01010
01011 void VideoOutputVDPAU::CheckFrameStates(void)
01012 {
01013 m_lock.lock();
01014 frame_queue_t::iterator it;
01015 it = vbuffers.begin_lock(kVideoBuffer_displayed);
01016 while (it != vbuffers.end(kVideoBuffer_displayed))
01017 {
01018 VideoFrame* frame = *it;
01019 if (!FrameIsInUse(frame))
01020 {
01021 if (vbuffers.contains(kVideoBuffer_decode, frame))
01022 {
01023 LOG(VB_PLAYBACK, LOG_INFO, LOC +
01024 QString("Frame %1 is in use by avlib and so is "
01025 "being held for later discarding.")
01026 .arg(DebugString(frame, true)));
01027 }
01028 else
01029 {
01030 vbuffers.safeEnqueue(kVideoBuffer_avail, frame);
01031 vbuffers.end_lock();
01032 it = vbuffers.begin_lock(kVideoBuffer_displayed);
01033 continue;
01034 }
01035 }
01036 ++it;
01037 }
01038 vbuffers.end_lock();
01039 m_lock.unlock();
01040 }
01041
01042 bool VideoOutputVDPAU::InitPIPLayer(QSize size)
01043 {
01044 if (!m_render)
01045 return false;
01046
01047 if (!m_pip_surface)
01048 m_pip_surface = m_render->CreateOutputSurface(size);
01049
01050 if (!m_pip_layer && m_pip_surface)
01051 m_pip_layer = m_render->CreateLayer(m_pip_surface);
01052
01053 return (m_pip_surface && m_pip_layer);
01054 }
01055
01056 void VideoOutputVDPAU::DeinitPIPS(void)
01057 {
01058 while (!m_pips.empty())
01059 {
01060 RemovePIP(m_pips.begin().key());
01061 m_pips.erase(m_pips.begin());
01062 }
01063
01064 m_pip_ready = false;
01065 }
01066
01067 void VideoOutputVDPAU::DeinitPIPLayer(void)
01068 {
01069 if (m_render)
01070 {
01071 if (m_pip_surface)
01072 {
01073 m_render->DestroyOutputSurface(m_pip_surface);
01074 m_pip_surface = 0;
01075 }
01076
01077 if (m_pip_layer)
01078 {
01079 m_render->DestroyLayer(m_pip_layer);
01080 m_pip_layer = 0;
01081 }
01082 }
01083
01084 m_pip_ready = false;
01085 }
01086
01087 void VideoOutputVDPAU::ShowPIP(VideoFrame *frame, MythPlayer *pipplayer,
01088 PIPLocation loc)
01089 {
01090 (void) frame;
01091 if (!pipplayer || !m_render)
01092 return;
01093
01094 int pipw, piph;
01095 VideoFrame *pipimage = pipplayer->GetCurrentFrame(pipw, piph);
01096 const bool pipActive = pipplayer->IsPIPActive();
01097 const bool pipVisible = pipplayer->IsPIPVisible();
01098 const float pipVideoAspect = pipplayer->GetVideoAspect();
01099 const QSize pipVideoDim = pipplayer->GetVideoBufferSize();
01100
01101 if ((pipVideoAspect <= 0) || !pipimage ||
01102 !pipimage->buf || pipimage->codec != FMT_YV12 || !pipVisible)
01103 {
01104 pipplayer->ReleaseCurrentFrame(pipimage);
01105 return;
01106 }
01107
01108 if (InitPIPLayer(window.GetDisplayVisibleRect().size()))
01109 {
01110 if (m_pips.contains(pipplayer) &&
01111 m_pips[pipplayer].videoSize != pipVideoDim)
01112 RemovePIP(pipplayer);
01113
01114 if (!m_pips.contains(pipplayer))
01115 {
01116 uint mixer = m_render->CreateVideoMixer(pipVideoDim, 0, 0);
01117 uint surf = m_render->CreateVideoSurface(pipVideoDim);
01118 vdpauPIP tmp = { pipVideoDim, surf, mixer};
01119 m_pips.insert(pipplayer, tmp);
01120 if (!mixer || !surf)
01121 RemovePIP(pipplayer);
01122 else
01123 LOG(VB_GENERAL, LOG_INFO, LOC + QString("Created pip %1x%2")
01124 .arg(pipVideoDim.width()).arg(pipVideoDim.height()));
01125 }
01126
01127 if (m_pips.contains(pipplayer))
01128 {
01129 QRect rect = GetPIPRect(loc, pipplayer);
01130
01131 if (!m_pip_ready)
01132 m_render->DrawBitmap(0, m_pip_surface, NULL, NULL,
01133 kVDPBlendNull);
01134
01135 uint32_t pitches[] = {
01136 pipimage->pitches[0],
01137 pipimage->pitches[2],
01138 pipimage->pitches[1] };
01139 void* const planes[] = {
01140 pipimage->buf,
01141 pipimage->buf + pipimage->offsets[2],
01142 pipimage->buf + pipimage->offsets[1] };
01143
01144 bool ok;
01145 ok = m_render->UploadYUVFrame(m_pips[pipplayer].videoSurface,
01146 planes, pitches);
01147 ok &= m_render->MixAndRend(m_pips[pipplayer].videoMixer,
01148 VDP_VIDEO_MIXER_PICTURE_STRUCTURE_FRAME,
01149 m_pips[pipplayer].videoSurface,
01150 m_pip_surface, NULL, false,
01151 QRect(QPoint(0,0), pipVideoDim),
01152 rect, rect);
01153 ok &= m_render->DrawBitmap(0, m_pip_surface, NULL, &rect,
01154 kVDPBlendPiP, 255);
01155
01156 if (pipActive)
01157 {
01158
01159 QRect l = QRect(QPoint(rect.x() - 10, rect.y() - 10),
01160 QSize(10, rect.height() + 20));
01161 QRect t = QRect(QPoint(rect.x(), rect.y() - 10),
01162 QSize(rect.width(), 10));
01163 QRect b = QRect(QPoint(rect.x(), rect.y() + rect.height()),
01164 QSize(rect.width(), 10));
01165 QRect r = QRect(QPoint(rect.x() + rect.width(), rect.y() -10),
01166 QSize(10, rect.height() + 20));
01167 m_render->DrawBitmap(0, m_pip_surface, NULL, &l, kVDPBlendNormal, 255, 127);
01168 m_render->DrawBitmap(0, m_pip_surface, NULL, &t, kVDPBlendNormal, 255, 127);
01169 m_render->DrawBitmap(0, m_pip_surface, NULL, &b, kVDPBlendNormal, 255, 127);
01170 m_render->DrawBitmap(0, m_pip_surface, NULL, &r, kVDPBlendNormal, 255, 127);
01171 }
01172
01173 m_pip_ready = ok;
01174 }
01175 }
01176 pipplayer->ReleaseCurrentFrame(pipimage);
01177 }
01178
01179 void VideoOutputVDPAU::RemovePIP(MythPlayer *pipplayer)
01180 {
01181 if (!m_pips.contains(pipplayer))
01182 return;
01183
01184 if (m_pips[pipplayer].videoSurface && m_render)
01185 m_render->DestroyVideoSurface(m_pips[pipplayer].videoSurface);
01186
01187 if (m_pips[pipplayer].videoMixer)
01188 m_render->DestroyVideoMixer(m_pips[pipplayer].videoMixer);
01189
01190 m_pips.remove(pipplayer);
01191 LOG(VB_PLAYBACK, LOG_INFO, LOC + "Removed 1 PIP");
01192
01193 if (m_pips.empty())
01194 DeinitPIPLayer();
01195 }
01196
01197 void VideoOutputVDPAU::ParseOptions(void)
01198 {
01199 m_skip_chroma = false;
01200 m_denoise = 0.0f;
01201 m_sharpen = 0.0f;
01202 m_colorspace = VDP_COLOR_STANDARD_ITUR_BT_601;
01203 m_mixer_features = kVDPFeatNone;
01204
01205 m_decoder_buffer_size = MAX_REFERENCE_FRAMES;
01206 m_process_buffer_size = DEF_PROCESS_BUFFER;
01207 if (codec_is_vdpau(video_codec_id))
01208 m_decoder_buffer_size = MIN_REFERENCE_FRAMES;
01209
01210 QStringList list = GetFilters().split(",");
01211 if (list.empty())
01212 return;
01213
01214 for (QStringList::Iterator i = list.begin(); i != list.end(); ++i)
01215 {
01216 QString name = (*i).section('=', 0, 0).toLower();
01217 QString opts = (*i).section('=', 1).toLower();
01218
01219 if (!name.contains("vdpau"))
01220 continue;
01221
01222 if (name.contains("vdpaubuffercount"))
01223 {
01224 uint num = opts.toUInt();
01225 if (MIN_PROCESS_BUFFER <= num && num <= MAX_PROCESS_BUFFER)
01226 {
01227 LOG(VB_PLAYBACK, LOG_INFO, LOC +
01228 QString("VDPAU process buffer size set to %1 (was %2)")
01229 .arg(num).arg(m_process_buffer_size));
01230 m_process_buffer_size = num;
01231 }
01232 }
01233 else if (name.contains("vdpauivtc"))
01234 {
01235 LOG(VB_PLAYBACK, LOG_INFO, LOC +
01236 "Enabling VDPAU inverse telecine "
01237 "(requires Basic or Advanced deinterlacer)");
01238 m_mixer_features |= kVDPFeatIVTC;
01239 }
01240 else if (name.contains("vdpauskipchroma"))
01241 {
01242 LOG(VB_PLAYBACK, LOG_INFO, LOC + "Enabling SkipChromaDeinterlace.");
01243 m_skip_chroma = true;
01244 }
01245 else if (name.contains("vdpaudenoise"))
01246 {
01247 float tmp = std::max(0.0f, std::min(1.0f, opts.toFloat()));
01248 if (tmp != 0.0)
01249 {
01250 LOG(VB_PLAYBACK, LOG_INFO, LOC +
01251 QString("VDPAU Denoise %1").arg(tmp,4,'f',2,'0'));
01252 m_denoise = tmp;
01253 m_mixer_features |= kVDPFeatDenoise;
01254 }
01255 }
01256 else if (name.contains("vdpausharpen"))
01257 {
01258 float tmp = std::max(-1.0f, std::min(1.0f, opts.toFloat()));
01259 if (tmp != 0.0)
01260 {
01261 LOG(VB_PLAYBACK, LOG_INFO, LOC +
01262 QString("VDPAU Sharpen %1").arg(tmp,4,'f',2,'0'));
01263 m_sharpen = tmp;
01264 m_mixer_features |= kVDPFeatSharpness;
01265 }
01266 }
01267 else if (name.contains("vdpaucolorspace"))
01268 {
01269 if (opts.contains("auto"))
01270 m_colorspace = -1;
01271 else if (opts.contains("601"))
01272 m_colorspace = VDP_COLOR_STANDARD_ITUR_BT_601;
01273 else if (opts.contains("709"))
01274 m_colorspace = VDP_COLOR_STANDARD_ITUR_BT_709;
01275
01276 if (m_colorspace > -1)
01277 {
01278 LOG(VB_PLAYBACK, LOG_INFO, LOC +
01279 QString("Forcing ITU BT.%1 colorspace")
01280 .arg((m_colorspace == VDP_COLOR_STANDARD_ITUR_BT_601) ?
01281 "BT.601" : "BT.709"));
01282 }
01283 }
01284 else if (name.contains("vdpauhqscaling"))
01285 {
01286 m_mixer_features |= kVDPFeatHQScaling;
01287 LOG(VB_PLAYBACK, LOG_INFO, LOC +
01288 "Requesting high quality scaling.");
01289 }
01290 }
01291 }
01292
01293 MythPainter *VideoOutputVDPAU::GetOSDPainter(void)
01294 {
01295 return m_osd_painter;
01296 }
01297
01298 bool VideoOutputVDPAU::GetScreenShot(int width, int height, QString filename)
01299 {
01300 if (m_render)
01301 return m_render->GetScreenShot(width, height, filename);
01302 return false;
01303 }
01304
01305 QStringList VideoOutputVDPAU::GetVisualiserList(void)
01306 {
01307 if (m_render)
01308 return VideoVisual::GetVisualiserList(m_render->Type());
01309 return VideoOutput::GetVisualiserList();
01310 }