├── .gitignore ├── README ├── main.cpp ├── qml └── qmlvideo │ └── main.qml ├── qmldeploy.pri ├── qmlvideo.cpp ├── qmlvideo.h └── qmlvideo.pro /.gitignore: -------------------------------------------------------------------------------- 1 | temp/ 2 | .~* 3 | *~ 4 | *.o 5 | *.bak 6 | Ui_* 7 | ui_* 8 | out/ 9 | Temp/ 10 | *-build-*/ 11 | *.user 12 | moc_* 13 | qrc_* 14 | Makefile 15 | *.vpp~* 16 | *.orig 17 | *.autosave 18 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | Video player for QML component, using VLC as decoder. 2 | 3 | 4 | Overview. 5 | The pipeline of video playing is 6 | 1. the VLC parsing the input video(local file or rtsp stream), 7 | 8 | 2. decode it and output frame 9 | 10 | 3. convert them from YUV to RGB 11 | 12 | 4. upload to texture 13 | 14 | 5. displaying it. 15 | 16 | Note: 17 | 18 | 1. main rendering thread ini VLC engine 19 | 20 | 2. load media, created media player, set callbacks 21 | 22 | 3. video format info 23 | determine colorspace 24 | call to render thread. 25 | -> setupsFormat return number of plane. 26 | 27 | 4. lock callback 28 | Lock Buffer Mutex(I won't mess up with the frame that displaying) 29 | send pixel buffer into VLC, 30 | 31 | 32 | 5. unlock for video memory 33 | -> updateTexture 34 | 35 | 36 | 6. display callback 37 | -> paintFrame. 38 | -> update to repaint 39 | 40 | QMetaObject::invokeMethod for cross thread function call. 41 | 42 | 7. when VLC thread is ready on next frame, it call lock-> unlock-> display callback in order. 43 | 44 | 45 | 46 | Texture Mode: Synchronous Transfers 47 | GPU -------------draw 48 | BUS -------upload 49 | CPU -- copy----------copy 50 | 51 | PBO mode: Async transfer 52 | save one more memory copy, vlc will draw into pixelbuffer memory space. 53 | GPU -----------------draw0---draw1 54 | BUS ---------upload0-upload1-upload0 55 | CPU -- copy0-copy1---copy2 56 | 57 | 58 | 59 | 60 | use shader to convert colorspace of texture? 61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "qmlvideo.h" 5 | 6 | int main(int argc, char *argv[]) 7 | { 8 | QApplication app(argc,argv); 9 | 10 | qmlRegisterType("QmlVideo", 1, 0, "Video"); 11 | 12 | QDeclarativeView *view = new QDeclarativeView(); 13 | QGLWidget *gl = new QGLWidget(); 14 | view->setViewport(gl); 15 | 16 | view->setSource(QUrl::fromLocalFile("qml/qmlvideo/main.qml")); 17 | view->setResizeMode(QDeclarativeView::SizeRootObjectToView); 18 | 19 | view->show(); 20 | 21 | return app.exec(); 22 | } 23 | -------------------------------------------------------------------------------- /qml/qmlvideo/main.qml: -------------------------------------------------------------------------------- 1 | // import QtQuick 1.0 // to target S60 5th Edition or Maemo 5 2 | import QtQuick 1.1 3 | import QmlVideo 1.0 4 | 5 | Rectangle { 6 | width: 360 7 | height: 360 8 | 9 | Grid { 10 | rows: 2; columns: 2; 11 | anchors.fill: parent; 12 | id: column; 13 | Video { 14 | id: video 15 | anchors.margins: 10; 16 | width: parent.width/2; 17 | height: parent.height/2; 18 | //fileName: "C:\\Users\\Public\\Videos\\Sample Videos\\wildlife.wmv"; 19 | //fileName: "rtsp://10.0.0.33/img/video.sav" 20 | fileName: "C:\\Users\\XWeng\\Downloads\\big_buck_bunny_1080p_h264.mov" 21 | state: Video.Playing; 22 | MouseArea { 23 | anchors.fill: parent 24 | acceptedButtons: Qt.LeftButton | Qt.RightButton 25 | onClicked: { 26 | if(mouse.button == Qt.LeftButton) 27 | { 28 | parent.pause(); 29 | } 30 | else 31 | { 32 | parent.stop(); 33 | parent.play(); 34 | } 35 | } 36 | } 37 | } 38 | Video { 39 | id: video2 40 | anchors.margins: 10; 41 | width: parent.width/2; 42 | height: parent.height/2; 43 | fileName: "C:\\Users\\Public\\Videos\\Sample Videos\\wildlife.wmv"; 44 | //fileName: "rtsp://10.0.0.33/img/video.sav" 45 | state: Video.Playing; 46 | MouseArea { 47 | anchors.fill: parent 48 | acceptedButtons: Qt.LeftButton | Qt.RightButton 49 | onClicked: { 50 | if(mouse.button == Qt.LeftButton) 51 | { 52 | parent.pause(); 53 | } 54 | else 55 | { 56 | parent.stop(); 57 | parent.play(); 58 | } 59 | } 60 | } 61 | } 62 | Video { 63 | id: video3 64 | anchors.margins: 10; 65 | width: parent.width/2; 66 | height: parent.height/2; 67 | fileName: "C:\\Users\\Public\\Videos\\Sample Videos\\wildlife.wmv"; 68 | //fileName: "rtsp://10.0.0.33/img/video.sav" 69 | state: Video.Playing; 70 | MouseArea { 71 | anchors.fill: parent 72 | acceptedButtons: Qt.LeftButton | Qt.RightButton 73 | onClicked: { 74 | if(mouse.button == Qt.LeftButton) 75 | { 76 | parent.pause(); 77 | } 78 | else 79 | { 80 | parent.stop(); 81 | parent.play(); 82 | } 83 | } 84 | } 85 | } 86 | Video { 87 | id: video4 88 | anchors.margins: 10; 89 | width: parent.width/2; 90 | height: parent.height/2; 91 | //fileName: "C:\\Users\\Public\\Videos\\Sample Videos\\wildlife.wmv"; 92 | fileName: "rtsp://10.0.0.33/img/video.sav" 93 | state: Video.Playing; 94 | MouseArea { 95 | anchors.fill: parent 96 | acceptedButtons: Qt.LeftButton | Qt.RightButton 97 | onClicked: { 98 | if(mouse.button == Qt.LeftButton) 99 | { 100 | parent.pause(); 101 | } 102 | else 103 | { 104 | parent.stop(); 105 | parent.play(); 106 | } 107 | } 108 | } 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /qmldeploy.pri: -------------------------------------------------------------------------------- 1 | # Include JS debugger library if QMLJSDEBUGGER_PATH is set 2 | !isEmpty(QMLJSDEBUGGER_PATH) { 3 | include($$QMLJSDEBUGGER_PATH/qmljsdebugger-lib.pri) 4 | } else { 5 | DEFINES -= QMLJSDEBUGGER 6 | } 7 | 8 | contains(CONFIG,qdeclarative-boostable):contains(MEEGO_EDITION,harmattan) { 9 | DEFINES += HARMATTAN_BOOSTER 10 | } 11 | # This file was generated by an application wizard of Qt Creator. 12 | # The code below handles deployment to Symbian and Maemo, aswell as copying 13 | # of the application data to shadow build directories on desktop. 14 | # It is recommended not to modify this file, since newer versions of Qt Creator 15 | # may offer an updated version of it. 16 | 17 | defineTest(qtcAddDeployment) { 18 | for(deploymentfolder, DEPLOYMENTFOLDERS) { 19 | item = item$${deploymentfolder} 20 | itemsources = $${item}.sources 21 | $$itemsources = $$eval($${deploymentfolder}.source) 22 | itempath = $${item}.path 23 | $$itempath= $$eval($${deploymentfolder}.target) 24 | export($$itemsources) 25 | export($$itempath) 26 | DEPLOYMENT += $$item 27 | } 28 | 29 | MAINPROFILEPWD = $$PWD 30 | 31 | symbian { 32 | isEmpty(ICON):exists($${TARGET}.svg):ICON = $${TARGET}.svg 33 | isEmpty(TARGET.EPOCHEAPSIZE):TARGET.EPOCHEAPSIZE = 0x20000 0x2000000 34 | } else:win32 { 35 | copyCommand = 36 | for(deploymentfolder, DEPLOYMENTFOLDERS) { 37 | source = $$MAINPROFILEPWD/$$eval($${deploymentfolder}.source) 38 | source = $$replace(source, /, \\) 39 | sourcePathSegments = $$split(source, \\) 40 | target = $$OUT_PWD/$$eval($${deploymentfolder}.target)/$$last(sourcePathSegments) 41 | target = $$replace(target, /, \\) 42 | target ~= s,\\\\\\.?\\\\,\\, 43 | !isEqual(source,$$target) { 44 | !isEmpty(copyCommand):copyCommand += && 45 | isEqual(QMAKE_DIR_SEP, \\) { 46 | copyCommand += $(COPY_DIR) \"$$source\" \"$$target\" 47 | } else { 48 | source = $$replace(source, \\\\, /) 49 | target = $$OUT_PWD/$$eval($${deploymentfolder}.target) 50 | target = $$replace(target, \\\\, /) 51 | copyCommand += test -d \"$$target\" || mkdir -p \"$$target\" && cp -r \"$$source\" \"$$target\" 52 | } 53 | } 54 | } 55 | !isEmpty(copyCommand) { 56 | copyCommand = @echo Copying application data... && $$copyCommand 57 | copydeploymentfolders.commands = $$copyCommand 58 | first.depends = $(first) copydeploymentfolders 59 | export(first.depends) 60 | export(copydeploymentfolders.commands) 61 | QMAKE_EXTRA_TARGETS += first copydeploymentfolders 62 | } 63 | } else:unix { 64 | maemo5 { 65 | desktopfile.files = $${TARGET}.desktop 66 | desktopfile.path = /usr/share/applications/hildon 67 | icon.files = $${TARGET}64.png 68 | icon.path = /usr/share/icons/hicolor/64x64/apps 69 | } else:!isEmpty(MEEGO_VERSION_MAJOR) { 70 | desktopfile.files = $${TARGET}_harmattan.desktop 71 | desktopfile.path = /usr/share/applications 72 | icon.files = $${TARGET}80.png 73 | icon.path = /usr/share/icons/hicolor/80x80/apps 74 | } else { # Assumed to be a Desktop Unix 75 | copyCommand = 76 | for(deploymentfolder, DEPLOYMENTFOLDERS) { 77 | source = $$MAINPROFILEPWD/$$eval($${deploymentfolder}.source) 78 | source = $$replace(source, \\\\, /) 79 | macx { 80 | target = $$OUT_PWD/$${TARGET}.app/Contents/Resources/$$eval($${deploymentfolder}.target) 81 | } else { 82 | target = $$OUT_PWD/$$eval($${deploymentfolder}.target) 83 | } 84 | target = $$replace(target, \\\\, /) 85 | sourcePathSegments = $$split(source, /) 86 | targetFullPath = $$target/$$last(sourcePathSegments) 87 | targetFullPath ~= s,/\\.?/,/, 88 | !isEqual(source,$$targetFullPath) { 89 | !isEmpty(copyCommand):copyCommand += && 90 | copyCommand += $(MKDIR) \"$$target\" 91 | copyCommand += && $(COPY_DIR) \"$$source\" \"$$target\" 92 | } 93 | } 94 | !isEmpty(copyCommand) { 95 | copyCommand = @echo Copying application data... && $$copyCommand 96 | copydeploymentfolders.commands = $$copyCommand 97 | first.depends = $(first) copydeploymentfolders 98 | export(first.depends) 99 | export(copydeploymentfolders.commands) 100 | QMAKE_EXTRA_TARGETS += first copydeploymentfolders 101 | } 102 | } 103 | installPrefix = /opt/$${TARGET} 104 | for(deploymentfolder, DEPLOYMENTFOLDERS) { 105 | item = item$${deploymentfolder} 106 | itemfiles = $${item}.files 107 | $$itemfiles = $$eval($${deploymentfolder}.source) 108 | itempath = $${item}.path 109 | $$itempath = $${installPrefix}/$$eval($${deploymentfolder}.target) 110 | export($$itemfiles) 111 | export($$itempath) 112 | INSTALLS += $$item 113 | } 114 | 115 | !isEmpty(desktopfile.path) { 116 | export(icon.files) 117 | export(icon.path) 118 | export(desktopfile.files) 119 | export(desktopfile.path) 120 | INSTALLS += icon desktopfile 121 | } 122 | 123 | target.path = $${installPrefix}/bin 124 | export(target.path) 125 | INSTALLS += target 126 | } 127 | 128 | export (ICON) 129 | export (INSTALLS) 130 | export (DEPLOYMENT) 131 | export (TARGET.EPOCHEAPSIZE) 132 | export (TARGET.CAPABILITY) 133 | export (LIBS) 134 | export (QMAKE_EXTRA_TARGETS) 135 | } 136 | -------------------------------------------------------------------------------- /qmlvideo.cpp: -------------------------------------------------------------------------------- 1 | #include "qmlvideo.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | QmlVideo::QmlVideo(QDeclarativeItem *parent) : 12 | QDeclarativeItem(parent), 13 | m_state(Stopped), 14 | m_paintMode(PaintModeQPainter) 15 | { 16 | //Set up item options 17 | setFlag(QGraphicsItem::ItemHasNoContents, false); 18 | setSmooth(true); 19 | 20 | qRegisterMetaType("const libvlc_event_t *"); 21 | 22 | memset(m_textureId, 0, sizeof(quint32)*3); 23 | memset(m_pbo1, 0, sizeof(quint32)*3); 24 | memset(m_pbo2, 0, sizeof(quint32)*3); 25 | 26 | //Initialize the VLC library; 27 | const char *argv[] = 28 | { 29 | "--no-xlib", /* tell VLC to not use Xlib */ 30 | "--network-caching=500", 31 | "--no-video-title-show", 32 | "--disable-screensaver", 33 | }; 34 | int argc = sizeof(argv) / sizeof(*argv); 35 | m_libVlc = libvlc_new(argc,argv); 36 | } 37 | 38 | QmlVideo::~QmlVideo(){ 39 | clearUp(); 40 | libvlc_release(m_libVlc); 41 | 42 | } 43 | 44 | void QmlVideo::clearUp(){ 45 | if(m_mediaPlayer != NULL)libvlc_media_player_stop(m_mediaPlayer); 46 | if(m_mediaPlayer != NULL)libvlc_media_player_release(m_mediaPlayer); 47 | 48 | cleanupBuffers(); 49 | cleanupTextures(); 50 | cleanupPBOs(); 51 | } 52 | 53 | QmlVideo::State QmlVideo::state() 54 | { 55 | return(m_state); 56 | } 57 | 58 | void QmlVideo::play(const QString &fileName) 59 | { 60 | if(!fileName.isNull()) 61 | setFileName(fileName); 62 | 63 | setState(Playing); 64 | } 65 | 66 | void QmlVideo::pause() 67 | { 68 | setState(Paused); 69 | } 70 | 71 | void QmlVideo::stop() 72 | { 73 | setState(Stopped); 74 | } 75 | 76 | void QmlVideo::setState(State state) 77 | { 78 | switch(state) 79 | { 80 | case Stopped: 81 | if(m_state == Stopped) 82 | return; 83 | libvlc_media_player_pause(m_mediaPlayer); 84 | libvlc_media_player_set_time(m_mediaPlayer, 0); 85 | break; 86 | case Playing: 87 | libvlc_media_player_play(m_mediaPlayer); 88 | break; 89 | case Paused: 90 | if(m_state != Playing) 91 | play(); 92 | else 93 | libvlc_media_player_pause(m_mediaPlayer); 94 | break; 95 | } 96 | } 97 | 98 | QString QmlVideo::fileName() 99 | { 100 | return(m_fileName); 101 | } 102 | 103 | void QmlVideo::setFileName(const QString &fileName) 104 | { 105 | clearUp(); 106 | if(m_state != Stopped) 107 | setState(Stopped); 108 | m_fileName = fileName; 109 | 110 | libvlc_media_t *m; 111 | m = libvlc_media_new_path(m_libVlc, qPrintable(fileName)); 112 | m_mediaPlayer = libvlc_media_player_new_from_media(m); 113 | libvlc_media_release(m); 114 | 115 | libvlc_video_set_format_callbacks(m_mediaPlayer, vlcVideoFormatCallback, NULL); 116 | libvlc_video_set_callbacks(m_mediaPlayer, vlcVideoLockCallBack, vlcVideoUnlockCallback, vlcVideoDisplayCallback, this); 117 | libvlc_event_attach(libvlc_media_player_event_manager(m_mediaPlayer), libvlc_MediaPlayerOpening, vlcVideoEventCallback, this); 118 | libvlc_event_attach(libvlc_media_player_event_manager(m_mediaPlayer), libvlc_MediaPlayerBuffering, vlcVideoEventCallback, this); 119 | libvlc_event_attach(libvlc_media_player_event_manager(m_mediaPlayer), libvlc_MediaPlayerPlaying, vlcVideoEventCallback, this); 120 | libvlc_event_attach(libvlc_media_player_event_manager(m_mediaPlayer), libvlc_MediaPlayerPaused, vlcVideoEventCallback, this); 121 | libvlc_event_attach(libvlc_media_player_event_manager(m_mediaPlayer), libvlc_MediaPlayerStopped, vlcVideoEventCallback, this); 122 | libvlc_event_attach(libvlc_media_player_event_manager(m_mediaPlayer), libvlc_MediaPlayerEndReached, vlcVideoEventCallback, this); 123 | libvlc_event_attach(libvlc_media_player_event_manager(m_mediaPlayer), libvlc_MediaPlayerEncounteredError, vlcVideoEventCallback, this); 124 | //libvlc_event_attach(libvlc_media_player_event_manager(m_mediaPlayer), libvlc_MediaPlayerTimeChanged, vlcVideoEventCallback, this); 125 | //libvlc_event_attach(libvlc_media_player_event_manager(m_mediaPlayer), libvlc_MediaPlayerPositionChanged, vlcVideoEventCallback, this); 126 | libvlc_event_attach(libvlc_media_player_event_manager(m_mediaPlayer), libvlc_MediaPlayerSeekableChanged, vlcVideoEventCallback, this); 127 | libvlc_event_attach(libvlc_media_player_event_manager(m_mediaPlayer), libvlc_MediaPlayerPausableChanged, vlcVideoEventCallback, this); 128 | libvlc_event_attach(libvlc_media_player_event_manager(m_mediaPlayer), libvlc_MediaPlayerLengthChanged, vlcVideoEventCallback, this); 129 | } 130 | 131 | void QmlVideo::paintFrame() 132 | { 133 | //Just signal that we need to repaint the item. 134 | update(); 135 | } 136 | 137 | void QmlVideo::paint(QPainter *p, const QStyleOptionGraphicsItem *style, QWidget *widget) 138 | { 139 | switch(m_paintMode) 140 | { 141 | case PaintModeQPainter: 142 | { 143 | QImage img((uchar *)m_pixelBuff[0], m_width, m_height, QImage::Format_RGB888); 144 | p->drawImage(boundingRect(), img.rgbSwapped(), QRect(0,0,m_width, m_height)); 145 | } 146 | break; 147 | case PaintModePBO: 148 | case PaintModeTexture: 149 | { 150 | p->beginNativePainting(); 151 | 152 | //qDebug() << "Paint..."; 153 | 154 | QRectF rect = boundingRect(); 155 | 156 | glEnable(GL_TEXTURE_2D); 157 | glBindTexture(GL_TEXTURE_2D, m_textureId[0]); 158 | 159 | glBegin(GL_QUADS); 160 | glTexCoord2d(0.0,0.0); 161 | glColor3i(0,0,0); 162 | glVertex2d(rect.x(), rect.y()); 163 | glTexCoord2d(0.0,1.0); 164 | glVertex2d(rect.x(), rect.y() + rect.height()); 165 | glTexCoord2d(1.0,1.0); 166 | glVertex2d(rect.x() + rect.width(), rect.y() + rect.height()); 167 | glTexCoord2d(1.0,0.0); 168 | glVertex2d(rect.x() + rect.width(), rect.y()); 169 | glEnd(); 170 | 171 | glBindTexture(GL_TEXTURE_2D, 0); 172 | glDisable(GL_TEXTURE_2D); 173 | 174 | p->endNativePainting(); 175 | } 176 | break; 177 | } 178 | } 179 | 180 | unsigned int QmlVideo::vlcVideoFormatCallback(void **object, char *chroma, unsigned int *width, unsigned int *height, 181 | unsigned int *pitches, unsigned int *lines) 182 | { 183 | unsigned int retval = 0; 184 | QmlVideo *instance = (QmlVideo *)*object; 185 | QMetaObject::invokeMethod(instance, "setupFormat", Qt::BlockingQueuedConnection, Q_RETURN_ARG(quint32, retval), 186 | Q_ARG(char *, chroma), Q_ARG(unsigned int *, width), Q_ARG(unsigned int *, height), 187 | Q_ARG(unsigned int *, pitches), Q_ARG(unsigned int *, lines)); 188 | return(retval); 189 | } 190 | 191 | void *QmlVideo::vlcVideoLockCallBack(void *object, void **planes) 192 | { 193 | //Lock the pixel mutex, and hand the pixel buffer to VLC 194 | QmlVideo *instance = (QmlVideo *)object; 195 | QMutexLocker((instance->m_pixelMutex)); 196 | planes[0] = (void *)instance->m_pixelBuff[0]; 197 | return NULL; 198 | } 199 | 200 | void QmlVideo::vlcVideoUnlockCallback(void *object, void *picture, void * const *planes) 201 | { 202 | QmlVideo *instance = (QmlVideo *)object; 203 | QMetaObject::invokeMethod(instance, "updateTexture", Qt::BlockingQueuedConnection, 204 | Q_ARG(void *, picture), Q_ARG(void * const *, planes)); 205 | } 206 | 207 | void QmlVideo::vlcVideoDisplayCallback(void *object, void *picture) 208 | { 209 | //Call the paintFrame function in the main thread. 210 | QmlVideo *instance = (QmlVideo *)object; 211 | QMetaObject::invokeMethod(instance, "paintFrame", Qt::BlockingQueuedConnection); 212 | } 213 | 214 | quint32 QmlVideo::setupFormat(char *chroma, unsigned int *width, unsigned int *height, unsigned int *pitches, unsigned int *lines) 215 | { 216 | qDebug() << "Got format request:" << chroma << *width << *height; 217 | 218 | GLenum err = glewInit(); 219 | if (GLEW_OK != err) 220 | { 221 | m_paintMode = PaintModeQPainter; 222 | } 223 | else 224 | { 225 | if(GLEW_EXT_pixel_buffer_object) 226 | { 227 | m_paintMode = PaintModePBO; 228 | } 229 | else 230 | { 231 | m_paintMode = PaintModeTexture; 232 | } 233 | } 234 | 235 | qDebug() << "Paint Mode:" << m_paintMode; 236 | 237 | setupPlanes(chroma, width, height, pitches, lines); 238 | setupBuffers(); 239 | setupTextures(); 240 | setupPBOs(); 241 | 242 | return(m_numPlanes); 243 | } 244 | 245 | void QmlVideo::updateTexture(void *picture, void * const *planes) 246 | { 247 | updateTextures(); 248 | updatePBOs(); 249 | } 250 | 251 | void QmlVideo::vlcVideoEventCallback(const libvlc_event_t *event, void *object) 252 | { 253 | libvlc_event_t *tmp = new libvlc_event_t; 254 | memcpy(tmp,event,sizeof(libvlc_event_t)); 255 | 256 | QmlVideo *instance = (QmlVideo *)object; 257 | QMetaObject::invokeMethod(instance, "playerEvent", Qt::QueuedConnection, 258 | Q_ARG(const libvlc_event_t *, tmp)); 259 | } 260 | 261 | void QmlVideo::playerEvent(const libvlc_event_t *event) 262 | { 263 | switch(event->type) 264 | { 265 | case libvlc_MediaPlayerEndReached: 266 | setFileName(fileName()); 267 | case libvlc_MediaPlayerStopped: 268 | qDebug() << "Stopped"; 269 | m_state=Stopped; 270 | emit(stateChanged(m_state)); 271 | emit(stopped()); 272 | break; 273 | case libvlc_MediaPlayerOpening: 274 | qDebug() << "Opening"; 275 | m_state=Opening; 276 | emit(stateChanged(m_state)); 277 | break; 278 | case libvlc_MediaPlayerBuffering: 279 | qDebug() << "Buffering"; 280 | m_state=Buffering; 281 | emit(stateChanged(m_state)); 282 | break; 283 | case libvlc_MediaPlayerPlaying: 284 | qDebug() << "Playing"; 285 | m_state=Playing; 286 | emit(stateChanged(m_state)); 287 | emit(playing()); 288 | break; 289 | case libvlc_MediaPlayerPaused: 290 | qDebug() << "Paused"; 291 | m_state=Paused; 292 | emit(stateChanged(m_state)); 293 | emit(paused()); 294 | break; 295 | default: 296 | qDebug() << "Event: " << libvlc_event_type_name(event->type); 297 | break; 298 | } 299 | 300 | delete event; 301 | } 302 | 303 | void QmlVideo::setupPlanes(char *chroma, unsigned int *width, unsigned int *height, 304 | unsigned int *pitches, unsigned int *lines) 305 | { 306 | switch(m_paintMode) 307 | { 308 | case PaintModeQPainter: 309 | strcpy(chroma, "RV24"); 310 | pitches[0] = *width * 3; 311 | lines[0] = *height * 3; 312 | m_width = *width; 313 | m_height = *height; 314 | m_numPlanes = 1; 315 | break; 316 | case PaintModeTexture: 317 | case PaintModePBO: 318 | strcpy(chroma, "RV24"); 319 | pitches[0] = *width * 3; 320 | lines[0] = *height * 3; 321 | m_width = *width; 322 | m_height = *height; 323 | m_numPlanes = 1; 324 | break; 325 | } 326 | } 327 | 328 | void QmlVideo::setupBuffers() 329 | { 330 | switch(m_paintMode) 331 | { 332 | case PaintModeQPainter: 333 | case PaintModeTexture: 334 | qDebug() << "Setting up buffers"; 335 | m_pixelBuff[0] = new char[m_width*m_height*3]; 336 | break; 337 | case PaintModePBO: 338 | break; 339 | } 340 | } 341 | 342 | void QmlVideo::cleanupBuffers() 343 | { 344 | switch(m_paintMode) 345 | { 346 | case PaintModeQPainter: 347 | case PaintModeTexture: 348 | delete m_pixelBuff[0]; 349 | break; 350 | case PaintModePBO: 351 | break; 352 | } 353 | } 354 | 355 | void QmlVideo::setupTextures() 356 | { 357 | switch(m_paintMode) { 358 | case PaintModeQPainter: 359 | break; 360 | case PaintModeTexture: 361 | case PaintModePBO: 362 | qDebug() << "Setting up textures"; 363 | glGenTextures(1, &m_textureId[0]); 364 | glBindTexture(GL_TEXTURE_2D, m_textureId[0]); 365 | glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 366 | glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 367 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); 368 | glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); 369 | glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL); 370 | glBindTexture(GL_TEXTURE_2D, 0); 371 | break; 372 | } 373 | } 374 | 375 | void QmlVideo::cleanupTextures() 376 | { 377 | switch(m_paintMode) { 378 | case PaintModeQPainter: 379 | break; 380 | case PaintModeTexture: 381 | case PaintModePBO: 382 | if(m_textureId[0] != 0){glDeleteTextures(1,&m_textureId[0]);} 383 | break; 384 | } 385 | } 386 | 387 | void QmlVideo::updateTextures() 388 | { 389 | switch(m_paintMode) { 390 | case PaintModeQPainter: 391 | break; 392 | case PaintModeTexture: 393 | qDebug() << "Updating Textures"; 394 | glBindTexture(GL_TEXTURE_2D, m_textureId[0]); 395 | glPixelStorei(GL_UNPACK_ALIGNMENT, 1); 396 | glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, m_width, m_height, 0, GL_BGR_EXT, GL_UNSIGNED_BYTE, m_pixelBuff[0]); 397 | glBindTexture(GL_TEXTURE_2D, 0); 398 | break; 399 | case PaintModePBO: 400 | break; 401 | } 402 | } 403 | 404 | void QmlVideo::setupPBOs() 405 | { 406 | switch(m_paintMode) 407 | { 408 | case PaintModeQPainter: 409 | case PaintModeTexture: 410 | break; 411 | case PaintModePBO: 412 | qDebug() << "Setting up PBOs"; 413 | glGenBuffers(1, &m_pbo1[0]); 414 | glBindBuffer(GL_PIXEL_UNPACK_BUFFER, m_pbo1[0]); 415 | glBufferData(GL_PIXEL_UNPACK_BUFFER, m_width * m_height * 3, 0, GL_STREAM_DRAW); 416 | m_pixelBuff[0] = (char *)glMapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY); 417 | glGenBuffers(1, &m_pbo2[0]); 418 | glBindBuffer(GL_PIXEL_UNPACK_BUFFER, m_pbo2[0]); 419 | glBufferData(GL_PIXEL_UNPACK_BUFFER, m_width * m_height * 3, 0, GL_STREAM_DRAW); 420 | glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); 421 | break; 422 | } 423 | } 424 | 425 | void QmlVideo::cleanupPBOs() 426 | { 427 | switch(m_paintMode) 428 | { 429 | case PaintModeQPainter: 430 | case PaintModeTexture: 431 | break; 432 | case PaintModePBO: 433 | if(m_pbo1[0] != 0){glDeleteBuffers(1, &m_pbo1[0]);} 434 | if(m_pbo2[0] != 0){glDeleteBuffers(1, &m_pbo2[0]);} 435 | break; 436 | } 437 | } 438 | 439 | void QmlVideo::updatePBOs() 440 | { 441 | switch(m_paintMode) 442 | { 443 | case PaintModeQPainter: 444 | case PaintModeTexture: 445 | break; 446 | case PaintModePBO: 447 | glBindBuffer(GL_PIXEL_UNPACK_BUFFER, m_pbo1[0]); 448 | glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER); 449 | glBindBuffer(GL_PIXEL_UNPACK_BUFFER, m_pbo2[0]); 450 | //Reset the buffer data to make sure we don't block when the buffer is mapped 451 | glBufferData(GL_PIXEL_UNPACK_BUFFER, m_width * m_height * 3, 0, GL_STREAM_DRAW); 452 | m_pixelBuff[0] = (char *)glMapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY); 453 | glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); 454 | 455 | quint32 tmp = m_pbo1[0]; 456 | m_pbo1[0] = m_pbo2[0]; 457 | m_pbo2[0] = tmp; 458 | 459 | glBindBuffer(GL_PIXEL_UNPACK_BUFFER, m_pbo2[0]); 460 | glBindTexture(GL_TEXTURE_2D, m_textureId[0]); 461 | glPixelStorei(GL_UNPACK_ALIGNMENT, 1); 462 | glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, m_width, m_height, 0, GL_BGR_EXT, GL_UNSIGNED_BYTE, 0); 463 | glBindTexture(GL_TEXTURE_2D, 0); 464 | glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); 465 | break; 466 | } 467 | } 468 | -------------------------------------------------------------------------------- /qmlvideo.h: -------------------------------------------------------------------------------- 1 | #ifndef QMLVIDEO_H 2 | #define QMLVIDEO_H 3 | 4 | #include 5 | #include 6 | 7 | struct libvlc_instance_t; 8 | struct libvlc_media_player_t; 9 | struct libvlc_event_t; 10 | 11 | class QmlVideo : public QDeclarativeItem 12 | { 13 | Q_OBJECT 14 | Q_ENUMS(State) 15 | Q_PROPERTY(State state READ state WRITE setState) 16 | Q_PROPERTY(QString fileName READ fileName WRITE setFileName) 17 | 18 | enum PaintMode 19 | { 20 | PaintModeQPainter, 21 | PaintModeTexture, 22 | PaintModePBO 23 | }; 24 | 25 | public: 26 | enum State 27 | { 28 | Opening, 29 | Buffering, 30 | Stopped, 31 | Playing, 32 | Paused 33 | }; 34 | 35 | explicit QmlVideo(QDeclarativeItem *parent = 0); 36 | ~QmlVideo(); 37 | 38 | void paint(QPainter *p, const QStyleOptionGraphicsItem *style, QWidget *widget); 39 | 40 | Q_INVOKABLE State state(); 41 | Q_INVOKABLE QString fileName(); 42 | Q_INVOKABLE void setFileName(const QString &fileName); 43 | 44 | signals: 45 | void stateChanged(State state); 46 | void stopped(); 47 | void playing(); 48 | void paused(); 49 | 50 | public slots: 51 | void play(const QString &ileName = QString::Null()); 52 | void pause(); 53 | void stop(); 54 | void setState(State state); 55 | 56 | protected slots: 57 | quint32 setupFormat(char *chroma, unsigned int *width, unsigned int *height, 58 | unsigned int *pitches, unsigned int *lines); 59 | void updateTexture(void *picture, void * const *planes); 60 | void paintFrame(); 61 | void playerEvent(const libvlc_event_t *event); 62 | 63 | private: 64 | //VLC callback functions 65 | static unsigned int vlcVideoFormatCallback(void **object, char *chroma, unsigned int *width, unsigned int *height, 66 | unsigned int *pitches, unsigned int *lines); 67 | static void *vlcVideoLockCallBack(void *object, void **planes); 68 | static void vlcVideoUnlockCallback(void *object, void *picture, void * const *planes); 69 | static void vlcVideoDisplayCallback(void *object, void *picture); 70 | static void vlcVideoEventCallback(const libvlc_event_t *event, void *object); 71 | 72 | void clearUp(); 73 | 74 | void setupPlanes(char *chroma, unsigned int *width, unsigned int *height, 75 | unsigned int *pitches, unsigned int *lines); 76 | void setupBuffers(); 77 | void cleanupBuffers(); 78 | void setupTextures(); 79 | void cleanupTextures(); 80 | void updateTextures(); 81 | void setupPBOs(); 82 | void cleanupPBOs(); 83 | void updatePBOs(); 84 | 85 | //Video Properties 86 | QString m_fileName; 87 | quint32 m_width; 88 | quint32 m_height; 89 | 90 | //State and buffer variables 91 | State m_state; 92 | char *m_pixelBuff[3]; 93 | QMutex *m_pixelMutex; 94 | libvlc_instance_t *m_libVlc; 95 | libvlc_media_player_t *m_mediaPlayer; 96 | PaintMode m_paintMode; 97 | quint32 m_textureId[3]; 98 | quint32 m_pbo1[3]; 99 | quint32 m_pbo2[3]; 100 | int m_numPlanes; 101 | }; 102 | 103 | #endif // QMLVIDEO_H 104 | -------------------------------------------------------------------------------- /qmlvideo.pro: -------------------------------------------------------------------------------- 1 | # Add more folders to ship with the application, here 2 | folder_01.source = qml/qmlvideo 3 | folder_01.target = qml 4 | DEPLOYMENTFOLDERS = folder_01 5 | 6 | QT += opengl declarative 7 | 8 | win32 { 9 | INCLUDEPATH += "C:/Program Files (x86)/VideoLAN/VLC/sdk/include" 10 | LIBS += "C:/Program Files (x86)/VideoLAN/VLC/sdk/lib/libvlccore.lib" 11 | LIBS += "C:/Program Files (x86)/VideoLAN/VLC/sdk/lib/libvlc.lib" 12 | INCLUDEPATH += "C:/Program Files (x86)/glew-1.9.0/include" 13 | LIBS += "C:/Program Files (x86)/glew-1.9.0/lib/glew32.lib" 14 | } 15 | 16 | # The .cpp file which was generated for your project. Feel free to hack it. 17 | SOURCES += main.cpp \ 18 | qmlvideo.cpp 19 | 20 | HEADERS += \ 21 | qmlvideo.h 22 | 23 | include(qmldeploy.pri) 24 | qtcAddDeployment() 25 | --------------------------------------------------------------------------------