Code archives/Graphics/Quicktime for Blitzmax

This code has been declared by its author to be Public Domain code.

Download source code

Quicktime for Blitzmax by AdamRedwoods2010
Here is my implementation for Quicktime for Blitzmax. It currently only runs under Windows and MacOSX.

Updated: 2011 Dec 27 - improved playback speed with DrawMovieImage()

TO USE:
I have this module living under the "blitzmax/mods/addons.mod" directory.

MACOSX: You will have the framework installed with XCode. No need to install additional libraries.

WINDOWS: In order for this to work, you will need to register and download Apple's Quicktime SDK for Windows (its free). Copy these files into the module's folder. Here is a sample folder layout:
+blitzmax
++mod
+++addons.mod
++++quicktime.mod
+++++maxquicktime_glue.cpp
+++++quicktime.bmx
+++++QuicktimeSDK
++++++CIncludes
++++++Libraries
++++++RIncludes
++++++ComponentIncludes
++++++Tools

You will also need to modify a couple lines in the "Math64.h" file (to compile under MINGW) to read as follows:
#if TYPE_LONGLONG && TARGET_OS_WIN32 && !defined(__MINGW32__)
    #define S64Max() 9223372036854775807i64

..and...

#if TYPE_LONGLONG && TARGET_OS_WIN32 && !defined(__MINGW32__)
    #define U64Max() 0xffffffffffffffffui64


WINDOWS & MACOSX
You will then need the "maxquicktime_glue.cpp" file, and place it in the quicktime.mod folder.

maxquicktime_glue.cpp


The code is not foolproof, but it works. I've tested under win vista, win7, macosx 10.5.6 with QT 7.

quicktime.bmx:
''
''Quicktime Module for MSWin & MacOSX
''by AdamRedwoods 2011
''

Module addons.quicktime

?Win32
ModuleInfo "CC_OPTS: -IQuickTimeSDK/CIncludes/"
ModuleInfo "CC_OPTS: -IQuickTimeSDK/CIncludes/CoreFoundation"
ModuleInfo "CC_OPTS: -IQuickTimeSDK/CIncludes/GNUCompatibility"
Import "maxquicktime_glue.cpp"
Import "QuickTimeSDK/Libraries/QTMLClient.lib" 
Import "QuickTimeSDK/Libraries/CVClient.lib"
?MacOS
'ModuleInfo "LD_OPTS: -framework/System/Library/Frameworks/QuickTime.framework"
ModuleInfo "LD_OPTS: -framework QuickTime"
Import "maxquicktime_glue.cpp"
?

Import brl.Pixmap
Import brl.glmax2d
Import brl.standardio
Import brl.GLGraphics


Extern 
	
	Function bmx_CreateQT:Byte Ptr() ''cpp class
    	Function EnterMovies() ' Initialize QuickTime 
    	Function ExitMovies() ' Terminate QuickTime
?Win32
    	Function TerminateQTML() 'terminate QTML
	Function InitializeQTML:Int(i:Int)  ' Initialize QTML
?

	Function bmx_OpenMovieFile:Int( qtclass:Byte Ptr, permission:Int) ''return -1 if error
	Function bmx_CloseMovieFile(qtclass:Byte Ptr)
	Function bmx_NewMovieFromFile:Int( qtclass:Byte Ptr, filename:Byte Ptr)
	Function bmx_NewMovieFromURL:Int( qtclass:Byte Ptr, filename:Byte Ptr)
	Function bmx_NewMovieFromFileRef:Int(qtclass:Byte Ptr, flags:Int)
	Function bmx_GetMovieLoadState:Int(qtclass:Byte Ptr)
	
	'''EXTERN_API( ComponentInstance ) NewMovieController(  Movie         theMovie,  Const Rect *  movieRect,  Long someFlags);
	Function bmx_NewMovieController:Int( qtclass:Byte Ptr, flags:Int=0) '' create new movie controller
	
	Function bmx_SetMovieActive(qtclass:Byte Ptr , boolean:Int)
	Function bmx_StartMovie(qtclass:Byte Ptr )
	Function bmx_MoviesTask( qtclass:Byte Ptr, flags:Int=0)
	Function bmx_UpdateMovie:Int(qtclass:Byte Ptr )
	Function bmx_StopMovie(qtclass:Byte Ptr)
	Function bmx_GoToBeginningOfMovie(qtclass:Byte Ptr )
	Function bmx_IsMovieDone(qtclass:Byte Ptr )
	Function bmx_SetMovieVolume ( qtclass:Byte Ptr, volume:Float) ''volume is signed short: -1.0 to 1.0 ''neg volume is silent
	
	Function bmx_GetMovieNextInterestingTime:Int(qtclass:Byte Ptr, flags:Int)
	Function bmx_SetMovieTimeValue(qtclass:Byte Ptr, newtime:Int)
	
	Function bmx_DisposeMovieController (qtclass:Byte Ptr) '                     // Close movie controller, if any
	Function bmx_DisposeMovie (qtclass:Byte Ptr) ' // Destroy movie Object, If any 
	Function bmx_FSMakeFSSpec:Int(qtclass:Byte Ptr, fullPath:Byte Ptr) ''in windows, pass full pathname
	''' FSMakeFSRefUnicode (   Const FSRef *parentRef, UniCharCount nameLength, Const UniChar *name, TextEncoding textEncodingHint,FSRef *newRef)
	''Function FSMakeFSRefUnicode (x:Int=0, filenamelength:Int, name:String, econdingHintConst:Int=0, newf:FSRef)
	Function bmx_NativePathNameToFSSpec:Int(qtclass:Byte Ptr, filename:Byte Ptr, flag:Int=0)
	
	Function bmx_QTNewGWorldFromPtr:Int(qtclass:Byte Ptr, pixelformat:Int, w:Int, h:Int, buffer:Byte Ptr, rowbytes:Int, GWorldFlags:Int=0)
	Function bmx_NewGWorld:Int(qtclass:Byte Ptr, w:Int, h:Int, flags:Int)
	Function bmx_SetMovieGWorld:Int(qtclass:Byte Ptr)
	Function bmx_SetGWorld(qtclass:Byte Ptr)   ' // Port Or Graphics world To make Current ''gdhandle null if gworld
	Function bmx_DisposeGWorld(qtclass:Byte Ptr) ''; //close gworld
	Function bmx_GetGWorldDevice:Int(qtclass:Byte Ptr)
	Function bmx_LockPixels:Int(qtclass:Byte Ptr) ''return true 1 or false 0
	Function bmx_UnlockPixels(qtclass:Byte Ptr)
		
	''Function CopyCStringToPascal(src:String, dst:String)
	Function  c2pstr:Int(src:Byte Ptr)
	
	Function bmx_SetMoviePlayHints(qtclass:Byte Ptr , hints:Int=0, hint2:Int=0)
	'hintsDontUseVideoOverlaySurface
	
	Function GetPixBaseAddr:Byte Ptr(pmhd:Object )
	Function GetGWorldPixMap:Byte Ptr(gw:Byte Ptr )
	'''EXTERN_API( Boolean ) PixMap32Bit(PixMapHandle pmHandle)
	''Function GetPixRowBytes:int(pmhd:Object) ''NOT IN MSWIN

	Function bmx_GetMovieBox(qtclass:Byte Ptr)
	Function bmx_GetMovieWidth(qtclass:Byte Ptr)
	Function bmx_GetMovieHeight(qtclass:Byte Ptr)
	Function bmx_GetMovieDuration:Int(qtclass:Byte Ptr)
	Function bmx_GetMovieTime:Int(qtclass:Byte Ptr)
	Function bmx_GetMovieTimeScale:Int(qtclass:Byte Ptr)
	Function bmx_GetMovieTrackCount:Int(qtclass:Byte Ptr)
	
	Function bmx_PreRollMovie( qtclass:Byte Ptr,  time:Int)
	
		
EndExtern



Const k1MonochromePixelFormat:Int       = $00000001', /* 1 bit indexed*/
Const  k2IndexedPixelFormat:Int          = $00000002', /* 2 bit indexed*/
Const  k4IndexedPixelFormat:Int          = $00000004', /* 4 bit indexed*/
Const  k8IndexedPixelFormat:Int          = $00000008', /* 8 bit indexed*/
Const  k16BE555PixelFormat:Int           = $00000010', /* 16 bit BE rgb 555 (Mac)*/
Const  k24RGBPixelFormat:Int             = $00000018', /* 24 bit rgb */
Const  k32ARGBPixelFormat:Int            = $00000020', /* 32 bit argb    (Mac)*/
Const  k1IndexedGrayPixelFormat:Int      = $00000021', /* 1 bit indexed gray*/
Const  k2IndexedGrayPixelFormat:Int      = $00000022', /* 2 bit indexed gray*/
Const  k4IndexedGrayPixelFormat:Int      = $00000024', /* 4 bit indexed gray*/
Const  k8IndexedGrayPixelFormat:Int      = $00000028' /* 8 bit indexed gray*/
Const  k32BGRAPixelFormat:Int 		= 1111970369
Const 	keepLocal:Int 	=8

Const NEWMOVIEACTIVE:Int = 1 Shl 0

Const mcTopLeftMovie:Int                = 1 Shl 0 ', /* usually centered */
Const mcScaleMovieToFit:Int              = 1 Shl 1 ', /* usually only scales down */
Const mcWithBadge:Int                    = 1 Shl 2 ', /* give me a badge */
Const mcNotVisible:Int                   = 1 Shl 3 ', /* don't show controller */
Const mcWithFrame :Int  			= 1 Shl 4

Const hintsDontUseVideoOverlaySurface:Int = 1 Shl 16
Const hintsOffscreen:Int                = 1 Shl 12
Const hintsUseScreenBuffer:Int          = 1 Shl 5
Const hintsAllowDynamicResize:Int       = 1 Shl 19

Const kInitializeQTMLUseGDIFlag:Int   = 1 Shl 1

Const deviceIsExternalBuffer:Int        = (1 Shl 3)
Const deviceIsGDISurface:Int = 	64
Const deviceIsIndirect:Int = 	1

''nextInterestingTime flags
Const nextTimeStep:Int =  1 Shl 4 
'nextTimeMediaSample ''not recommended since messes up on mpeg
'nextTimeEdgeOK

Const   kMovieLoadStateError:Int                     = -1
Const   kMovieLoadStateLoading:Int                  = 1000
Const   kMovieLoadStateLoaded:Int                    = 2000
Const   kMovieLoadStatePlayable:Int                  = 10000
Const   kMovieLoadStatePlaythroughOK:Int            = 20000
Const   kMovieLoadStateComplete:Int                  = 100000

Rem

hintsScrubMode                = 1 << 0, /* mask == && (if flags == scrub on, flags != scrub off) */
  hintsLoop                     = 1 << 1,
  hintsDontPurge                = 1 << 2,
  hintsUseScreenBuffer          = 1 << 5,
  hintsAllowInterlace           = 1 << 6,
  hintsUseSoundInterp           = 1 << 7,
  hintsHighQuality              = 1 << 8, /* slooooow */
  hintsPalindrome               = 1 << 9,
  hintsInactive                 = 1 << 11,
  hintsOffscreen                = 1 << 12,
  hintsDontDraw                 = 1 << 13,
  hintsAllowBlacklining         = 1 << 14,
  hintsDontUseVideoOverlaySurface = 1 << 16,
  hintsIgnoreBandwidthRestrictions = 1 << 17,
  hintsPlayingEveryFrame        = 1 << 18,
  hintsAllowDynamicResize       = 1 << 19,
  hintsSingleField              = 1 << 20,
  hintsNoRenderingTimeOut       = 1 << 21,
  hintsFlushVideoInsteadOfDirtying = 1 << 22,
  hintsEnableSubPixelPositioning = 1L << 23,
  hintsRenderingMode            = 1L << 24,
  hintsAllowIdleSleep           = 1L << 25, /* asks media handlers not to call UpdateSystemActivity etc */
  hintsDeinterlaceFields        = 1L << 26
EndRem


Type TQuickTime
	Field isQTInit:Int =0
	Field myQTClass:Byte Ptr = Null
	Field debug:Int
	Field _pixmap:TPixmap
	Field texid:Int
	Field imageframe:TGLImageFrame
	
	Method Init:Int(flags:Int=0)
		If (isQTInit) Then Return True
		If StartQT(flags) <> 0 Then Return False

		If EnterMovies() <> 0
			If debug Then Print "EnterMovies fail"
			EndQT()
			Return False
		EndIf
		myQTClass = bmx_CreateQT()
		isQTInit =1
		If debug Then Print "INIT ok"
		Return True
	EndMethod
	
	Method StartQT:Int(flags:Int =0)
		?Win32
		Local r:Int = InitializeQTML(flags) ''the check for QT on system
		If r Then Print "Quicktime not found."
		Return r
		?MacOS
		Return 0 ''assume QT is installed
		?
		Return 0 ''0= success (error code) in QT

	EndMethod
	
	Method EndQT()
		
		?Win32
		
		TerminateQTML()
		?
		Return
	EndMethod
	
	Method LoadMovieToPixmap:Int(filename:String, pixmap:TPixmap, flags:Int=0, isURL:Int=0)

		Init()
		
		If Not myQTClass Or Not pixmap Then Return False
		Local r:Int
		Local pixw:Int = pixmap.width
		Local pixh:Int = pixmap.height
		r = OpenMovie(filename, 0, isURL)'newMovieActive)
		If r Then Return False
		
		SetupGWorld(pixmap, flags)
		
		Return True
	EndMethod
	
	Method LoadMovieToImage:Int(filename:String, img:TImage, flags:Int=0, isURL:Int=0)

		Init()
		
		If Not myQTClass Or Not img Then Return False
		Local r:Int
		Local pixw:Int = img.width
		Local pixh:Int = img.height
		r = OpenMovie(filename, 0, isURL)'newMovieActive)
		If r Then Return False
		
		If Not img.pixmaps[0] Then img.pixmaps[0] = TPixmap.Create( img.width,img.height,PF_RGB888 )
		If img.pixmaps[0].format <> PF_RGB888 Then img.pixmaps[0] = img.pixmaps[0].convert(PF_RGB888) ;Print "convert to rgb888"
		
		SetupGWorld(img.pixmaps[0], flags)
		
		Return True
	EndMethod
	
	Method SetupGWorld:Int(pixmap:TPixmap, flags:Int=0)
		If Not pixmap Then Return False
		Local r:Int
		
		'r=bmx_NewGWorld(myQTClass, 900, 600, 0)
		r=bmx_QTNewGWorldFromPtr(myQTClass, 24, pixmap.width, pixmap.height,  PixmapPixelPtr(pixmap), pixmap.pitch, deviceIsExternalBuffer)'deviceIsIndirect)
		If debug Then Print "Gworld: "+uinttoint(r)
		
		r = bmx_SetGWorld(myQTClass) ''works better than setmoviegworld in osx
		r = bmx_SetMovieGWorld(myQTClass) ''need both for windows
		If debug Then Print "setGW: "+uinttoint(r)
		
		r = bmx_NewMovieController (myQTClass, mcTopLeftMovie|mcNotVisible|flags)
		If debug Then Print "controller: " +uinttoint(r)
		bmx_SetMoviePlayHints(myQTClass, hintsOffscreen|hintsAllowDynamicResize, hintsOffscreen|hintsAllowDynamicResize )
		If debug Then Print "hints ok"
		r=bmx_SetMovieActive(myQTClass,True)
		If debug Print "setactivem: "+uinttoint(r)
		GotoBeginning()
				
		_pixmap = pixmap
		
		Return True
	EndMethod
	
	''Opening a movie does not necessarily load the entirety
	Method OpenMovie:Int(filename:String, flags:Int =0, isURL:Int = 0)
		Init()
		If Not myQTClass Then Return -1
		
		''determine isURL
		If Not isURL And filename.StartsWith("http:") Then isURL = 1
		
		Local r:Int
		'r = bmx_NativePathNameToFSSpec(myQTClass, filename, 0)
		'If debug Then Print "fsspec: " +uinttoint(r)
		'r= bmx_OpenMovieFile( myQTClass, 1)
		'If debug Then Print "openfile "+uinttoint(r)
		'If(r) Then Return -1
		
		''flags = newMovieActive
		'r=bmx_NewMovieFromFile(myQTClass, flags)
		If isURL Then	r = bmx_NewMovieFromURL(myQTClass, filename) Else r = bmx_NewMovieFromFile(myQTClass, filename)
		
		Local myLoadState:Int= kMovieLoadStateLoading
		Local tick:Int =MilliSecs()
		
		While  Not (myLoadState & kMovieLoadStatePlaythroughOK) 'And tick <> 0
			bmx_GetMovieDuration(myQTClass)
			myLoadState = bmx_GetMovieLoadState(myQTClass)
		Wend
		
		If debug Then Print "newmovie: "+uinttoint(r)
		bmx_CloseMovieFile(myQTClass) ''success close file
		''get width and height and etc
		bmx_GetMovieBox(myQTClass)
		Return 0
	EndMethod
	
	Method IsMovieReady:Int()
		Local k:Int = bmx_GetMovieLoadState(myQTClass)
		If k & kMovieLoadStatePlaythroughOK Then Return True
		Return False
	EndMethod
	
	Method CloseMovie()
		If Not myQTClass Then Return 
		ExitMovies()
		bmx_DisposeMovieController (myQTClass)
		bmx_DisposeGWorld(myQTClass)
		
		EndQT()
		isQtInit =0
		If debug Then Print "CloseMovie ok"
	EndMethod
	
	Method GetMovieWidth:Int()
		Return bmx_GetMovieWidth(myQTClass)
	EndMethod
	Method GetMovieHeight:Int()
		Return bmx_GetMovieHeight(myQTClass)
	EndMethod
	'' in seconds
	Method GetMovieDuration:Float()
		Return bmx_GetMovieDuration(myQTClass)/Float(bmx_GetMovieTimeScale(myQTClass))
	EndMethod
	Method GetMovieTracks:Int()
		Return bmx_GetMovieTrackCount(myQTClass)
	EndMethod
	''in ticks
	Method GetMovieTime:Int()
		Return bmx_GetMovieTime(myQTClass)
	EndMethod
	'' in seconds
	Method GetMovieTimeSecs:Float()
		Return bmx_GetMovieTime(myQTClass)/Float(bmx_GetMovieTimeScale(myQTClass))
	EndMethod
	
	'' volume is between 0.0 and 1.0
	Method SetMovieVolume(vol:Float)
		bmx_SetMovieVolume(myQTClass, vol)
	EndMethod

	''movie time in ticks
	Method SetMovieTime(ms:Int)
		bmx_SetMovieTimeValue(myQTClass, ms )
	EndMethod

	Method StartMovie()
		If Not myQTClass Then Return
		bmx_StartMovie(myQTClass)
		If debug Then Print "startmovie ok"
	EndMethod

	Method StopMovie()
		If Not myQTClass Then Return
		bmx_StopMovie(myQTClass)
		If debug Then Print "stopmovie ok"
	EndMethod

	Method UpdateMovie(update:Int=0)
		'If update Then bmx_UpdateMovie(myQTClass)
		bmx_MoviesTask(myQTClass,0)
		'bmx_UpdateMovie(myQTClass)
	EndMethod
	
	Method IsMovieDone:Int()
		Return bmx_IsMovieDone(myQTClass)
	EndMethod

	Method DrawMovieImage(img:TImage, x:Int, y:Int)
		If Not img Or Not _pixmap Then Return
		
		If Not texid
			imageframe = TGLImageFrame.CreateFromPixmap( _pixmap,0 )
			texid = imageframe.name
			If debug Then Print "draw movie bind"
		EndIf

		img.pixmaps[0]=_pixmap
		img.seqs[0]=0

		If texid
			glBindTexture(GL_TEXTURE_2D, texid)
			glPixelStorei GL_UNPACK_ROW_LENGTH, _pixmap.pitch/BytesPerPixel[_pixmap.format]
			
			glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, _pixmap.width, _pixmap.height, GL_RGB, GL_UNSIGNED_BYTE, _pixmap.pixels)
			glPixelStorei GL_UNPACK_ROW_LENGTH,0
			
			imageframe.Draw( 0,0,_pixmap.width,_pixmap.height, x, y ,0,0,_pixmap.width,_pixmap.height)

		EndIf
			
	EndMethod
	
	Method GotoBeginning()
		bmx_GoToBeginningOfMovie(myQTClass)
	EndMethod

EndType



Function UintToInt:Int(value:Int)
      Const OFFSET_2:Int = 65536
      Const MAXINT_2:Int = 32767

        If Value < 0 Or Value >= OFFSET_2 Then Return value ' Overflow
        If Value <= MAXINT_2 Then
          Return Value
        Else
          Return Value - OFFSET_2
        End If
End Function

Comments

AdamRedwoods2010
Here is a sample test program. You will need to grab your own "Ironman2" trailer for the test or use the URLs.

Use the mouse to click to pause/play and click-hold with a move left/right to scrub the movie.

SEE UPDATED CODE BELOW


xlsior2010
I have this module living under Blitzmax's "pub" module directory.


Not a good idea -- the pub and brl trees both get erased each time you install a blitzmax update on top of an existing installation.

You really should make your own subfolder under the mod folder to prevent that from happening. :-?

but an interesting addition nonetheless. Does it do both video and audio, or just video?


Brucey2010
ModuleInfo "BCC_OPTS: -O2"

I don't think there is such an option - unless Mark sneaked something in there when I wasn't looking?

The latest version of BlitzMax may already be using -O2, however.


AdamRedwoods2010
Thanks for the tips!
I've updated and revised the source code.

Yes, this quicktime module plays audio and video. The sample test program also allows scrubbing through the movie as well.


AdamRedwoods2011
- Updated module, now works with OSX.

- Also added capabilities to load from URL.

See example test file for cool trailers to view.


AdamRedwoods2011
UPDATED 2011 dec 27
- improved playback speed using DrawMovieImage( myimage, x, y)... I was able to play two apple trailers at the same time
- allows scaling, coloring, etc.
- can only use OpenGL

UPDATED sample code:



burpy2012
(deleted)


AdamRedwoods2012
mar 2012
- possible case issue for win32 CCOPTS in "QuickTime". corrected above.


Cobra Blade2013
Recently I tried rebuilding all of BlitzMax's modules using the latest version of OS X & Xcode and received this error from maxquicktime_glue.cpp.

Compile Error

error: 'NewGWorld' was not declared in this scope


Captain Wicker (crazy hillbilly)2013
@CB:
Same here.


Code Archives Forum