| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806480748084809481048114812481348144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836483748384839484048414842484348444845484648474848484948504851485248534854485548564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881488248834884488548864887488848894890489148924893489448954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935493649374938493949404941494249434944494549464947494849494950495149524953495449554956495749584959496049614962496349644965496649674968496949704971497249734974497549764977497849794980498149824983498449854986498749884989499049914992499349944995499649974998499950005001500250035004500550065007500850095010501150125013501450155016501750185019502050215022502350245025502650275028502950305031503250335034503550365037503850395040504150425043504450455046504750485049505050515052505350545055505650575058505950605061506250635064506550665067506850695070507150725073507450755076507750785079508050815082508350845085508650875088508950905091509250935094509550965097509850995100510151025103510451055106510751085109511051115112511351145115511651175118511951205121512251235124512551265127512851295130513151325133513451355136513751385139514051415142514351445145514651475148514951505151515251535154515551565157515851595160516151625163516451655166516751685169517051715172517351745175517651775178517951805181518251835184518551865187518851895190519151925193519451955196519751985199520052015202520352045205520652075208520952105211521252135214521552165217521852195220522152225223522452255226522752285229523052315232523352345235523652375238523952405241524252435244524552465247524852495250525152525253525452555256525752585259526052615262526352645265526652675268526952705271527252735274527552765277527852795280528152825283528452855286528752885289529052915292529352945295529652975298529953005301530253035304530553065307530853095310531153125313531453155316531753185319532053215322532353245325532653275328532953305331533253335334533553365337533853395340534153425343534453455346534753485349535053515352535353545355535653575358535953605361536253635364536553665367536853695370537153725373537453755376537753785379538053815382538353845385538653875388538953905391539253935394539553965397539853995400540154025403540454055406540754085409541054115412541354145415541654175418541954205421542254235424542554265427542854295430543154325433543454355436543754385439544054415442544354445445544654475448544954505451545254535454545554565457545854595460546154625463546454655466546754685469547054715472547354745475547654775478547954805481548254835484548554865487548854895490549154925493549454955496549754985499550055015502550355045505550655075508550955105511551255135514551555165517551855195520552155225523552455255526552755285529553055315532553355345535553655375538553955405541554255435544554555465547554855495550555155525553555455555556555755585559556055615562556355645565556655675568556955705571557255735574557555765577557855795580558155825583558455855586558755885589559055915592559355945595559655975598559956005601560256035604560556065607560856095610561156125613561456155616561756185619562056215622562356245625562656275628562956305631563256335634563556365637563856395640564156425643564456455646564756485649565056515652565356545655565656575658565956605661566256635664566556665667566856695670567156725673567456755676567756785679568056815682568356845685568656875688568956905691569256935694569556965697569856995700570157025703570457055706570757085709571057115712571357145715571657175718571957205721572257235724572557265727572857295730573157325733573457355736573757385739574057415742574357445745574657475748574957505751575257535754575557565757575857595760576157625763576457655766576757685769577057715772577357745775577657775778577957805781578257835784578557865787578857895790579157925793579457955796579757985799580058015802580358045805580658075808580958105811581258135814581558165817581858195820582158225823582458255826582758285829583058315832583358345835583658375838583958405841584258435844584558465847584858495850585158525853585458555856585758585859586058615862586358645865586658675868586958705871587258735874587558765877587858795880588158825883588458855886588758885889589058915892589358945895589658975898589959005901590259035904590559065907590859095910591159125913591459155916591759185919592059215922592359245925592659275928592959305931593259335934593559365937593859395940594159425943594459455946594759485949595059515952595359545955595659575958595959605961596259635964596559665967596859695970597159725973597459755976597759785979598059815982598359845985598659875988598959905991599259935994599559965997599859996000600160026003600460056006600760086009601060116012601360146015601660176018601960206021602260236024602560266027602860296030603160326033603460356036603760386039604060416042604360446045604660476048604960506051605260536054605560566057605860596060606160626063606460656066606760686069607060716072607360746075607660776078607960806081608260836084608560866087608860896090609160926093609460956096609760986099610061016102610361046105610661076108610961106111611261136114611561166117611861196120612161226123612461256126612761286129613061316132613361346135613661376138613961406141614261436144614561466147614861496150615161526153615461556156615761586159616061616162616361646165616661676168616961706171617261736174617561766177617861796180618161826183618461856186618761886189619061916192619361946195619661976198619962006201620262036204620562066207620862096210621162126213621462156216621762186219622062216222622362246225622662276228622962306231623262336234623562366237623862396240624162426243624462456246624762486249625062516252625362546255625662576258625962606261626262636264626562666267626862696270627162726273627462756276627762786279628062816282628362846285628662876288628962906291629262936294629562966297629862996300630163026303630463056306630763086309631063116312631363146315631663176318631963206321632263236324632563266327632863296330633163326333633463356336633763386339634063416342634363446345634663476348634963506351635263536354635563566357635863596360636163626363636463656366636763686369637063716372637363746375637663776378637963806381638263836384638563866387638863896390639163926393639463956396639763986399640064016402640364046405640664076408640964106411641264136414641564166417641864196420642164226423642464256426642764286429643064316432643364346435643664376438643964406441644264436444644564466447644864496450645164526453645464556456645764586459646064616462646364646465646664676468646964706471647264736474647564766477647864796480648164826483648464856486648764886489649064916492649364946495649664976498649965006501650265036504650565066507650865096510651165126513651465156516651765186519652065216522652365246525652665276528652965306531653265336534653565366537653865396540654165426543654465456546654765486549655065516552655365546555655665576558655965606561656265636564656565666567656865696570657165726573657465756576657765786579658065816582658365846585658665876588658965906591659265936594659565966597659865996600660166026603660466056606660766086609661066116612661366146615661666176618661966206621662266236624662566266627662866296630663166326633663466356636663766386639664066416642664366446645664666476648664966506651665266536654665566566657665866596660666166626663666466656666666766686669667066716672667366746675667666776678667966806681668266836684668566866687668866896690669166926693669466956696669766986699670067016702670367046705670667076708670967106711671267136714671567166717671867196720672167226723672467256726672767286729673067316732673367346735673667376738673967406741674267436744674567466747674867496750675167526753675467556756675767586759676067616762676367646765676667676768676967706771677267736774677567766777677867796780678167826783678467856786678767886789679067916792679367946795679667976798679968006801680268036804680568066807680868096810681168126813681468156816681768186819682068216822682368246825682668276828682968306831683268336834683568366837683868396840684168426843684468456846684768486849685068516852685368546855685668576858685968606861686268636864686568666867686868696870687168726873687468756876687768786879688068816882688368846885688668876888688968906891689268936894689568966897689868996900690169026903690469056906690769086909691069116912691369146915691669176918691969206921692269236924692569266927692869296930693169326933693469356936693769386939694069416942694369446945694669476948694969506951695269536954695569566957695869596960696169626963696469656966696769686969697069716972697369746975697669776978697969806981698269836984698569866987698869896990699169926993699469956996699769986999700070017002700370047005700670077008700970107011701270137014701570167017701870197020702170227023702470257026702770287029703070317032703370347035703670377038703970407041704270437044704570467047704870497050705170527053705470557056705770587059706070617062706370647065706670677068706970707071707270737074707570767077707870797080708170827083708470857086708770887089709070917092709370947095709670977098709971007101710271037104710571067107710871097110711171127113711471157116711771187119712071217122712371247125712671277128712971307131713271337134713571367137713871397140714171427143714471457146714771487149715071517152715371547155715671577158715971607161716271637164716571667167716871697170717171727173717471757176717771787179718071817182718371847185718671877188718971907191719271937194719571967197719871997200720172027203720472057206720772087209721072117212721372147215721672177218721972207221722272237224722572267227722872297230723172327233723472357236723772387239724072417242724372447245724672477248724972507251725272537254725572567257725872597260726172627263726472657266726772687269727072717272727372747275727672777278727972807281728272837284728572867287728872897290729172927293729472957296729772987299730073017302730373047305730673077308730973107311731273137314731573167317731873197320732173227323732473257326732773287329733073317332733373347335733673377338733973407341734273437344734573467347734873497350735173527353735473557356735773587359736073617362736373647365736673677368736973707371737273737374737573767377737873797380738173827383738473857386738773887389739073917392739373947395739673977398739974007401740274037404740574067407740874097410741174127413741474157416741774187419742074217422742374247425742674277428742974307431743274337434743574367437743874397440744174427443744474457446744774487449745074517452745374547455745674577458745974607461746274637464746574667467746874697470747174727473747474757476747774787479748074817482748374847485748674877488748974907491749274937494749574967497749874997500750175027503750475057506750775087509751075117512751375147515751675177518751975207521752275237524752575267527752875297530753175327533753475357536753775387539754075417542754375447545754675477548754975507551755275537554755575567557755875597560756175627563756475657566756775687569757075717572757375747575757675777578757975807581758275837584758575867587758875897590759175927593759475957596759775987599760076017602760376047605760676077608760976107611761276137614761576167617761876197620762176227623762476257626762776287629763076317632763376347635763676377638763976407641764276437644764576467647764876497650765176527653765476557656765776587659766076617662766376647665766676677668766976707671767276737674767576767677767876797680768176827683768476857686768776887689769076917692769376947695769676977698769977007701770277037704770577067707770877097710771177127713771477157716771777187719772077217722772377247725772677277728772977307731773277337734773577367737773877397740774177427743774477457746774777487749775077517752775377547755775677577758775977607761776277637764776577667767776877697770777177727773777477757776777777787779778077817782778377847785778677877788778977907791779277937794779577967797779877997800780178027803780478057806780778087809781078117812781378147815781678177818781978207821782278237824782578267827782878297830783178327833783478357836783778387839784078417842784378447845784678477848784978507851785278537854785578567857785878597860786178627863786478657866786778687869787078717872787378747875787678777878787978807881788278837884788578867887788878897890789178927893789478957896789778987899790079017902790379047905790679077908790979107911791279137914791579167917791879197920792179227923792479257926792779287929793079317932793379347935793679377938793979407941794279437944794579467947794879497950795179527953795479557956795779587959796079617962796379647965796679677968796979707971797279737974797579767977797879797980798179827983798479857986798779887989799079917992799379947995799679977998799980008001800280038004800580068007800880098010801180128013801480158016801780188019802080218022802380248025802680278028802980308031803280338034803580368037803880398040804180428043804480458046804780488049805080518052805380548055805680578058805980608061806280638064806580668067806880698070807180728073807480758076807780788079808080818082808380848085808680878088808980908091809280938094809580968097809880998100810181028103810481058106810781088109811081118112811381148115811681178118811981208121812281238124812581268127812881298130813181328133813481358136813781388139814081418142814381448145814681478148814981508151815281538154815581568157815881598160816181628163816481658166816781688169817081718172817381748175817681778178817981808181818281838184818581868187818881898190819181928193819481958196819781988199820082018202820382048205820682078208820982108211821282138214821582168217821882198220822182228223822482258226822782288229823082318232823382348235823682378238823982408241824282438244824582468247824882498250825182528253825482558256825782588259826082618262826382648265826682678268826982708271827282738274827582768277827882798280828182828283828482858286828782888289829082918292829382948295829682978298829983008301830283038304830583068307830883098310831183128313831483158316831783188319832083218322832383248325832683278328832983308331833283338334833583368337833883398340834183428343834483458346834783488349835083518352835383548355835683578358835983608361836283638364836583668367836883698370837183728373837483758376837783788379838083818382838383848385838683878388838983908391839283938394839583968397839883998400840184028403840484058406840784088409841084118412841384148415841684178418841984208421842284238424842584268427842884298430843184328433843484358436843784388439844084418442844384448445844684478448844984508451845284538454845584568457845884598460846184628463846484658466846784688469847084718472847384748475847684778478847984808481848284838484848584868487848884898490849184928493849484958496849784988499850085018502850385048505850685078508850985108511851285138514851585168517851885198520852185228523852485258526852785288529853085318532853385348535853685378538853985408541854285438544854585468547854885498550855185528553855485558556855785588559856085618562856385648565856685678568856985708571857285738574857585768577857885798580858185828583858485858586858785888589859085918592859385948595859685978598859986008601860286038604860586068607860886098610861186128613861486158616861786188619862086218622862386248625862686278628862986308631863286338634863586368637863886398640864186428643864486458646864786488649865086518652865386548655865686578658865986608661866286638664866586668667866886698670867186728673867486758676867786788679868086818682868386848685868686878688868986908691869286938694869586968697869886998700870187028703870487058706870787088709871087118712871387148715871687178718871987208721872287238724872587268727872887298730873187328733873487358736873787388739874087418742874387448745874687478748874987508751875287538754875587568757875887598760876187628763876487658766876787688769877087718772877387748775877687778778877987808781878287838784878587868787878887898790879187928793879487958796879787988799880088018802880388048805880688078808880988108811881288138814881588168817881888198820882188228823882488258826882788288829883088318832883388348835883688378838883988408841884288438844884588468847884888498850885188528853885488558856885788588859886088618862886388648865886688678868886988708871887288738874887588768877887888798880888188828883888488858886888788888889889088918892889388948895889688978898889989008901890289038904890589068907890889098910891189128913891489158916891789188919892089218922892389248925892689278928892989308931893289338934893589368937893889398940894189428943894489458946894789488949895089518952895389548955895689578958895989608961896289638964896589668967896889698970897189728973897489758976897789788979898089818982898389848985898689878988898989908991899289938994899589968997899889999000900190029003900490059006900790089009901090119012901390149015901690179018901990209021902290239024902590269027902890299030903190329033903490359036903790389039904090419042904390449045904690479048904990509051905290539054905590569057905890599060906190629063906490659066906790689069907090719072907390749075907690779078907990809081908290839084908590869087908890899090909190929093909490959096909790989099910091019102910391049105910691079108910991109111911291139114911591169117911891199120912191229123912491259126912791289129913091319132913391349135913691379138913991409141914291439144914591469147914891499150915191529153915491559156915791589159916091619162916391649165916691679168916991709171917291739174917591769177917891799180918191829183918491859186918791889189919091919192919391949195919691979198919992009201920292039204920592069207920892099210921192129213921492159216921792189219922092219222922392249225922692279228922992309231923292339234923592369237923892399240924192429243924492459246924792489249925092519252925392549255925692579258925992609261926292639264926592669267926892699270927192729273927492759276927792789279928092819282928392849285928692879288928992909291929292939294929592969297929892999300930193029303930493059306930793089309931093119312931393149315931693179318931993209321932293239324932593269327932893299330933193329333933493359336933793389339934093419342934393449345934693479348934993509351935293539354935593569357935893599360936193629363936493659366936793689369937093719372937393749375937693779378937993809381938293839384938593869387938893899390939193929393939493959396939793989399940094019402940394049405940694079408940994109411941294139414941594169417941894199420942194229423942494259426942794289429943094319432943394349435943694379438943994409441944294439444944594469447944894499450945194529453945494559456945794589459946094619462946394649465946694679468946994709471947294739474947594769477947894799480948194829483948494859486948794889489949094919492949394949495949694979498949995009501950295039504950595069507950895099510951195129513951495159516951795189519952095219522952395249525952695279528952995309531953295339534953595369537953895399540954195429543954495459546954795489549955095519552955395549555955695579558955995609561956295639564956595669567956895699570957195729573957495759576957795789579958095819582958395849585958695879588958995909591959295939594959595969597959895999600960196029603960496059606960796089609961096119612961396149615961696179618961996209621962296239624962596269627962896299630963196329633963496359636963796389639964096419642964396449645964696479648964996509651965296539654965596569657965896599660966196629663966496659666966796689669967096719672967396749675967696779678967996809681968296839684968596869687968896899690969196929693969496959696969796989699970097019702970397049705970697079708970997109711971297139714971597169717971897199720972197229723972497259726972797289729973097319732973397349735973697379738973997409741974297439744974597469747974897499750975197529753975497559756975797589759976097619762976397649765976697679768976997709771977297739774977597769777977897799780978197829783978497859786978797889789979097919792979397949795979697979798979998009801980298039804980598069807980898099810981198129813981498159816981798189819982098219822982398249825982698279828982998309831983298339834983598369837983898399840984198429843984498459846984798489849985098519852985398549855985698579858985998609861986298639864986598669867986898699870987198729873987498759876987798789879988098819882988398849885988698879888988998909891989298939894989598969897989898999900990199029903990499059906990799089909991099119912991399149915991699179918991999209921992299239924992599269927992899299930993199329933993499359936993799389939994099419942994399449945994699479948994999509951995299539954995599569957995899599960996199629963996499659966996799689969997099719972997399749975997699779978997999809981998299839984998599869987998899899990999199929993999499959996999799989999100001000110002100031000410005100061000710008100091001010011100121001310014100151001610017100181001910020100211002210023100241002510026100271002810029100301003110032100331003410035100361003710038100391004010041100421004310044100451004610047100481004910050100511005210053100541005510056100571005810059100601006110062100631006410065100661006710068100691007010071100721007310074100751007610077100781007910080100811008210083100841008510086100871008810089100901009110092100931009410095100961009710098100991010010101101021010310104101051010610107101081010910110101111011210113101141011510116101171011810119101201012110122101231012410125101261012710128101291013010131101321013310134101351013610137101381013910140101411014210143101441014510146101471014810149101501015110152101531015410155101561015710158101591016010161101621016310164101651016610167101681016910170101711017210173101741017510176101771017810179101801018110182101831018410185101861018710188101891019010191101921019310194101951019610197101981019910200102011020210203102041020510206102071020810209102101021110212102131021410215102161021710218102191022010221102221022310224102251022610227102281022910230102311023210233102341023510236102371023810239102401024110242102431024410245102461024710248102491025010251102521025310254102551025610257102581025910260102611026210263102641026510266102671026810269102701027110272102731027410275102761027710278102791028010281102821028310284102851028610287102881028910290102911029210293102941029510296102971029810299103001030110302103031030410305103061030710308103091031010311103121031310314103151031610317103181031910320103211032210323103241032510326103271032810329103301033110332103331033410335103361033710338103391034010341103421034310344103451034610347103481034910350103511035210353103541035510356103571035810359103601036110362103631036410365103661036710368103691037010371103721037310374103751037610377103781037910380103811038210383103841038510386103871038810389103901039110392103931039410395103961039710398103991040010401104021040310404104051040610407104081040910410104111041210413104141041510416104171041810419104201042110422104231042410425104261042710428104291043010431104321043310434104351043610437104381043910440104411044210443104441044510446104471044810449104501045110452104531045410455104561045710458104591046010461104621046310464104651046610467104681046910470104711047210473104741047510476104771047810479104801048110482104831048410485104861048710488104891049010491104921049310494104951049610497104981049910500105011050210503105041050510506105071050810509105101051110512105131051410515105161051710518105191052010521105221052310524105251052610527105281052910530105311053210533105341053510536105371053810539105401054110542105431054410545105461054710548105491055010551105521055310554105551055610557105581055910560105611056210563105641056510566105671056810569105701057110572105731057410575105761057710578105791058010581105821058310584105851058610587105881058910590105911059210593105941059510596105971059810599106001060110602106031060410605106061060710608106091061010611106121061310614106151061610617106181061910620106211062210623106241062510626106271062810629106301063110632106331063410635106361063710638106391064010641106421064310644106451064610647106481064910650106511065210653106541065510656106571065810659106601066110662106631066410665106661066710668106691067010671106721067310674106751067610677106781067910680106811068210683106841068510686106871068810689106901069110692106931069410695106961069710698106991070010701107021070310704107051070610707107081070910710107111071210713107141071510716107171071810719107201072110722107231072410725107261072710728107291073010731107321073310734107351073610737107381073910740107411074210743107441074510746107471074810749107501075110752107531075410755107561075710758107591076010761107621076310764107651076610767107681076910770107711077210773107741077510776107771077810779107801078110782107831078410785107861078710788107891079010791107921079310794107951079610797107981079910800108011080210803108041080510806108071080810809108101081110812108131081410815108161081710818108191082010821108221082310824108251082610827108281082910830108311083210833108341083510836108371083810839108401084110842108431084410845108461084710848108491085010851108521085310854108551085610857108581085910860108611086210863108641086510866108671086810869108701087110872108731087410875108761087710878108791088010881108821088310884108851088610887108881088910890108911089210893108941089510896108971089810899109001090110902109031090410905109061090710908109091091010911109121091310914109151091610917109181091910920109211092210923109241092510926109271092810929109301093110932109331093410935109361093710938109391094010941109421094310944109451094610947109481094910950109511095210953109541095510956109571095810959109601096110962109631096410965109661096710968109691097010971109721097310974109751097610977109781097910980109811098210983109841098510986109871098810989109901099110992109931099410995109961099710998109991100011001110021100311004110051100611007110081100911010110111101211013110141101511016110171101811019110201102111022110231102411025110261102711028110291103011031110321103311034110351103611037110381103911040110411104211043110441104511046110471104811049110501105111052110531105411055110561105711058110591106011061110621106311064110651106611067110681106911070110711107211073110741107511076110771107811079110801108111082110831108411085110861108711088110891109011091110921109311094110951109611097110981109911100111011110211103111041110511106111071110811109111101111111112111131111411115111161111711118111191112011121111221112311124111251112611127111281112911130111311113211133111341113511136111371113811139111401114111142111431114411145111461114711148111491115011151111521115311154111551115611157111581115911160111611116211163111641116511166111671116811169111701117111172111731117411175111761117711178111791118011181111821118311184111851118611187111881118911190111911119211193111941119511196111971119811199112001120111202112031120411205112061120711208112091121011211112121121311214112151121611217112181121911220112211122211223112241122511226112271122811229112301123111232112331123411235112361123711238112391124011241112421124311244112451124611247112481124911250112511125211253112541125511256112571125811259112601126111262112631126411265112661126711268112691127011271112721127311274112751127611277112781127911280112811128211283112841128511286112871128811289112901129111292112931129411295112961129711298112991130011301113021130311304113051130611307113081130911310113111131211313113141131511316113171131811319113201132111322113231132411325113261132711328113291133011331113321133311334113351133611337113381133911340113411134211343113441134511346113471134811349113501135111352113531135411355113561135711358113591136011361113621136311364113651136611367113681136911370113711137211373113741137511376113771137811379113801138111382113831138411385113861138711388113891139011391113921139311394113951139611397113981139911400114011140211403114041140511406114071140811409114101141111412114131141411415114161141711418114191142011421114221142311424114251142611427114281142911430114311143211433114341143511436114371143811439114401144111442114431144411445114461144711448114491145011451114521145311454114551145611457114581145911460114611146211463114641146511466114671146811469114701147111472114731147411475114761147711478114791148011481114821148311484114851148611487114881148911490114911149211493114941149511496114971149811499115001150111502115031150411505115061150711508115091151011511115121151311514115151151611517115181151911520115211152211523115241152511526115271152811529115301153111532115331153411535115361153711538115391154011541115421154311544115451154611547115481154911550115511155211553115541155511556115571155811559115601156111562115631156411565115661156711568115691157011571115721157311574115751157611577115781157911580115811158211583115841158511586115871158811589115901159111592115931159411595115961159711598115991160011601116021160311604116051160611607116081160911610116111161211613116141161511616116171161811619116201162111622116231162411625116261162711628116291163011631116321163311634116351163611637116381163911640116411164211643116441164511646116471164811649116501165111652116531165411655116561165711658116591166011661116621166311664116651166611667116681166911670116711167211673116741167511676116771167811679116801168111682116831168411685116861168711688116891169011691116921169311694116951169611697116981169911700117011170211703117041170511706117071170811709117101171111712117131171411715117161171711718117191172011721117221172311724117251172611727117281172911730117311173211733117341173511736117371173811739117401174111742117431174411745117461174711748117491175011751117521175311754117551175611757117581175911760117611176211763117641176511766117671176811769117701177111772117731177411775117761177711778117791178011781117821178311784117851178611787117881178911790117911179211793117941179511796117971179811799118001180111802118031180411805118061180711808118091181011811118121181311814118151181611817118181181911820118211182211823118241182511826118271182811829118301183111832118331183411835118361183711838118391184011841118421184311844118451184611847118481184911850118511185211853118541185511856118571185811859118601186111862118631186411865118661186711868118691187011871118721187311874118751187611877118781187911880118811188211883118841188511886118871188811889118901189111892118931189411895118961189711898118991190011901119021190311904119051190611907119081190911910119111191211913119141191511916119171191811919119201192111922119231192411925119261192711928119291193011931119321193311934119351193611937119381193911940119411194211943119441194511946119471194811949119501195111952119531195411955119561195711958119591196011961119621196311964119651196611967119681196911970119711197211973119741197511976119771197811979119801198111982119831198411985119861198711988119891199011991119921199311994119951199611997119981199912000120011200212003120041200512006120071200812009120101201112012120131201412015120161201712018120191202012021120221202312024120251202612027120281202912030120311203212033120341203512036120371203812039120401204112042120431204412045120461204712048120491205012051120521205312054120551205612057120581205912060120611206212063120641206512066120671206812069120701207112072120731207412075120761207712078120791208012081120821208312084120851208612087120881208912090120911209212093120941209512096120971209812099121001210112102121031210412105121061210712108121091211012111121121211312114121151211612117121181211912120121211212212123121241212512126121271212812129121301213112132121331213412135121361213712138121391214012141121421214312144121451214612147121481214912150121511215212153121541215512156121571215812159121601216112162121631216412165121661216712168121691217012171121721217312174121751217612177121781217912180121811218212183121841218512186121871218812189121901219112192121931219412195121961219712198121991220012201122021220312204122051220612207122081220912210122111221212213122141221512216122171221812219122201222112222122231222412225122261222712228122291223012231122321223312234122351223612237122381223912240122411224212243122441224512246122471224812249122501225112252122531225412255122561225712258122591226012261122621226312264122651226612267122681226912270122711227212273122741227512276122771227812279122801228112282122831228412285122861228712288122891229012291122921229312294122951229612297122981229912300123011230212303123041230512306123071230812309123101231112312123131231412315123161231712318123191232012321123221232312324123251232612327123281232912330123311233212333123341233512336123371233812339123401234112342123431234412345123461234712348123491235012351123521235312354123551235612357123581235912360123611236212363123641236512366123671236812369123701237112372123731237412375123761237712378123791238012381123821238312384123851238612387123881238912390123911239212393123941239512396123971239812399124001240112402124031240412405124061240712408124091241012411124121241312414124151241612417124181241912420124211242212423124241242512426124271242812429124301243112432124331243412435124361243712438124391244012441124421244312444124451244612447124481244912450124511245212453124541245512456124571245812459124601246112462124631246412465124661246712468124691247012471124721247312474124751247612477124781247912480124811248212483124841248512486124871248812489124901249112492124931249412495124961249712498124991250012501125021250312504125051250612507125081250912510125111251212513125141251512516125171251812519125201252112522125231252412525125261252712528125291253012531125321253312534125351253612537125381253912540125411254212543125441254512546125471254812549125501255112552125531255412555125561255712558125591256012561125621256312564125651256612567125681256912570125711257212573125741257512576125771257812579125801258112582125831258412585125861258712588125891259012591125921259312594125951259612597125981259912600126011260212603126041260512606126071260812609126101261112612126131261412615126161261712618126191262012621126221262312624126251262612627126281262912630126311263212633126341263512636126371263812639126401264112642126431264412645126461264712648126491265012651126521265312654126551265612657126581265912660126611266212663126641266512666126671266812669126701267112672126731267412675126761267712678126791268012681126821268312684126851268612687126881268912690126911269212693126941269512696126971269812699127001270112702127031270412705127061270712708127091271012711127121271312714127151271612717127181271912720127211272212723127241272512726127271272812729127301273112732127331273412735127361273712738127391274012741127421274312744127451274612747127481274912750127511275212753127541275512756127571275812759127601276112762127631276412765127661276712768127691277012771127721277312774127751277612777127781277912780127811278212783127841278512786127871278812789127901279112792127931279412795127961279712798127991280012801128021280312804128051280612807128081280912810128111281212813128141281512816128171281812819128201282112822128231282412825128261282712828128291283012831128321283312834128351283612837128381283912840128411284212843128441284512846128471284812849128501285112852128531285412855128561285712858128591286012861128621286312864128651286612867128681286912870128711287212873128741287512876128771287812879128801288112882128831288412885128861288712888128891289012891128921289312894128951289612897128981289912900129011290212903129041290512906129071290812909129101291112912129131291412915129161291712918129191292012921129221292312924129251292612927129281292912930129311293212933129341293512936129371293812939129401294112942129431294412945129461294712948129491295012951129521295312954129551295612957129581295912960129611296212963129641296512966129671296812969129701297112972129731297412975129761297712978129791298012981129821298312984129851298612987129881298912990129911299212993129941299512996129971299812999130001300113002130031300413005130061300713008130091301013011130121301313014130151301613017130181301913020130211302213023130241302513026130271302813029130301303113032130331303413035130361303713038130391304013041130421304313044130451304613047130481304913050130511305213053130541305513056130571305813059130601306113062130631306413065130661306713068130691307013071130721307313074130751307613077130781307913080130811308213083130841308513086130871308813089130901309113092130931309413095130961309713098130991310013101131021310313104131051310613107131081310913110131111311213113131141311513116131171311813119131201312113122131231312413125131261312713128131291313013131131321313313134131351313613137131381313913140131411314213143131441314513146131471314813149131501315113152131531315413155131561315713158131591316013161131621316313164131651316613167131681316913170131711317213173131741317513176131771317813179131801318113182131831318413185131861318713188131891319013191131921319313194131951319613197131981319913200132011320213203132041320513206132071320813209132101321113212132131321413215132161321713218132191322013221132221322313224132251322613227132281322913230132311323213233132341323513236132371323813239132401324113242132431324413245132461324713248132491325013251132521325313254132551325613257132581325913260132611326213263132641326513266132671326813269132701327113272132731327413275132761327713278132791328013281132821328313284132851328613287132881328913290132911329213293132941329513296132971329813299133001330113302133031330413305133061330713308133091331013311133121331313314133151331613317133181331913320133211332213323133241332513326133271332813329133301333113332133331333413335133361333713338133391334013341133421334313344133451334613347133481334913350133511335213353133541335513356133571335813359133601336113362133631336413365133661336713368133691337013371133721337313374133751337613377133781337913380133811338213383133841338513386133871338813389133901339113392133931339413395133961339713398133991340013401134021340313404134051340613407134081340913410134111341213413134141341513416134171341813419134201342113422134231342413425134261342713428134291343013431134321343313434134351343613437134381343913440134411344213443134441344513446134471344813449134501345113452134531345413455134561345713458134591346013461134621346313464134651346613467134681346913470134711347213473134741347513476134771347813479134801348113482134831348413485134861348713488134891349013491134921349313494134951349613497134981349913500135011350213503135041350513506135071350813509135101351113512135131351413515135161351713518135191352013521135221352313524135251352613527135281352913530135311353213533135341353513536135371353813539135401354113542135431354413545135461354713548135491355013551135521355313554135551355613557135581355913560135611356213563135641356513566135671356813569135701357113572135731357413575135761357713578135791358013581135821358313584135851358613587135881358913590135911359213593135941359513596135971359813599136001360113602136031360413605136061360713608136091361013611136121361313614136151361613617136181361913620136211362213623136241362513626136271362813629136301363113632136331363413635136361363713638136391364013641136421364313644136451364613647136481364913650136511365213653136541365513656136571365813659136601366113662136631366413665136661366713668136691367013671136721367313674136751367613677136781367913680136811368213683136841368513686136871368813689136901369113692136931369413695136961369713698136991370013701137021370313704137051370613707137081370913710137111371213713137141371513716137171371813719137201372113722137231372413725137261372713728137291373013731137321373313734137351373613737137381373913740137411374213743137441374513746137471374813749137501375113752137531375413755137561375713758137591376013761137621376313764137651376613767137681376913770137711377213773137741377513776137771377813779137801378113782137831378413785137861378713788137891379013791137921379313794137951379613797137981379913800138011380213803138041380513806138071380813809138101381113812138131381413815138161381713818138191382013821138221382313824138251382613827138281382913830138311383213833138341383513836138371383813839138401384113842138431384413845138461384713848138491385013851138521385313854138551385613857138581385913860138611386213863138641386513866138671386813869138701387113872138731387413875138761387713878138791388013881138821388313884138851388613887138881388913890138911389213893138941389513896138971389813899139001390113902139031390413905139061390713908139091391013911139121391313914139151391613917139181391913920139211392213923139241392513926139271392813929139301393113932139331393413935139361393713938139391394013941139421394313944139451394613947139481394913950139511395213953139541395513956139571395813959139601396113962139631396413965139661396713968139691397013971139721397313974139751397613977139781397913980139811398213983139841398513986139871398813989139901399113992139931399413995139961399713998139991400014001140021400314004140051400614007140081400914010140111401214013140141401514016140171401814019140201402114022140231402414025140261402714028140291403014031140321403314034140351403614037140381403914040140411404214043140441404514046140471404814049140501405114052140531405414055140561405714058140591406014061140621406314064140651406614067140681406914070140711407214073140741407514076140771407814079140801408114082140831408414085140861408714088140891409014091140921409314094140951409614097140981409914100141011410214103141041410514106141071410814109141101411114112141131411414115141161411714118141191412014121141221412314124141251412614127141281412914130141311413214133141341413514136141371413814139141401414114142141431414414145141461414714148141491415014151141521415314154141551415614157141581415914160141611416214163141641416514166141671416814169141701417114172141731417414175141761417714178141791418014181141821418314184141851418614187141881418914190141911419214193141941419514196141971419814199142001420114202142031420414205142061420714208142091421014211142121421314214142151421614217142181421914220142211422214223142241422514226142271422814229142301423114232142331423414235142361423714238142391424014241142421424314244142451424614247142481424914250142511425214253142541425514256142571425814259142601426114262142631426414265142661426714268142691427014271142721427314274142751427614277142781427914280142811428214283142841428514286142871428814289142901429114292142931429414295142961429714298142991430014301143021430314304143051430614307143081430914310143111431214313143141431514316143171431814319143201432114322143231432414325143261432714328143291433014331143321433314334143351433614337143381433914340143411434214343143441434514346143471434814349143501435114352143531435414355143561435714358143591436014361143621436314364143651436614367143681436914370143711437214373143741437514376143771437814379143801438114382143831438414385143861438714388143891439014391143921439314394143951439614397143981439914400144011440214403144041440514406144071440814409144101441114412144131441414415144161441714418144191442014421144221442314424144251442614427144281442914430144311443214433144341443514436144371443814439144401444114442144431444414445144461444714448144491445014451144521445314454144551445614457144581445914460144611446214463144641446514466144671446814469144701447114472144731447414475144761447714478144791448014481144821448314484144851448614487144881448914490144911449214493144941449514496144971449814499145001450114502145031450414505145061450714508145091451014511145121451314514145151451614517145181451914520145211452214523145241452514526145271452814529145301453114532145331453414535145361453714538145391454014541145421454314544145451454614547145481454914550145511455214553145541455514556145571455814559145601456114562145631456414565145661456714568145691457014571145721457314574145751457614577145781457914580145811458214583145841458514586145871458814589145901459114592145931459414595145961459714598145991460014601146021460314604146051460614607146081460914610146111461214613146141461514616146171461814619146201462114622146231462414625146261462714628146291463014631146321463314634146351463614637146381463914640146411464214643146441464514646146471464814649146501465114652146531465414655146561465714658146591466014661146621466314664146651466614667146681466914670146711467214673146741467514676146771467814679146801468114682146831468414685146861468714688146891469014691146921469314694146951469614697146981469914700147011470214703147041470514706147071470814709147101471114712147131471414715147161471714718147191472014721147221472314724147251472614727147281472914730147311473214733147341473514736147371473814739147401474114742147431474414745147461474714748147491475014751147521475314754147551475614757147581475914760147611476214763147641476514766147671476814769147701477114772147731477414775147761477714778147791478014781147821478314784147851478614787147881478914790147911479214793147941479514796147971479814799148001480114802148031480414805148061480714808148091481014811148121481314814148151481614817148181481914820148211482214823148241482514826148271482814829148301483114832148331483414835148361483714838148391484014841148421484314844148451484614847148481484914850148511485214853148541485514856148571485814859148601486114862148631486414865148661486714868148691487014871148721487314874148751487614877148781487914880148811488214883148841488514886148871488814889148901489114892148931489414895148961489714898148991490014901149021490314904149051490614907149081490914910149111491214913149141491514916149171491814919149201492114922149231492414925149261492714928149291493014931149321493314934149351493614937149381493914940149411494214943149441494514946149471494814949149501495114952149531495414955149561495714958149591496014961149621496314964149651496614967149681496914970149711497214973149741497514976149771497814979149801498114982149831498414985149861498714988149891499014991149921499314994149951499614997149981499915000150011500215003150041500515006150071500815009150101501115012150131501415015150161501715018150191502015021150221502315024150251502615027150281502915030150311503215033150341503515036150371503815039150401504115042150431504415045150461504715048150491505015051150521505315054150551505615057150581505915060150611506215063150641506515066150671506815069150701507115072150731507415075150761507715078150791508015081150821508315084150851508615087150881508915090150911509215093150941509515096150971509815099151001510115102151031510415105151061510715108151091511015111151121511315114151151511615117151181511915120151211512215123151241512515126151271512815129151301513115132151331513415135151361513715138151391514015141151421514315144151451514615147151481514915150151511515215153151541515515156151571515815159151601516115162151631516415165151661516715168151691517015171151721517315174151751517615177151781517915180151811518215183151841518515186151871518815189151901519115192151931519415195151961519715198151991520015201152021520315204152051520615207152081520915210152111521215213152141521515216152171521815219152201522115222152231522415225152261522715228152291523015231152321523315234152351523615237152381523915240152411524215243152441524515246152471524815249152501525115252152531525415255152561525715258152591526015261152621526315264152651526615267152681526915270152711527215273152741527515276152771527815279152801528115282152831528415285152861528715288152891529015291152921529315294152951529615297152981529915300153011530215303153041530515306153071530815309153101531115312153131531415315153161531715318153191532015321153221532315324153251532615327153281532915330153311533215333153341533515336153371533815339153401534115342153431534415345153461534715348153491535015351153521535315354153551535615357153581535915360153611536215363153641536515366153671536815369153701537115372153731537415375153761537715378153791538015381153821538315384153851538615387153881538915390153911539215393153941539515396153971539815399154001540115402154031540415405154061540715408154091541015411154121541315414154151541615417154181541915420154211542215423154241542515426154271542815429154301543115432154331543415435154361543715438154391544015441154421544315444154451544615447154481544915450154511545215453154541545515456154571545815459154601546115462154631546415465154661546715468154691547015471154721547315474154751547615477154781547915480154811548215483154841548515486154871548815489154901549115492154931549415495154961549715498154991550015501155021550315504155051550615507155081550915510155111551215513155141551515516155171551815519155201552115522155231552415525155261552715528155291553015531155321553315534155351553615537155381553915540155411554215543155441554515546155471554815549155501555115552155531555415555155561555715558155591556015561155621556315564155651556615567155681556915570155711557215573155741557515576155771557815579155801558115582 |
- (*********************************************************************
- * DSPack 2.3.3 *
- * DirectShow BaseClass *
- * *
- * home page : http://www.progdigy.com *
- * email : hgourvest@progdigy.com *
- * *
- * date : 21-02-2003 *
- * *
- * The contents of this file are used with permission, subject to *
- * the Mozilla Public License Version 1.1 (the "License"); you may *
- * not use this file except in compliance with the License. You may *
- * obtain a copy of the License at *
- * http://www.mozilla.org/MPL/MPL-1.1.html *
- * *
- * Software distributed under the License is distributed on an *
- * "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or *
- * implied. See the License for the specific language governing *
- * rights and limitations under the License. *
- * *
- * Contributor(s) *
- * Andriy Nevhasymyy <a.n@email.com> *
- * Milenko Mitrovic <dcoder@dsp-worx.de> *
- * Michael Andersen <michael@mechdata.dk> *
- * Martin Offenwanger <coder@dsplayer.de> *
- * *
- *********************************************************************)
- {.$DEFINE DEBUG} // Debug Log
- {.$DEFINE TRACE} // Trace Criteral Section (DEBUG must be ON)
- {.$DEFINE MESSAGE} // Use OutputDebugString instead of a File (DEBUG must be ON)
- {.$DEFINE PERF} // Show Performace Counter
- {.$DEFINE VTRANSPERF} // Show additional TBCVideoTransformFilter Performace Counter (PERF must be ON)
- {$MINENUMSIZE 4}
- {$ALIGN ON}
- unit BaseClass;
- {$IFDEF VER150}
- {$WARN UNSAFE_CODE OFF}
- {$WARN UNSAFE_TYPE OFF}
- {$WARN UNSAFE_CAST OFF}
- {$ENDIF}
- interface
- uses Windows, SysUtils, Classes, Math, ActiveX, Forms, Messages, Controls,
- DirectShow9, dialogs, ComObj, mmsystem, DSUtil;
- const
- OATRUE = -1;
- OAFALSE = 0;
- DEFAULTCACHE = 10; // Default node object cache size
- type
- TBCCritSec = class
- private
- FCritSec : TRTLCriticalSection;
- {$IFDEF DEBUG}
- FcurrentOwner: Longword;
- FlockCount : Longword;
- {$ENDIF}
- public
- constructor Create;
- destructor Destroy; override;
- procedure Lock;
- procedure UnLock;
- function CritCheckIn: boolean;
- function CritCheckOut: boolean;
- end;
- TBCBaseObject = class(TObJect)
- private
- FName: string;
- public
- constructor Create(Name: string);
- destructor Destroy; override;
- class function NewInstance: TObject; override;
- procedure FreeInstance; override;
- class function ObjectsActive: integer;
- end;
- TBCClassFactory = Class;
- TBCUnknown = class(TBCBaseObject, IUnKnown)
- private
- FRefCount: integer;
- FOwner : Pointer;
- protected
- function IUnknown.QueryInterface = NonDelegatingQueryInterface;
- function IUnknown._AddRef = NonDelegatingAddRef;
- function IUnknown._Release = NonDelegatingRelease;
- function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
- public
- function _AddRef: Integer; stdcall;
- function _Release: Integer; stdcall;
- constructor Create(name: string; Unk: IUnknown);
- constructor CreateFromFactory(Factory: TBCClassFactory; const Controller: IUnknown); virtual;
- function NonDelegatingQueryInterface(const IID: TGUID; out Obj): HResult; virtual; stdcall;
- function NonDelegatingAddRef: Integer; virtual; stdcall;
- function NonDelegatingRelease: Integer; virtual; stdcall;
- function GetOwner: IUnKnown;
- end;
- TBCUnknownClass = Class of TBCUnknown;
- TFormPropertyPage = class;
- TFormPropertyPageClass = class of TFormPropertyPage;
- TBCBaseFilter = class;
- TBCBaseFilterClass = class of TBCBaseFilter;
- TBCClassFactory = class(TObject, IUnKnown, IClassFactory)
- private
- FNext : TBCClassFactory;
- FComClass : TBCUnknownClass;
- FPropClass: TFormPropertyPageClass;
- FName : String;
- FClassID : TGUID;
- FCategory : TGUID;
- FMerit : LongWord;
- FPinCount : Cardinal;
- FPins : PRegFilterPins;
- function RegisterFilter(FilterMapper: IFilterMapper; Register: Boolean): boolean; overload;
- function RegisterFilter(FilterMapper: IFilterMapper2; Register: Boolean): boolean; overload;
- procedure UpdateRegistry(Register: Boolean); overload;
- protected
- function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
- function _AddRef: Integer; stdcall;
- function _Release: Integer; stdcall;
- function CreateInstance(const UnkOuter: IUnknown; const IID: TGUID;
- out Obj): HResult; stdcall;
- function LockServer(fLock: BOOL): HResult; stdcall;
- public
- constructor CreateFilter(ComClass: TBCUnknownClass; Name: string;
- const ClassID: TGUID; const Category: TGUID; Merit: LongWord;
- PinCount: Cardinal; Pins: PRegFilterPins);
- constructor CreatePropertyPage(ComClass: TFormPropertyPageClass; const ClassID: TGUID);
- property Name: String read FName;
- property ClassID: TGUID read FClassID;
- end;
- TBCFilterTemplate = class
- private
- FFactoryList : TBCClassFactory;
- procedure AddObjectFactory(Factory: TBCClassFactory);
- public
- constructor Create;
- destructor Destroy; override;
- function RegisterServer(Register: Boolean): boolean;
- function GetFactoryFromClassID(const CLSID: TGUID): TBCClassFactory;
- end;
- TBCMediaType = object
- MediaType: PAMMediaType;
- function Equal(mt: TBCMediaType): boolean; overload;
- function Equal(mt: PAMMediaType): boolean; overload;
- function MatchesPartial(Partial: PAMMediaType): boolean;
- function IsPartiallySpecified: boolean;
- function IsValid: boolean;
- procedure InitMediaType;
- function FormatLength: Cardinal;
- end;
- TBCBasePin = class;
- TBCBaseFilter = class(TBCUnknown, IBaseFilter, IAMovieSetup)
- protected
- FState : TFilterState; // current state: running, paused
- FClock : IReferenceClock; // this graph's ref clock
- FStart : TReferenceTime; // offset from stream time to reference time
- FCLSID : TGUID; // This filters clsid used for serialization
- FLock : TBCCritSec; // Object we use for locking
- FFilterName : WideString; // Full filter name
- FGraph : IFilterGraph; // Graph we belong to
- FSink : IMediaEventSink; // Called with notify events
- FPinVersion: Integer; // Current pin version
- public
- constructor Create(Name: string; // Object description
- Unk : IUnKnown; // IUnknown of delegating object
- Lock: TBCCritSec; // Object who maintains lock
- const clsid: TGUID // The clsid to be used to serialize this filter
- ); overload;
- constructor Create(Name: string; // Object description
- Unk : IUnKnown; // IUnknown of delegating object
- Lock: TBCCritSec; // Object who maintains lock
- const clsid: TGUID; // The clsid to be used to serialize this filter
- out hr: HRESULT // General OLE return code
- ); overload;
- constructor CreateFromFactory(Factory: TBCClassFactory; const Controller: IUnknown); override;
- destructor destroy; override;
- // --- IPersist method ---
- function GetClassID(out classID: TCLSID): HResult; stdcall;
- // --- IMediaFilter methods ---
- // override Stop and Pause so we can activate the pins.
- // Note that Run will call Pause first if activation needed.
- // Override these if you want to activate your filter rather than
- // your pins.
- function Stop: HRESULT; virtual; stdcall;
- function Pause: HRESULT; virtual; stdcall;
- // the start parameter is the difference to be added to the
- // sample's stream time to get the reference time for
- // its presentation
- function Run(tStart: TReferenceTime): HRESULT; virtual; stdcall;
- function GetState(dwMilliSecsTimeout: DWORD; out State: TFilterState): HRESULT; virtual; stdcall;
- function SetSyncSource(pClock: IReferenceClock): HRESULT; stdcall;
- function GetSyncSource(out pClock: IReferenceClock): HRESULT; stdcall;
- // --- helper methods ---
- // return the current stream time - ie find out what
- // stream time should be appearing now
- function StreamTime(out rtStream: TReferenceTime): HRESULT; virtual;
- // Is the filter currently active?
- function IsActive: boolean;
- // Is this filter stopped (without locking)
- function IsStopped: boolean;
- // --- IBaseFilter methods ---
- // pin enumerator
- function EnumPins(out ppEnum: IEnumPins): HRESULT; stdcall;
- // default behaviour of FindPin assumes pin ids are their names
- function FindPin(Id: PWideChar; out Pin: IPin): HRESULT; virtual; stdcall;
- function QueryFilterInfo(out pInfo: TFilterInfo): HRESULT; stdcall;
- // milenko start (added virtual to be able to override it in the renderers)
- function JoinFilterGraph(pGraph: IFilterGraph; pName: PWideChar): HRESULT; virtual; stdcall;
- // milenko end
- // return a Vendor information string. Optional - may return E_NOTIMPL.
- // memory returned should be freed using CoTaskMemFree
- // default implementation returns E_NOTIMPL
- function QueryVendorInfo(out pVendorInfo: PWideChar): HRESULT; stdcall;
- // --- helper methods ---
- // send an event notification to the filter graph if we know about it.
- // returns S_OK if delivered, S_FALSE if the filter graph does not sink
- // events, or an error otherwise.
- function NotifyEvent(EventCode, EventParam1, EventParam2: LongInt): HRESULT;
- // return the filter graph we belong to
- function GetFilterGraph: IFilterGraph;
- // Request reconnect
- // pPin is the pin to reconnect
- // pmt is the type to reconnect with - can be NULL
- // Calls ReconnectEx on the filter graph
- function ReconnectPin(Pin: IPin; pmt: PAMMediaType): HRESULT;
- // find out the current pin version (used by enumerators)
- function GetPinVersion: LongInt; virtual;
- procedure IncrementPinVersion;
- // you need to supply these to access the pins from the enumerator
- // and for default Stop and Pause/Run activation.
- function GetPinCount: integer; virtual; abstract;
- function GetPin(n: Integer): TBCBasePin; virtual; abstract;
- // --- IAMovieSetup methods ---
- {nev: start 04/16/04 added "virtual"}
- function Register: HRESULT; virtual; stdcall;
- function Unregister: HRESULT; virtual; stdcall;
- {nev: end}
- property State: TFilterState read FState;
- property GRaph : IFilterGraph read FGRaph;
- end;
- { NOTE The implementation of this class calls the CUnknown constructor with
- a NULL outer unknown pointer. This has the effect of making us a self
- contained class, ie any QueryInterface, AddRef or Release calls will be
- routed to the class's NonDelegatingUnknown methods. You will typically
- find that the classes that do this then override one or more of these
- virtual functions to provide more specialised behaviour. A good example
- of this is where a class wants to keep the QueryInterface internal but
- still wants its lifetime controlled by the external object }
- TBCBasePin = class(TBCUnknown, IPin, IQualityControl)
- protected
- FPinName: WideString;
- FConnected : IPin; // Pin we have connected to
- Fdir : TPinDirection; // Direction of this pin
- FLock : TBCCritSec; // Object we use for locking
- FRunTimeError : boolean; // Run time error generated
- FCanReconnectWhenActive: boolean; // OK to reconnect when active
- FTryMyTypesFirst : boolean; // When connecting enumerate
- // this pin's types first
- FFilter : TBCBaseFilter; // Filter we were created by
- FQSink : IQualityControl; // Target for Quality messages
- FTypeVersion : LongInt; // Holds current type version
- Fmt : TAMMediaType; // Media type of connection
- FStart : TReferenceTime; // time from NewSegment call
- FStop : TReferenceTime; // time from NewSegment
- FRate : double; // rate from NewSegment
- FRef : LongInt;
- function GetCurrentMediaType: TBCMediaType;
- function GetAMMediaType: PAMMediaType;
- protected
- procedure DisplayPinInfo(ReceivePin: IPin);
- procedure DisplayTypeInfo(Pin: IPin; pmt: PAMMediaType);
- // used to agree a media type for a pin connection
- // given a specific media type, attempt a connection (includes
- // checking that the type is acceptable to this pin)
- function AttemptConnection(
- ReceivePin: IPin; // connect to this pin
- pmt : PAMMediaType // using this type
- ): HRESULT;
- // try all the media types in this enumerator - for each that
- // we accept, try to connect using ReceiveConnection.
- function TryMediaTypes(
- ReceivePin: IPin; // connect to this pin
- pmt : PAMMediaType; // proposed type from Connect
- Enum : IEnumMediaTypes // try this enumerator
- ): HRESULT;
- // establish a connection with a suitable mediatype. Needs to
- // propose a media type if the pmt pointer is null or partially
- // specified - use TryMediaTypes on both our and then the other pin's
- // enumerator until we find one that works.
- function AgreeMediaType(
- ReceivePin: IPin; // connect to this pin
- pmt : PAMMediaType // proposed type from Connect
- ): HRESULT;
- function DisconnectInternal: HRESULT; stdcall;
- public
- function NonDelegatingAddRef: Integer; override; stdcall;
- function NonDelegatingRelease: Integer; override; stdcall;
- constructor Create(
- ObjectName: string; // Object description
- Filter : TBCBaseFilter; // Owning filter who knows about pins
- Lock : TBCCritSec; // Object who implements the lock
- out hr : HRESULT; // General OLE return code
- Name : WideString; // Pin name for us
- dir : TPinDirection); // Either PINDIR_INPUT or PINDIR_OUTPUT
- destructor destroy; override;
- // --- IPin methods ---
- // take lead role in establishing a connection. Media type pointer
- // may be null, or may point to partially-specified mediatype
- // (subtype or format type may be GUID_NULL).
- function Connect(pReceivePin: IPin; const pmt: PAMMediaType): HRESULT; virtual; stdcall;
- // (passive) accept a connection from another pin
- function ReceiveConnection(pConnector: IPin; const pmt: TAMMediaType): HRESULT; virtual; stdcall;
- function Disconnect: HRESULT; virtual; stdcall;
- function ConnectedTo(out pPin: IPin): HRESULT; virtual; stdcall;
- function ConnectionMediaType(out pmt: TAMMediaType): HRESULT; virtual; stdcall;
- function QueryPinInfo(out pInfo: TPinInfo): HRESULT; virtual; stdcall;
- function QueryDirection(out pPinDir: TPinDirection): HRESULT; stdcall;
- function QueryId(out Id: PWideChar): HRESULT; virtual; stdcall;
- // does the pin support this media type
- function QueryAccept(const pmt: TAMMediaType): HRESULT; virtual; stdcall;
- // return an enumerator for this pins preferred media types
- function EnumMediaTypes(out ppEnum: IEnumMediaTypes): HRESULT; virtual; stdcall;
- // return an array of IPin* - the pins that this pin internally connects to
- // All pins put in the array must be AddReffed (but no others)
- // Errors: "Can't say" - FAIL, not enough slots - return S_FALSE
- // Default: return E_NOTIMPL
- // The filter graph will interpret NOT_IMPL as any input pin connects to
- // all visible output pins and vice versa.
- // apPin can be NULL if nPin==0 (not otherwise).
- function QueryInternalConnections(out apPin: IPin; var nPin: ULONG): HRESULT; virtual; stdcall;
- // Called when no more data will be sent
- function EndOfStream: HRESULT; virtual; stdcall;
- function BeginFlush: HRESULT; virtual; stdcall; abstract;
- function EndFlush: HRESULT; virtual; stdcall; abstract;
- // Begin/EndFlush still PURE
- // NewSegment notifies of the start/stop/rate applying to the data
- // about to be received. Default implementation records data and
- // returns S_OK.
- // Override this to pass downstream.
- function NewSegment(tStart, tStop: TReferenceTime; dRate: double): HRESULT; virtual; stdcall;
- // --- IQualityControl methods ---
- function Notify(pSelf: IBaseFilter; q: TQuality): HRESULT; virtual; stdcall;
- function SetSink(piqc: IQualityControl): HRESULT; virtual; stdcall;
- // --- helper methods ---
- // Returns True if the pin is connected. false otherwise.
- function IsConnected: boolean;
- // Return the pin this is connected to (if any)
- property GetConnected: IPin read FConnected;
- // Check if our filter is currently stopped
- function IsStopped: boolean;
- // find out the current type version (used by enumerators)
- function GetMediaTypeVersion: longint; virtual;
- procedure IncrementTypeVersion;
- // switch the pin to active (paused or running) mode
- // not an error to call this if already active
- function Active: HRESULT; virtual;
- // switch the pin to inactive state - may already be inactive
- function Inactive: HRESULT; virtual;
- // Notify of Run() from filter
- function Run(Start: TReferenceTime): HRESULT; virtual;
- // check if the pin can support this specific proposed type and format
- function CheckMediaType(mt: PAMMediaType): HRESULT; virtual; abstract;
- // set the connection to use this format (previously agreed)
- function SetMediaType(mt: PAMMediaType): HRESULT; virtual;
- // check that the connection is ok before verifying it
- // can be overridden eg to check what interfaces will be supported.
- function CheckConnect(Pin: IPin): HRESULT; virtual;
- // Set and release resources required for a connection
- function BreakConnect: HRESULT; virtual;
- function CompleteConnect(ReceivePin: IPin): HRESULT; virtual;
- // returns the preferred formats for a pin
- function GetMediaType(Position: integer; out MediaType: PAMMediaType): HRESULT; virtual;
- // access to NewSegment values
- property CurrentStopTime: TReferenceTime read FStop;
- property CurrentStartTime: TReferenceTime read FStart;
- property CurrentRate: double read FRate;
- // Access name
- property Name: WideString read FPinName;
- property CanReconnectWhenActive: boolean read FCanReconnectWhenActive write FCanReconnectWhenActive;
- // Media type
- property CurrentMediaType: TBCMediaType read GetCurrentMediaType;
- property AMMediaType: PAMMediaType read GetAMMediaType;
- end;
- TBCEnumPins = class(TInterfacedObject, IEnumPins)
- private
- FPosition: integer; // Current ordinal position
- FPinCount: integer; // Number of pins available
- FFilter: TBCBaseFilter; // The filter who owns us
- FVersion: LongInt; // Pin version information
- // These pointers have not been AddRef'ed and
- // so they should not be dereferenced. They are
- // merely kept to ID which pins have been enumerated.
- FPinCache: TList;
- { If while we are retrieving a pin for example from the filter an error
- occurs we assume that our internal state is stale with respect to the
- filter (someone may have deleted all the pins). We can check before
- starting whether or not the operation is likely to fail by asking the
- filter what it's current version number is. If the filter has not
- overriden the GetPinVersion method then this will always match }
- function AreWeOutOfSync: boolean;
- (* This method performs the same operations as Reset, except is does not clear
- the cache of pins already enumerated. *)
- function Refresh: HRESULT; stdcall;
- public
- constructor Create(Filter: TBCBaseFilter; EnumPins: TBCEnumPins);
- destructor Destroy; override;
- function Next(cPins: ULONG; // place this many pins...
- out ppPins: IPin; // ...in this array of IPin*
- pcFetched: PULONG // actual count passed returned here
- ): HRESULT; stdcall;
- function Skip(cPins: ULONG): HRESULT; stdcall;
- function Reset: HRESULT; stdcall;
- function Clone(out ppEnum: IEnumPins): HRESULT; stdcall;
- end;
- TBCEnumMediaTypes = class(TInterfacedObject, IEnumMediaTypes)
- private
- FPosition: Cardinal; // Current ordinal position
- FPin : TBCBasePin; // The pin who owns us
- FVersion : LongInt; // Media type version value
- function AreWeOutOfSync: boolean;
- public
- constructor Create(Pin: TBCBasePin; EnumMediaTypes: TBCEnumMediaTypes);
- destructor Destroy; override;
- function Next(cMediaTypes: ULONG; out ppMediaTypes: PAMMediaType;
- pcFetched: PULONG): HRESULT; stdcall;
- function Skip(cMediaTypes: ULONG): HRESULT; stdcall;
- function Reset: HRESULT; stdcall;
- function Clone(out ppEnum: IEnumMediaTypes): HRESULT; stdcall;
- end;
- TBCBaseOutputPin = class(TBCBasePin)
- protected
- FAllocator: IMemAllocator;
- // interface on the downstreaminput pin, set up in CheckConnect when we connect.
- FInputPin : IMemInputPin;
- public
- constructor Create(ObjectName: string; Filter: TBCBaseFilter; Lock: TBCCritSec;
- out hr: HRESULT; const Name: WideString);
- // override CompleteConnect() so we can negotiate an allocator
- function CompleteConnect(ReceivePin: IPin): HRESULT; override;
- // negotiate the allocator and its buffer size/count and other properties
- // Calls DecideBufferSize to set properties
- function DecideAllocator(Pin: IMemInputPin; out Alloc: IMemAllocator): HRESULT; virtual;
- // override this to set the buffer size and count. Return an error
- // if the size/count is not to your liking.
- // The allocator properties passed in are those requested by the
- // input pin - use eg the alignment and prefix members if you have
- // no preference on these.
- function DecideBufferSize(Alloc: IMemAllocator; propInputRequest: PAllocatorProperties): HRESULT; virtual;
- // returns an empty sample buffer from the allocator
- function GetDeliveryBuffer(out Sample: IMediaSample; StartTime: PReferenceTime;
- EndTime: PReferenceTime; Flags: Longword): HRESULT; virtual;
- // deliver a filled-in sample to the connected input pin
- // note - you need to release it after calling this. The receiving
- // pin will addref the sample if it needs to hold it beyond the
- // call.
- function Deliver(Sample: IMediaSample): HRESULT; virtual;
- // override this to control the connection
- function InitAllocator(out Alloc: IMemAllocator): HRESULT; virtual;
- function CheckConnect(Pin: IPin): HRESULT; override;
- function BreakConnect: HRESULT; override;
- // override to call Commit and Decommit
- function Active: HRESULT; override;
- function Inactive: HRESULT; override;
- // we have a default handling of EndOfStream which is to return
- // an error, since this should be called on input pins only
- function EndOfStream: HRESULT; override; stdcall;
- // called from elsewhere in our filter to pass EOS downstream to
- // our connected input pin
- function DeliverEndOfStream: HRESULT; virtual;
- // same for Begin/EndFlush - we handle Begin/EndFlush since it
- // is an error on an output pin, and we have Deliver methods to
- // call the methods on the connected pin
- function BeginFlush: HRESULT; override; stdcall;
- function EndFlush: HRESULT; override; stdcall;
- function DeliverBeginFlush: HRESULT; virtual;
- function DeliverEndFlush: HRESULT; virtual;
- // deliver NewSegment to connected pin - you will need to
- // override this if you queue any data in your output pin.
- function DeliverNewSegment(Start, Stop: TReferenceTime; Rate: double): HRESULT; virtual;
- end;
- TBCBaseInputPin = class(TBCBasePin, IMemInputPin)
- protected
- FAllocator: IMemAllocator; // Default memory allocator
- // allocator is read-only, so received samples
- // cannot be modified (probably only relevant to in-place
- // transforms
- FReadOnly: boolean;
- //private: this should really be private... only the MPEG code
- // currently looks at it directly and it should use IsFlushing().
- // in flushing state (between BeginFlush and EndFlush)
- // if True, all Receives are returned with S_FALSE
- FFlushing: boolean;
- // Sample properties - initalized in Receive
- FSampleProps: TAMSample2Properties;
- public
- constructor Create(ObjectName: string; Filter: TBCBaseFilter;
- Lock: TBCCritSec; out hr: HRESULT; Name: WideString);
- destructor Destroy; override;
- // ----------IMemInputPin--------------
- // return the allocator interface that this input pin
- // would like the output pin to use
- function GetAllocator(out ppAllocator: IMemAllocator): HRESULT; stdcall;
- // tell the input pin which allocator the output pin is actually
- // going to use.
- function NotifyAllocator(pAllocator: IMemAllocator; bReadOnly: BOOL): HRESULT; stdcall;
- // this method is optional (can return E_NOTIMPL).
- // default implementation returns E_NOTIMPL. Override if you have
- // specific alignment or prefix needs, but could use an upstream
- // allocator
- function GetAllocatorRequirements(out pProps: TAllocatorProperties): HRESULT; stdcall;
- // do something with this media sample
- function Receive(pSample: IMediaSample): HRESULT; virtual; stdcall;
- // do something with these media samples
- function ReceiveMultiple(var pSamples: IMediaSample; nSamples: Longint;
- out nSamplesProcessed: Longint): HRESULT; stdcall;
- // See if Receive() blocks
- function ReceiveCanBlock: HRESULT; stdcall;
- //-----------Helper-------------
- // Default handling for BeginFlush - call at the beginning
- // of your implementation (makes sure that all Receive calls
- // fail). After calling this, you need to free any queued data
- // and then call downstream.
- function BeginFlush: HRESULT; override; stdcall;
- // default handling for EndFlush - call at end of your implementation
- // - before calling this, ensure that there is no queued data and no thread
- // pushing any more without a further receive, then call downstream,
- // then call this method to clear the m_bFlushing flag and re-enable
- // receives
- function EndFlush: HRESULT; override; stdcall;
- // Release the pin's allocator.
- function BreakConnect: HRESULT; override;
- // helper method to check the read-only flag
- property IsReadOnly: boolean read FReadOnly;
- // helper method to see if we are flushing
- property IsFlushing: boolean read FFlushing;
- // Override this for checking whether it's OK to process samples
- // Also call this from EndOfStream.
- function CheckStreaming: HRESULT; virtual;
- // Pass a Quality notification on to the appropriate sink
- function PassNotify(const q: TQuality): HRESULT;
- //================================================================================
- // IQualityControl methods (from CBasePin)
- //================================================================================
- function Notify(pSelf: IBaseFilter; q: TQuality): HRESULT; override; stdcall;
- // no need to override:
- // STDMETHODIMP SetSink(IQualityControl * piqc);
- // switch the pin to inactive state - may already be inactive
- function Inactive: HRESULT; override;
- // Return sample properties pointer
- function SampleProps: PAMSample2Properties;
- end;
- // milenko start (added TBCDynamicOutputPin conversion)
- TBLOCK_STATE = (NOT_BLOCKED, PENDING, BLOCKED);
- TBCDynamicOutputPin = class(TBCBaseOutputPin, IPinFlowControl)
- public
- constructor Create(ObjectName: WideString; Filter: TBCBaseFilter;
- Lock: TBCCritSec; out hr: HRESULT; Name: WideString);
- destructor Destroy; override;
- // IUnknown Methods
- function NonDelegatingQueryInterface(const IID: TGUID; out Obj): HResult; override;
- // IPin Methods
- function Disconnect: HRESULT; override; stdcall;
- // IPinFlowControl Methods
- function Block(dwBlockFlags: DWORD; hEvent: THandle): HResult; stdcall;
- // Set graph config info
- procedure SetConfigInfo(GraphConfig: IGraphConfig; StopEvent: THandle);
- {$IFDEF DEBUG}
- function Deliver(Sample: IMediaSample): HRESULT; override;
- function DeliverEndOfStream: HRESULT; override;
- function DeliverNewSegment(Start, Stop: TReferenceTime; Rate: double): HRESULT; override;
- {$ENDIF} // DEBUG
- function DeliverBeginFlush: HRESULT; override;
- function DeliverEndFlush: HRESULT; override;
- function Active: HRESULT; override;
- function Inactive: HRESULT; override;
- function CompleteConnect(ReceivePin: IPin): HRESULT; override;
- function StartUsingOutputPin: HRESULT; virtual;
- procedure StopUsingOutputPin; virtual;
- function StreamingThreadUsingOutputPin: Boolean; virtual;
- function ChangeOutputFormat(const pmt: PAMMediaType; tSegmentStart, tSegmentStop:
- TreferenceTime; dSegmentRate: Double): HRESULT;
- function ChangeMediaType(const pmt: PAMMEdiaType): HRESULT;
- function DynamicReconnect(const pmt: PAMMediaType): HRESULT;
- protected
- // This lock should be held when the following class members are
- // being used: m_hNotifyCallerPinBlockedEvent, m_BlockState,
- // m_dwBlockCallerThreadID and m_dwNumOutstandingOutputPinUsers.
- FBlockStateLock: TBCCritSec;
- // This event should be signaled when the output pin is
- // not blocked. This is a manual reset event. For more
- // information on events, see the documentation for
- // CreateEvent() in the Windows SDK.
- FUnblockOutputPinEvent: THandle;
- // This event will be signaled when block operation succeedes or
- // when the user cancels the block operation. The block operation
- // can be canceled by calling IPinFlowControl2::Block( 0, NULL )
- // while the block operation is pending.
- FNotifyCallerPinBlockedEvent: THandle;
- // The state of the current block operation.
- FBlockState: TBLOCK_STATE;
- // The ID of the thread which last called IPinFlowControl::Block().
- // For more information on thread IDs, see the documentation for
- // GetCurrentThreadID() in the Windows SDK.
- FBlockCallerThreadID: DWORD;
- // The number of times StartUsingOutputPin() has been sucessfully
- // called and a corresponding call to StopUsingOutputPin() has not
- // been made. When this variable is greater than 0, the streaming
- // thread is calling IPin::NewSegment(), IPin::EndOfStream(),
- // IMemInputPin::Receive() or IMemInputPin::ReceiveMultiple(). The
- // streaming thread could also be calling: DynamicReconnect(),
- // ChangeMediaType() or ChangeOutputFormat(). The output pin cannot
- // be blocked while the output pin is being used.
- FNumOutstandingOutputPinUsers: DWORD;
- // This event should be set when the IMediaFilter::Stop() is called.
- // This is a manual reset event. It is also set when the output pin
- // delivers a flush to the connected input pin.
- FStopEvent: THandle;
- FGraphConfig: IGraphConfig;
- // TRUE if the output pin's allocator's samples are read only.
- // Otherwise FALSE. For more information, see the documentation
- // for IMemInputPin::NotifyAllocator().
- FPinUsesReadOnlyAllocator: Boolean;
- function SynchronousBlockOutputPin: HRESULT;
- function AsynchronousBlockOutputPin(NotifyCallerPinBlockedEvent: THandle): HRESULT;
- function UnblockOutputPin: HRESULT;
- procedure BlockOutputPin;
- procedure ResetBlockState;
- class function WaitEvent(Event: THandle): HRESULT;
- private
- function Initialize: HRESULT;
- function ChangeMediaTypeHelper(const pmt: PAMMediaType): HRESULT;
- {$IFDEF DEBUG}
- procedure AssertValid;
- {$ENDIF} // DEBUG
- end;
- // milenko end
- TBCTransformOutputPin = class;
- TBCTransformInputPin = class;
- TBCTransformFilter = class(TBCBaseFilter)
- protected
- FEOSDelivered : boolean; // have we sent EndOfStream
- FSampleSkipped : boolean; // Did we just skip a frame
- FQualityChanged: boolean; // Have we degraded?
- // critical section protecting filter state.
- FcsFilter: TBCCritSec;
- // critical section stopping state changes (ie Stop) while we're
- // processing a sample.
- //
- // This critical section is held when processing
- // events that occur on the receive thread - Receive() and EndOfStream().
- //
- // If you want to hold both m_csReceive and m_csFilter then grab
- // m_csFilter FIRST - like CTransformFilter::Stop() does.
- FcsReceive: TBCCritSec;
- // these hold our input and output pins
- FInput : TBCTransformInputPin;
- FOutput: TBCTransformOutputPin;
- public
- // map getpin/getpincount for base enum of pins to owner
- // override this to return more specialised pin objects
- function GetPinCount: integer; override;
- function GetPin(n: integer): TBCBasePin; override;
- function FindPin(Id: PWideChar; out ppPin: IPin): HRESULT; override; stdcall;
- // override state changes to allow derived transform filter
- // to control streaming start/stop
- function Stop: HRESULT; override; stdcall;
- function Pause: HRESULT; override; stdcall;
- constructor Create(ObjectName: string; unk: IUnKnown; const clsid: TGUID);
- constructor CreateFromFactory(Factory: TBCClassFactory; const Controller: IUnknown); override;
- destructor destroy; override;
- // =================================================================
- // ----- override these bits ---------------------------------------
- // =================================================================
- // These must be supplied in a derived class
- function Transform(msIn, msout: IMediaSample): HRESULT; virtual;
- // check if you can support mtIn
- function CheckInputType(mtIn: PAMMediaType): HRESULT; virtual; abstract;
- // check if you can support the transform from this input to this output
- function CheckTransform(mtIn, mtOut: PAMMediaType): HRESULT; virtual; abstract;
- // this goes in the factory template table to create new instances
- // static CCOMObject * CreateInstance(LPUNKNOWN, HRESULT *);
- // call the SetProperties function with appropriate arguments
- function DecideBufferSize(Allocator: IMemAllocator; prop: PAllocatorProperties): HRESULT; virtual; abstract;
- // override to suggest OUTPUT pin media types
- function GetMediaType(Position: integer; out MediaType: PAMMediaType): HRESULT; virtual; abstract;
- // =================================================================
- // ----- Optional Override Methods -----------------------
- // =================================================================
- // you can also override these if you want to know about streaming
- function StartStreaming: HRESULT; virtual;
- function StopStreaming: HRESULT; virtual;
- // override if you can do anything constructive with quality notifications
- function AlterQuality(const q: TQuality): HRESULT; virtual;
- // override this to know when the media type is actually set
- function SetMediaType(direction: TPinDirection; pmt: PAMMediaType): HRESULT; virtual;
- // chance to grab extra interfaces on connection
- function CheckConnect(dir: TPinDirection; Pin: IPin): HRESULT; virtual;
- function BreakConnect(dir: TPinDirection): HRESULT; virtual;
- function CompleteConnect(direction: TPinDirection; ReceivePin: IPin): HRESULT; virtual;
- // chance to customize the transform process
- function Receive(Sample: IMediaSample): HRESULT; virtual;
- // Standard setup for output sample
- function InitializeOutputSample(Sample: IMediaSample; out OutSample: IMediaSample): HRESULT; virtual;
- // if you override Receive, you may need to override these three too
- function EndOfStream: HRESULT; virtual;
- function BeginFlush: HRESULT; virtual;
- function EndFlush: HRESULT; virtual;
- function NewSegment(Start, Stop: TReferenceTime; Rate: double): HRESULT; virtual;
- property Input: TBCTransformInputPin read FInput write FInput;
- property Output: TBCTransformOutputPin read FOutPut write FOutput;
- end;
- TBCTransformInputPin = class(TBCBaseInputPin)
- private
- FTransformFilter: TBCTransformFilter;
- public
- constructor Create(ObjectName: string; TransformFilter: TBCTransformFilter;
- out hr: HRESULT; Name: WideString);
- destructor destroy; override;
- function QueryId(out id: PWideChar): HRESULT; override; stdcall;
- // Grab and release extra interfaces if required
- function CheckConnect(Pin: IPin): HRESULT; override;
- function BreakConnect: HRESULT; override;
- function CompleteConnect(ReceivePin: IPin): HRESULT; override;
- // check that we can support this output type
- function CheckMediaType(mtIn: PAMMediaType): HRESULT; override;
- // set the connection media type
- function SetMediaType(mt: PAMMediaType): HRESULT; override;
- // --- IMemInputPin -----
- // here's the next block of data from the stream.
- // AddRef it yourself if you need to hold it beyond the end
- // of this call.
- function Receive(pSample: IMediaSample): HRESULT; override; stdcall;
- // provide EndOfStream that passes straight downstream
- // (there is no queued data)
- function EndOfStream: HRESULT; override; stdcall;
- // passes it to CTransformFilter::BeginFlush
- function BeginFlush: HRESULT; override; stdcall;
- // passes it to CTransformFilter::EndFlush
- function EndFlush: HRESULT; override; stdcall;
- function NewSegment(Start, Stop: TReferenceTime; Rate: double): HRESULT; override; stdcall;
- // Check if it's OK to process samples
- function CheckStreaming: HRESULT; override;
- end;
- TBCTransformOutputPin = class(TBCBaseOutputPin)
- protected
- FTransformFilter: TBCTransformFilter;
- // implement IMediaPosition by passing upstream
- FPosition: IUnknown;
- public
- constructor Create(ObjectName: string; TransformFilter: TBCTransformFilter;
- out hr: HRESULT; Name: WideString);
- destructor destroy; override;
- // override to expose IMediaPosition
- function NonDelegatingQueryInterface(const IID: TGUID; out Obj): HResult; override;
- // --- TBCBaseOutputPin ------------
- function QueryId(out Id: PWideChar): HRESULT; override; stdcall;
- // Grab and release extra interfaces if required
- function CheckConnect(Pin: IPin): HRESULT; override;
- function BreakConnect: HRESULT; override;
- function CompleteConnect(ReceivePin: IPin): HRESULT; override;
- // check that we can support this output type
- function CheckMediaType(mtOut: PAMMediaType): HRESULT; override;
- // set the connection media type
- function SetMediaType(pmt: PAMMediaType): HRESULT; override;
- // called from CBaseOutputPin during connection to ask for
- // the count and size of buffers we need.
- function DecideBufferSize(Alloc: IMemAllocator; Prop: PAllocatorProperties): HRESULT; override;
- // returns the preferred formats for a pin
- function GetMediaType(Position: integer; out MediaType: PAMMediaType): HRESULT; override;
- // inherited from IQualityControl via CBasePin
- function Notify(Sendr: IBaseFilter; q: TQuality): HRESULT; override; stdcall;
- end;
- // milenko start (added TBCVideoTransformFilter conversion)
- TBCVideoTransformFilter = class(TBCTransformFilter)
- public
- constructor Create(Name: WideString; Unk: IUnknown; clsid: TGUID);
- destructor Destroy; override;
- function EndFlush: HRESULT; override;
- // =================================================================
- // ----- override these bits ---------------------------------------
- // =================================================================
- // The following methods are in CTransformFilter which is inherited.
- // They are mentioned here for completeness
- //
- // These MUST be supplied in a derived class
- //
- // NOTE:
- // virtual HRESULT Transform(IMediaSample * pIn, IMediaSample *pOut);
- // virtual HRESULT CheckInputType(const CMediaType* mtIn) PURE;
- // virtual HRESULT CheckTransform
- // (const CMediaType* mtIn, const CMediaType* mtOut) PURE;
- // static CCOMObject * CreateInstance(LPUNKNOWN, HRESULT *);
- // virtual HRESULT DecideBufferSize
- // (IMemAllocator * pAllocator, ALLOCATOR_PROPERTIES *pprop) PURE;
- // virtual HRESULT GetMediaType(int iPosition, CMediaType *pMediaType) PURE;
- //
- // These MAY also be overridden
- //
- // virtual HRESULT StopStreaming();
- // virtual HRESULT SetMediaType(PIN_DIRECTION direction,const CMediaType *pmt);
- // virtual HRESULT CheckConnect(PIN_DIRECTION dir,IPin *pPin);
- // virtual HRESULT BreakConnect(PIN_DIRECTION dir);
- // virtual HRESULT CompleteConnect(PIN_DIRECTION direction,IPin *pReceivePin);
- // virtual HRESULT EndOfStream(void);
- // virtual HRESULT BeginFlush(void);
- // virtual HRESULT EndFlush(void);
- // virtual HRESULT NewSegment
- // (REFERENCE_TIME tStart,REFERENCE_TIME tStop,double dRate);
- {$IFDEF PERF}
- // If you override this - ensure that you register all these ids
- // as well as any of your own,
- procedure RegisterPerfId; virtual;
- {$ENDIF}
- protected
- // =========== QUALITY MANAGEMENT IMPLEMENTATION ========================
- // Frames are assumed to come in three types:
- // Type 1: an AVI key frame or an MPEG I frame.
- // This frame can be decoded with no history.
- // Dropping this frame means that no further frame can be decoded
- // until the next type 1 frame.
- // Type 1 frames are sync points.
- // Type 2: an AVI non-key frame or an MPEG P frame.
- // This frame cannot be decoded unless the previous type 1 frame was
- // decoded and all type 2 frames since have been decoded.
- // Dropping this frame means that no further frame can be decoded
- // until the next type 1 frame.
- // Type 3: An MPEG B frame.
- // This frame cannot be decoded unless the previous type 1 or 2 frame
- // has been decoded AND the subsequent type 1 or 2 frame has also
- // been decoded. (This requires decoding the frames out of sequence).
- // Dropping this frame affects no other frames. This implementation
- // does not allow for these. All non-sync-point frames are treated
- // as being type 2.
- //
- // The spacing of frames of type 1 in a file is not guaranteed. There MUST
- // be a type 1 frame at (well, near) the start of the file in order to start
- // decoding at all. After that there could be one every half second or so,
- // there could be one at the start of each scene (aka "cut", "shot") or
- // there could be no more at all.
- // If there is only a single type 1 frame then NO FRAMES CAN BE DROPPED
- // without losing all the rest of the movie. There is no way to tell whether
- // this is the case, so we find that we are in the gambling business.
- // To try to improve the odds, we record the greatest interval between type 1s
- // that we have seen and we bet on things being no worse than this in the
- // future.
- // You can tell if it's a type 1 frame by calling IsSyncPoint().
- // there is no architected way to test for a type 3, so you should override
- // the quality management here if you have B-frames.
- FKeyFramePeriod: integer; // the largest observed interval between type 1 frames
- // 1 means every frame is type 1, 2 means every other.
- FFramesSinceKeyFrame: integer; // Used to count frames since the last type 1.
- // becomes the new m_nKeyFramePeriod if greater.
- FSkipping: Boolean; // we are skipping to the next type 1 frame
- {$IFDEF PERF}
- FidFrameType: integer; // MSR id Frame type. 1=Key, 2="non-key"
- FidSkip: integer; // MSR id skipping
- FidLate: integer; // MSR id lateness
- FidTimeTillKey: integer; // MSR id for guessed time till next key frame.
- {$ENDIF}
- FitrLate: integer; // lateness from last Quality message
- // (this overflows at 214 secs late).
- FtDecodeStart: integer; // timeGetTime when decode started.
- FitrAvgDecode: integer; // Average decode time in reference units.
- FNoSkip: Boolean; // debug - no skipping.
- // We send an EC_QUALITY_CHANGE notification to the app if we have to degrade.
- // We send one when we start degrading, not one for every frame, this means
- // we track whether we've sent one yet.
- FQualityChanged: Boolean;
- // When non-zero, don't pass anything to renderer until next keyframe
- // If there are few keys, give up and eventually draw something
- FWaitForKey: integer;
- function AbortPlayback(hr: HRESULT): HRESULT; // if something bad happens
- function ShouldSkipFrame(pIn: IMediaSample): Boolean;
- public
- function StartStreaming: HRESULT; override;
- function Receive(Sample: IMediaSample): HRESULT; override;
- function AlterQuality(const q: TQuality): HRESULT; override;
- end;
- // milenko end
- TBCTransInPlaceOutputPin = class;
- TBCTransInPlaceInputPin = class;
- TBCTransInPlaceFilter = class(TBCTransformFilter)
- public
- // map getpin/getpincount for base enum of pins to owner
- // override this to return more specialised pin objects
- function GetPin(n: integer): TBCBasePin; override;
- // Set bModifiesData == false if your derived filter does
- // not modify the data samples (for instance it's just copying
- // them somewhere else or looking at the timestamps).
- constructor Create(ObjectName: string; unk: IUnKnown; clsid: TGUID;
- out hr: HRESULT; ModifiesData: boolean = True);
- constructor CreateFromFactory(Factory: TBCClassFactory; const Controller: IUnknown); override;
- // The following are defined to avoid undefined pure virtuals.
- // Even if they are never called, they will give linkage warnings/errors
- // We override EnumMediaTypes to bypass the transform class enumerator
- // which would otherwise call this.
- function GetMediaType(Position: integer; out MediaType: PAMMediaType): HRESULT; override;
- // This is called when we actually have to provide out own allocator.
- function DecideBufferSize(Alloc: IMemAllocator; propInputRequest: PAllocatorProperties): HRESULT; override;
- // The functions which call this in CTransform are overridden in this
- // class to call CheckInputType with the assumption that the type
- // does not change. In Debug builds some calls will be made and
- // we just ensure that they do not assert.
- function CheckTransform(mtIn, mtOut: PAMMediaType): HRESULT; override;
- // =================================================================
- // ----- You may want to override this -----------------------------
- // =================================================================
- function CompleteConnect(dir: TPinDirection; ReceivePin: IPin): HRESULT; override;
- // chance to customize the transform process
- function Receive(Sample: IMediaSample): HRESULT; override;
- // =================================================================
- // ----- You MUST override these -----------------------------------
- // =================================================================
- function Transform(Sample: IMediaSample): HRESULT; reintroduce; virtual; abstract;
- // this goes in the factory template table to create new instances
- // static CCOMObject * CreateInstance(LPUNKNOWN, HRESULT *);
- protected
- FModifiesData: boolean; // Does this filter change the data?
- function Copy(Source: IMediaSample): IMediaSample;
- // these hold our input and output pins
- function InputPin: TBCTransInPlaceInputPin;
- function OutputPin: TBCTransInPlaceOutputPin;
- // Helper to see if the input and output types match
- function TypesMatch: boolean;
- // Are the input and output allocators different?
- function UsingDifferentAllocators: boolean;
- end;
- TBCTransInPlaceInputPin = class(TBCTransformInputPin)
- protected
- FTIPFilter: TBCTransInPlaceFilter; // our filter
- FReadOnly : boolean; // incoming stream is read only
- public
- constructor Create(ObjectName: string; Filter: TBCTransInPlaceFilter;
- out hr: HRESULT; Name: WideString);
- // --- IMemInputPin -----
- // Provide an enumerator for media types by getting one from downstream
- function EnumMediaTypes(out ppEnum: IEnumMediaTypes): HRESULT; override; stdcall;
- // Say whether media type is acceptable.
- function CheckMediaType(pmt: PAMMediaType): HRESULT; override;
- // Return our upstream allocator
- function GetAllocator(out Allocator: IMemAllocator): HRESULT; stdcall;
- // get told which allocator the upstream output pin is actually
- // going to use.
- function NotifyAllocator(Allocator: IMemAllocator; ReadOnly: BOOL): HRESULT; stdcall;
- // Allow the filter to see what allocator we have
- // N.B. This does NOT AddRef
- function PeekAllocator: IMemAllocator;
- // Pass this on downstream if it ever gets called.
- function GetAllocatorRequirements(props: PAllocatorProperties): HRESULT; stdcall;
- property ReadOnly: Boolean read FReadOnly;
- end;
- // ==================================================
- // Implements the output pin
- // ==================================================
- TBCTransInPlaceOutputPin = class(TBCTransformOutputPin)
- protected
- // m_pFilter points to our CBaseFilter
- FTIPFilter: TBCTransInPlaceFilter;
- public
- constructor Create(ObjectName: string; Filter: TBCTransInPlaceFilter;
- out hr: HRESULT; Name: WideString);
- // --- CBaseOutputPin ------------
- // negotiate the allocator and its buffer size/count
- // Insists on using our own allocator. (Actually the one upstream of us).
- // We don't override this - instead we just agree the default
- // then let the upstream filter decide for itself on reconnect
- // virtual HRESULT DecideAllocator(IMemInputPin * pPin, IMemAllocator ** pAlloc);
- // Provide a media type enumerator. Get it from upstream.
- function EnumMediaTypes(out ppEnum: IEnumMediaTypes): HRESULT; override; stdcall;
- // Say whether media type is acceptable.
- function CheckMediaType(pmt: PAMMediaType): HRESULT; override;
- // This just saves the allocator being used on the output pin
- // Also called by input pin's GetAllocator()
- procedure SetAllocator(Allocator: IMemAllocator);
- function ConnectedIMemInputPin: IMemInputPin;
- // Allow the filter to see what allocator we have
- // N.B. This does NOT AddRef
- function PeekAllocator: IMemAllocator;
- end;
- TBCBasePropertyPage = class(TBCUnknown, IPropertyPage)
- private
- FObjectSet: boolean; // SetObject has been called or not.
- protected
- FPageSite: IPropertyPageSite; // Details for our property site
- FDirty: boolean; // Has anything been changed
- FForm: TFormPropertyPage;
- public
- constructor Create(Name: String; Unk: IUnKnown; Form: TFormPropertyPage);
- destructor Destroy; override;
- procedure SetPageDirty;
- { IPropertyPage }
- function SetPageSite(const pageSite: IPropertyPageSite): HResult; stdcall;
- function Activate(hwndParent: HWnd; const rc: TRect; bModal: BOOL): HResult; stdcall;
- function Deactivate: HResult; stdcall;
- function GetPageInfo(out pageInfo: TPropPageInfo): HResult; stdcall;
- function SetObjects(cObjects: Longint; pUnkList: PUnknownList): HResult; stdcall;
- function Show(nCmdShow: Integer): HResult; stdcall;
- function Move(const rect: TRect): HResult; stdcall;
- function IsPageDirty: HResult; stdcall;
- function Apply: HResult; stdcall;
- function Help(pszHelpDir: POleStr): HResult; stdcall;
- function TranslateAccelerator(msg: PMsg): HResult; stdcall;
- end;
- TOnConnect = procedure(sender: Tobject; Unknown: IUnknown) of object;
- TFormPropertyPage = class(TForm, IUnKnown, IPropertyPage)
- private
- FPropertyPage: TBCBasePropertyPage;
- procedure MyWndProc(var aMsg: TMessage);
- public
- constructor Create(AOwner: TComponent); override;
- published
- function OnConnect(Unknown: IUnknown): HRESULT; virtual;
- function OnDisconnect: HRESULT; virtual;
- function OnApplyChanges: HRESULT; virtual;
- property PropertyPage : TBCBasePropertyPage read FPropertyPage implements IUnKnown, IPropertyPage;
- end;
- TBCBaseDispatch = class{IDispatch}
- protected
- FTI: ITypeInfo;
- public
- // IDispatch methods
- function GetTypeInfoCount(out Count: Integer): HResult; stdcall;
- function GetTypeInfo(const iid: TGUID; info: Cardinal; lcid: LCID; out tinfo): HRESULT; stdcall;
- function GetIDsOfNames(const IID: TGUID; Names: Pointer;
- NameCount, LocaleID: Integer; DispIDs: Pointer): HResult; stdcall;
- end;
- TBCMediaControl = class(TBCUnknown, IDispatch)
- public
- FBaseDisp: TBCBaseDispatch;
- constructor Create(name: string; unk: IUnknown);
- destructor Destroy; override;
- // IDispatch methods
- function GetTypeInfoCount(out Count: Integer): HResult; stdcall;
- function GetTypeInfo(Index, LocaleID: Integer; out TypeInfo): HResult; stdcall;
- function GetIDsOfNames(const IID: TGUID; Names: Pointer;
- NameCount, LocaleID: Integer; DispIDs: Pointer): HResult; stdcall;
- function Invoke(DispID: Integer; const IID: TGUID; LocaleID: Integer;
- Flags: Word; var Params; VarResult, ExcepInfo, ArgErr: Pointer): HResult; stdcall;
- end;
- TBCMediaEvent = class(TBCUnknown, IDisPatch{,IMediaEventEx})
- protected
- FBasedisp: TBCBaseDispatch;
- public
- constructor Create(Name: string; Unk: IUnknown);
- destructor destroy; override;
- // IDispatch methods
- function GetTypeInfoCount(out Count: Integer): HResult; stdcall;
- function GetTypeInfo(Index, LocaleID: Integer; out TypeInfo): HResult; stdcall;
- function GetIDsOfNames(const IID: TGUID; Names: Pointer;
- NameCount, LocaleID: Integer; DispIDs: Pointer): HResult; stdcall;
- function Invoke(DispID: Integer; const IID: TGUID; LocaleID: Integer;
- Flags: Word; var Params; VarResult, ExcepInfo, ArgErr: Pointer): HResult; stdcall;
- end;
- TBCMediaPosition = class(TBCUnknown, IDispatch {IMediaPosition})
- protected
- FBaseDisp: TBCBaseDispatch;
- public
- constructor Create(Name: String; Unk: IUnknown); overload;
- constructor Create(Name: String; Unk: IUnknown; out hr: HRESULT); overload;
- destructor Destroy; override;
- // IDispatch methods
- function GetTypeInfoCount(out Count: Integer): HResult; stdcall;
- function GetTypeInfo(Index, LocaleID: Integer; out TypeInfo): HResult; stdcall;
- function GetIDsOfNames(const IID: TGUID; Names: Pointer;
- NameCount, LocaleID: Integer; DispIDs: Pointer): HResult; stdcall;
- function Invoke(DispID: Integer; const IID: TGUID; LocaleID: Integer;
- Flags: Word; var Params; VarResult, ExcepInfo, ArgErr: Pointer): HResult; stdcall;
- end;
- // A utility class that handles IMediaPosition and IMediaSeeking on behalf
- // of single-input pin renderers, or transform filters.
- //
- // Renderers will expose this from the filter; transform filters will
- // expose it from the output pin and not the renderer.
- //
- // Create one of these, giving it your IPin* for your input pin, and delegate
- // all IMediaPosition methods to it. It will query the input pin for
- // IMediaPosition and respond appropriately.
- //
- // Call ForceRefresh if the pin connection changes.
- //
- // This class no longer caches the upstream IMediaPosition or IMediaSeeking
- // it acquires it on each method call. This means ForceRefresh is not needed.
- // The method is kept for source compatibility and to minimise the changes
- // if we need to put it back later for performance reasons.
- TBCPosPassThru = class(TBCMediaPosition, IMediaSeeking)
- protected
- FPin: IPin;
- function GetPeer(out MP: IMediaPosition): HRESULT;
- function GetPeerSeeking(out MS: IMediaSeeking): HRESULT;
- public
- constructor Create(name: String; Unk: IUnknown; out hr: HRESULT; Pin: IPin);
- function ForceRefresh: HRESULT;{return S_OK;}
- // override to return an accurate current position
- function GetMediaTime(out StartTime, EndTime: int64): HRESULT; virtual;
- // IMediaSeeking methods
- function GetCapabilities(out pCapabilities: DWORD): HRESULT; stdcall;
- function CheckCapabilities(var pCapabilities: DWORD): HRESULT; stdcall;
- function IsFormatSupported(const pFormat: TGUID): HRESULT; stdcall;
- function QueryPreferredFormat(out pFormat: TGUID): HRESULT; stdcall;
- function GetTimeFormat(out pFormat: TGUID): HRESULT; stdcall;
- function IsUsingTimeFormat(const pFormat: TGUID): HRESULT; stdcall;
- function SetTimeFormat(const pFormat: TGUID): HRESULT; stdcall;
- function GetDuration(out pDuration: int64): HRESULT; stdcall;
- function GetStopPosition(out pStop: int64): HRESULT; stdcall;
- function GetCurrentPosition(out pCurrent: int64): HRESULT; stdcall;
- function ConvertTimeFormat(out pTarget: int64; pTargetFormat: PGUID;
- Source: int64; pSourceFormat: PGUID): HRESULT; stdcall;
- function SetPositions(var pCurrent: int64; dwCurrentFlags: DWORD;
- var pStop: int64; dwStopFlags: DWORD): HRESULT; stdcall;
- function GetPositions(out pCurrent, pStop: int64): HRESULT; stdcall;
- function GetAvailable(out pEarliest, pLatest: int64): HRESULT; stdcall;
- function SetRate(dRate: double): HRESULT; stdcall;
- function GetRate(out pdRate: double): HRESULT; stdcall;
- function GetPreroll(out pllPreroll: int64): HRESULT; stdcall;
- // IMediaPosition properties
- function get_Duration(out plength: TRefTime): HResult; stdcall;
- function put_CurrentPosition(llTime: TRefTime): HResult; stdcall;
- function get_CurrentPosition(out pllTime: TRefTime): HResult; stdcall;
- function get_StopTime(out pllTime: TRefTime): HResult; stdcall;
- function put_StopTime(llTime: TRefTime): HResult; stdcall;
- function get_PrerollTime(out pllTime: TRefTime): HResult; stdcall;
- function put_PrerollTime(llTime: TRefTime): HResult; stdcall;
- function put_Rate(dRate: double): HResult; stdcall;
- function get_Rate(out pdRate: double): HResult; stdcall;
- function CanSeekForward(out pCanSeekForward: Longint): HResult; stdcall;
- function CanSeekBackward(out pCanSeekBackward: Longint): HResult; stdcall;
- end;
- TBCRendererPosPassThru = class(TBCPosPassThru)
- protected
- FPositionLock: TBCCritSec; // Locks access to our position
- FStartMedia : Int64; // Start media time last seen
- FEndMedia : Int64; // And likewise the end media
- FReset : boolean; // Have media times been set
- public
- // Used to help with passing media times through graph
- constructor Create(name: String; Unk: IUnknown; out hr: HRESULT; Pin: IPin); reintroduce;
- destructor destroy; override;
- function RegisterMediaTime(MediaSample: IMediaSample): HRESULT; overload;
- function RegisterMediaTime(StartTime, EndTime: int64): HRESULT; overload;
- function GetMediaTime(out StartTime, EndTime: int64): HRESULT; override;
- function ResetMediaTime: HRESULT;
- function EOS: HRESULT;
- end;
- // wrapper for event objects
- TBCAMEvent = class
- protected
- FEvent: THANDLE;
- public
- constructor Create(ManualReset: boolean = false);
- destructor destroy; override;
- property Handle: THandle read FEvent;
- procedure SetEv;
- function Wait(Timeout: Cardinal = INFINITE): boolean;
- procedure Reset;
- function Check: boolean;
- end;
- TBCTimeoutEvent = TBCAMEvent;
- // wrapper for event objects that do message processing
- // This adds ONE method to the CAMEvent object to allow sent
- // messages to be processed while waiting
- TBCAMMsgEvent = class(TBCAMEvent)
- public
- // Allow SEND messages to be processed while waiting
- function WaitMsg(Timeout: DWord = INFINITE): boolean;
- end;
- // support for a worker thread
- // simple thread class supports creation of worker thread, synchronization
- // and communication. Can be derived to simplify parameter passing
- TThreadProc = function: DWORD of object;
- TBCAMThread = class
- private
- FEventSend: TBCAMEvent;
- FEventComplete: TBCAMEvent;
- FParam: DWord;
- FReturnVal: DWord;
- FThreadProc: TThreadProc;
- protected
- FThread: THandle;
- // thread will run this function on startup
- // must be supplied by derived class
- function ThreadProc: DWord; virtual;
- public
- FAccessLock: TBCCritSec; // locks access by client threads
- FWorkerLock: TBCCritSec; // locks access to shared objects
- constructor Create;
- destructor Destroy; override;
- // thread initially runs this. param is actually 'this'. function
- // just gets this and calls ThreadProc
- function InitialThreadProc(p: Pointer): DWORD; virtual; stdcall; // WINAPI;
- // start thread running - error if already running
- function Create_: boolean;
- // signal the thread, and block for a response
- //
- function CallWorker(Param: DWORD): DWORD;
- // accessor thread calls this when done with thread (having told thread
- // to exit)
- procedure Close;
- // ThreadExists
- // Return True if the thread exists. FALSE otherwise
- function ThreadExists: boolean; // const
- // wait for the next request
- function GetRequest: DWORD;
- // is there a request?
- function CheckRequest(Param: PDWORD): boolean;
- // reply to the request
- procedure Reply(v: DWORD);
- // If you want to do WaitForMultipleObjects you'll need to include
- // this handle in your wait list or you won't be responsive
- function GetRequestHandle: THANDLE;
- // Find out what the request was
- function GetRequestParam: DWORD;
- // call CoInitializeEx (COINIT_DISABLE_OLE1DDE) if
- // available. S_FALSE means it's not available.
- class function CoInitializeHelper: HRESULT;
- end;
- TBCRenderedInputPin = class(TBCBaseInputPin)
- private
- procedure DoCompleteHandling;
- protected
- // Member variables to track state
- FAtEndOfStream : boolean; // Set by EndOfStream
- FCompleteNotified : boolean; // Set when we notify for EC_COMPLETE
- public
- constructor Create(ObjectName: string; Filter: TBCBaseFilter;
- Lock: TBCCritSec; out hr: HRESULT; Name: WideString);
- // Override methods to track end of stream state
- function EndOfStream: HRESULT; override; stdcall;
- function EndFlush: HRESULT; override; stdcall;
- function Active: HRESULT; override;
- function Run(Start: TReferenceTime): HRESULT; override;
- end;
- (* A generic list of pointers to objects.
- No storage management or copying is done on the objects pointed to.
- Objectives: avoid using MFC libraries in ndm kernel mode and
- provide a really useful list type.
- The class is thread safe in that separate threads may add and
- delete items in the list concurrently although the application
- must ensure that constructor and destructor access is suitably
- synchronised. An application can cause deadlock with operations
- which use two lists by simultaneously calling
- list1->Operation(list2) and list2->Operation(list1). So don't!
- The names must not conflict with MFC classes as an application
- may use both.
- *)
- (* A POSITION represents (in some fashion that's opaque) a cursor
- on the list that can be set to identify any element. NULL is
- a valid value and several operations regard NULL as the position
- "one step off the end of the list". (In an n element list there
- are n+1 places to insert and NULL is that "n+1-th" value).
- The POSITION of an element in the list is only invalidated if
- that element is deleted. Move operations may mean that what
- was a valid POSITION in one list is now a valid POSITION in
- a different list.
- Some operations which at first sight are illegal are allowed as
- harmless no-ops. For instance RemoveHead is legal on an empty
- list and it returns NULL. This allows an atomic way to test if
- there is an element there, and if so, get it. The two operations
- AddTail and RemoveHead thus implement a MONITOR (See Hoare's paper).
- Single element operations return POSITIONs, non-NULL means it worked.
- whole list operations return a BOOL. True means it all worked.
- This definition is the same as the POSITION type for MFCs, so we must
- avoid defining it twice.
- *)
- Position = Pointer;
- {$ifdef DEBUG}
- TBCNode = class(TBCBaseObject)
- {$else}
- TBCNode = class
- {$endif}
- private
- FPrev: TBCNode; // Previous node in the list
- FNext: TBCNode; // Next node in the list
- FObject: Pointer; // Pointer to the object
- public
- // Constructor - initialise the object's pointers
- {$ifdef DEBUG}
- constructor Create;
- {$endif}
- // Return the previous node before this one
- property Prev: TBCNode read FPrev write FPrev;
- // Return the next node after this one
- property Next: TBCNode read FNext write FNext;
- // Get the pointer to the object for this node */
- property Data: Pointer read FObject write FObject;
- end;
- TBCNodeCache = class
- private
- FCacheSize: Integer;
- FUsed: Integer;
- FHead: TBCNode;
- public
- constructor Create(CacheSize: Integer);
- destructor Destroy; override;
- procedure AddToCache(Node: TBCNode);
- function RemoveFromCache: TBCNode;
- end;
- (* A class representing one node in a list.
- Each node knows a pointer to it's adjacent nodes and also a pointer
- to the object that it looks after.
- All of these pointers can be retrieved or set through member functions.
- *)
- TBCBaseList = class
- {$ifdef DEBUG}
- (TBCBaseObject)
- {$endif}
- (* Making these classes inherit from CBaseObject does nothing
- functionally but it allows us to check there are no memory
- leaks in debug builds.
- *)
- protected
- FFirst: TBCNode; // Pointer to first node in the list
- FLast: TBCNode; // Pointer to the last node in the list
- FCount: LongInt; // Number of nodes currently in the list
- private
- FCache: TBCNodeCache; // Cache of unused node pointers
- public
- constructor Create(Name: string; Items: Integer = DEFAULTCACHE);
- destructor Destroy; override;
- // Remove all the nodes from self i.e. make the list empty
- procedure RemoveAll;
- // Return a cursor which identifies the first element of self
- function GetHeadPositionI: Position;
- /// Return a cursor which identifies the last element of self
- function GetTailPositionI: Position;
- // Return the number of objects in self
- function GetCountI: Integer;
- protected
- (* Return the pointer to the object at rp,
- Update rp to the next node in self
- but make it nil if it was at the end of self.
- This is a wart retained for backwards compatibility.
- GetPrev is not implemented.
- Use Next, Prev and Get separately.
- *)
- function GetNextI(var rp: Position): Pointer;
- (* Return a pointer to the object at p
- Asking for the object at nil will return nil harmlessly.
- *)
- function GetI(p: Position): Pointer;
- public
- (* return the next / prev position in self
- return NULL when going past the end/start.
- Next(nil) is same as GetHeadPosition()
- Prev(nil) is same as GetTailPosition()
- An n element list therefore behaves like a n+1 element
- cycle with nil at the start/end.
- !!WARNING!! - This handling of nil is DIFFERENT from GetNext.
- Some reasons are:
- 1. For a list of n items there are n+1 positions to insert
- These are conveniently encoded as the n POSITIONs and nil.
- 2. If you are keeping a list sorted (fairly common) and you
- search forward for an element to insert before and don't
- find it you finish up with nil as the element before which
- to insert. You then want that nil to be a valid POSITION
- so that you can insert before it and you want that insertion
- point to mean the (n+1)-th one that doesn't have a POSITION.
- (symmetrically if you are working backwards through the list).
- 3. It simplifies the algebra which the methods generate.
- e.g. AddBefore(p,x) is identical to AddAfter(Prev(p),x)
- in ALL cases. All the other arguments probably are reflections
- of the algebraic point.
- *)
- function Next(pos: Position): Position;
- function Prev(pos: Position): Position;
- (* Return the first position in self which holds the given
- pointer. Return nil if the pointer was not not found.
- *)
- protected
- function FindI(Obj: Pointer): Position;
- (* Remove the first node in self (deletes the pointer to its
- object from the list, does not free the object itself).
- Return the pointer to its object.
- If self was already empty it will harmlessly return nil.
- *)
- function RemoveHeadI: Pointer;
- (* Remove the last node in self (deletes the pointer to its
- object from the list, does not free the object itself).
- Return the pointer to its object.
- If self was already empty it will harmlessly return nil.
- *)
- function RemoveTailI: Pointer;
- (* Remove the node identified by p from the list (deletes the pointer
- to its object from the list, does not free the object itself).
- Asking to Remove the object at nil will harmlessly return nil.
- Return the pointer to the object removed.
- *)
- function RemoveI(pos: Position): Pointer;
- (* Add single object *pObj to become a new last element of the list.
- Return the new tail position, nil if it fails.
- If you are adding a COM objects, you might want AddRef it first.
- Other existing POSITIONs in self are still valid
- *)
- function AddTailI(Obj: Pointer): Position;
- public
- (* Add all the elements in *pList to the tail of self.
- This duplicates all the nodes in *pList (i.e. duplicates
- all its pointers to objects). It does not duplicate the objects.
- If you are adding a list of pointers to a COM object into the list
- it's a good idea to AddRef them all it when you AddTail it.
- Return True if it all worked, FALSE if it didn't.
- If it fails some elements may have been added.
- Existing POSITIONs in self are still valid
- If you actually want to MOVE the elements, use MoveToTail instead.
- *)
- function AddTail(List: TBCBaseList): boolean;
- // Mirror images of AddHead:
- (* Add single object to become a new first element of the list.
- Return the new head position, nil if it fails.
- Existing POSITIONs in self are still valid
- *)
- protected
- function AddHeadI(Obj: Pointer): Position;
- public
- (* Add all the elements in *pList to the head of self.
- Same warnings apply as for AddTail.
- Return True if it all worked, FALSE if it didn't.
- If it fails some of the objects may have been added.
- If you actually want to MOVE the elements, use MoveToHead instead.
- *)
- function AddHead(List: TBCBaseList): BOOL;
- (* Add the object *pObj to self after position p in self.
- AddAfter(nil,x) adds x to the start - equivalent to AddHead
- Return the position of the object added, nil if it failed.
- Existing POSITIONs in self are undisturbed, including p.
- *)
- protected
- function AddAfterI(pos: Position; Obj: Pointer): Position;
- public
- (* Add the list *pList to self after position p in self
- AddAfter(nil,x) adds x to the start - equivalent to AddHead
- Return True if it all worked, FALSE if it didn't.
- If it fails, some of the objects may be added
- Existing POSITIONs in self are undisturbed, including p.
- *)
- function AddAfter(p: Position; List: TBCBaseList): BOOL;
- (* Mirror images:
- Add the object *pObj to this-List after position p in self.
- AddBefore(nil,x) adds x to the end - equivalent to AddTail
- Return the position of the new object, nil if it fails
- Existing POSITIONs in self are undisturbed, including p.
- *)
- protected
- function AddBeforeI(pos: Position; Obj: Pointer): Position;
- public
- (* Add the list *pList to self before position p in self
- AddAfter(nil,x) adds x to the start - equivalent to AddHead
- Return True if it all worked, FALSE if it didn't.
- If it fails, some of the objects may be added
- Existing POSITIONs in self are undisturbed, including p.
- *)
- function AddBefore(p: Position; List: TBCBaseList): BOOL;
- (* Note that AddAfter(p,x) is equivalent to AddBefore(Next(p),x)
- even in cases where p is nil or Next(p) is nil.
- Similarly for mirror images etc.
- This may make it easier to argue about programs.
- *)
- (* The following operations do not copy any elements.
- They move existing blocks of elements around by switching pointers.
- They are fairly efficient for long lists as for short lists.
- (Alas, the Count slows things down).
- They split the list into two parts.
- One part remains as the original list, the other part
- is appended to the second list. There are eight possible
- variations:
- Split the list {after/before} a given element
- keep the {head/tail} portion in the original list
- append the rest to the {head/tail} of the new list.
- Since After is strictly equivalent to Before Next
- we are not in serious need of the Before/After variants.
- That leaves only four.
- If you are processing a list left to right and dumping
- the bits that you have processed into another list as
- you go, the Tail/Tail variant gives the most natural result.
- If you are processing in reverse order, Head/Head is best.
- By using nil positions and empty lists judiciously either
- of the other two can be built up in two operations.
- The definition of nil (see Next/Prev etc) means that
- degenerate cases include
- "move all elements to new list"
- "Split a list into two lists"
- "Concatenate two lists"
- (and quite a few no-ops)
- !!WARNING!! The type checking won't buy you much if you get list
- positions muddled up - e.g. use a POSITION that's in a different
- list and see what a mess you get!
- *)
- (* Split self after position p in self
- Retain as self the tail portion of the original self
- Add the head portion to the tail end of *pList
- Return True if it all worked, FALSE if it didn't.
- e.g.
- foo->MoveToTail(foo->GetHeadPosition(), bar);
- moves one element from the head of foo to the tail of bar
- foo->MoveToTail(nil, bar);
- is a no-op, returns nil
- foo->MoveToTail(foo->GetTailPosition, bar);
- concatenates foo onto the end of bar and empties foo.
- A better, except excessively long name might be
- MoveElementsFromHeadThroughPositionToOtherTail
- *)
- function MoveToTail(pos: Position; List: TBCBaseList): boolean;
- (* Mirror image:
- Split self before position p in self.
- Retain in self the head portion of the original self
- Add the tail portion to the start (i.e. head) of *pList
- e.g.
- foo->MoveToHead(foo->GetTailPosition(), bar);
- moves one element from the tail of foo to the head of bar
- foo->MoveToHead(nil, bar);
- is a no-op, returns nil
- foo->MoveToHead(foo->GetHeadPosition, bar);
- concatenates foo onto the start of bar and empties foo.
- *)
- function MoveToHead(pos: Position; List: TBCBaseList): boolean;
- (* Reverse the order of the [pointers to] objects in self *)
- procedure Reverse;
- end;
- // Desc: DirectShow base classes - defines classes to simplify creation of
- // ActiveX source filters that support continuous generation of data.
- // No support is provided for IMediaControl or IMediaPosition.
- //
- // Derive your source filter from CSource.
- // During construction either:
- // Create some CSourceStream objects to manage your pins
- // Provide the user with a means of doing so eg, an IPersistFile interface.
- //
- // CSource provides:
- // IBaseFilter interface management
- // IMediaFilter interface management, via CBaseFilter
- // Pin counting for CBaseFilter
- //
- // Derive a class from CSourceStream to manage your output pin types
- // Implement GetMediaType/1 to return the type you support. If you support multiple
- // types then overide GetMediaType/3, CheckMediaType and GetMediaTypeCount.
- // Implement Fillbuffer() to put data into one buffer.
- //
- // CSourceStream provides:
- // IPin management via CBaseOutputPin
- // Worker thread management
- // Override construction to provide a means of creating
- // CSourceStream derived objects - ie a way of creating pins.
- TBCSourceStream = class;
- TStreamArray = array of TBCSourceStream;
- TBCSource = class(TBCBaseFilter)
- protected
- FPins: Integer; // The number of pins on this filter. Updated by CSourceStream
- FStreams: Pointer; // the pins on this filter.
- FStateLock: TBCCritSec;
- public
- constructor Create(const Name: string; unk: IUnknown; const clsid: TGUID; out hr: HRESULT); overload;
- constructor Create(const Name: string; unk: IUnknown; const clsid: TGUID); overload;
- destructor Destroy; override;
- function GetPinCount: Integer; override;
- function GetPin(n: Integer): TBCBasePin; override;
- // -- Utilities --
- property StateLock: TBCCritSec read FStateLock; // provide our critical section
- function AddPin(Stream: TBCSourceStream): HRESULT;
- function RemovePin(Stream: TBCSourceStream): HRESULT;
- function FindPin(Id: PWideChar; out Pin: IPin): HRESULT; override;
- function FindPinNumber(Pin: IPin): Integer;
- end;
- //
- // CSourceStream
- //
- // Use this class to manage a stream of data that comes from a
- // pin.
- // Uses a worker thread to put data on the pin.
- TThreadCommand = (
- CMD_INIT,
- CMD_PAUSE,
- CMD_RUN,
- CMD_STOP,
- CMD_EXIT
- );
- TBCSourceStream = class(TBCBaseOutputPin)
- public
- constructor Create(const ObjectName: string; out hr: HRESULT;
- Filter: TBCSource; const Name: WideString);
- destructor Destroy; override;
- protected
- FThread: TBCAMThread;
- FFilter: TBCSource; // The parent of this stream
- // *
- // * Data Source
- // *
- // * The following three functions: FillBuffer, OnThreadCreate/Destroy, are
- // * called from within the ThreadProc. They are used in the creation of
- // * the media samples this pin will provide
- // *
- // Override this to provide the worker thread a means
- // of processing a buffer
- function FillBuffer(Samp: IMediaSample): HRESULT; virtual; abstract;
- // Called as the thread is created/destroyed - use to perform
- // jobs such as start/stop streaming mode
- // If OnThreadCreate returns an error the thread will exit.
- function OnThreadCreate: HRESULT; virtual;
- function OnThreadDestroy: HRESULT; virtual;
- function OnThreadStartPlay: HRESULT; virtual;
- public
- // *
- // * Worker Thread
- // *
- function Active: HRESULT; override; // Starts up the worker thread
- function Inactive: HRESULT; override; // Exits the worker thread.
- // thread commands
- function Init: HRESULT;
- function Exit_: HRESULT;
- function Run: HRESULT; reintroduce;
- function Pause: HRESULT;
- function Stop: HRESULT;
- // *
- // * AM_MEDIA_TYPE support
- // *
- // If you support more than one media type then override these 2 functions
- function CheckMediaType(MediaType: PAMMediaType): HRESULT; override;
- function GetMediaType(Position: integer; out MediaType: PAMMediaType): HRESULT; overload; override; // List pos. 0-n
- // If you support only one type then override this fn.
- // This will only be called by the default implementations
- // of CheckMediaType and GetMediaType(int, CMediaType*)
- // You must override this fn. or the above 2!
- function GetMediaType(MediaType: PAMMediaType): HRESULT; reintroduce; overload; virtual;
- function QueryId(out id: PWideChar): HRESULT; override;
- protected
- function GetRequest: TThreadCommand;
- function CheckRequest(var com: TThreadCommand): boolean;
- // override these if you want to add thread commands
- function ThreadProc: DWORD; virtual; // the thread function
- function DoBufferProcessingLoop: HRESULT; virtual; // the loop executed whilst running
- end;
- TBCBaseRenderer = class;
- TBCRendererInputPin = class;
- // This is our input pin class that channels calls to the renderer
- TBCRendererInputPin = class(TBCBaseInputPin)
- protected
- FRenderer: TBCBaseRenderer;
- public
- constructor Create(Renderer: TBCBaseRenderer; out hr: HResult;
- Name: PWideChar);
- // Overriden from the base pin classes
- function BreakConnect: HResult; override;
- function CompleteConnect(ReceivePin: IPin): HResult; override;
- function SetMediaType(MediaType: PAMMediaType): HResult; override;
- function CheckMediaType(MediaType: PAMMediaType): HResult; override;
- function Active: HResult; override;
- function Inactive: HResult; override;
- // Add rendering behaviour to interface functions
- function QueryId(out Id: PWideChar): HResult; override; stdcall;
- function EndOfStream: HResult; override; stdcall;
- function BeginFlush: HResult; override; stdcall;
- function EndFlush: HResult; override; stdcall;
- function Receive(MediaSample: IMediaSample): HResult; override; stdcall;
- function InheritedReceive(MediaSample: IMediaSample): HResult;
- virtual; stdcall;
- end;
- // Main renderer class that handles synchronisation and state changes
- TBCBaseRenderer = class(TBCBaseFilter)
- protected
- // friend class CRendererInputPin;
- //FEndOfStreamTimerCB: TFNTimeCallBack;
- // Media seeking pass by object
- FPosition: TBCRendererPosPassThru;
- //FPosition: IUnknown;
- // Used to signal timer events
- FRenderEvent: TBCAMEvent;
- // Signalled to release worker thread
- FThreadSignal: TBCAMEvent;
- // Signalled when state complete
- FCompleteEvent: TBCAMEvent;
- // Stop us from rendering more data
- FAbort: Boolean;
- // Are we currently streaming
- FIsStreaming: Boolean;
- // Timer advise cookie
- FAdvisedCookie: DWord;
- // Current image media sample
- FMediaSample: IMediaSample;
- // Any more samples in the stream
- FIsEOS: Boolean;
- // Have we delivered an EC_COMPLETE
- FIsEOSDelivered: Boolean;
- // Our renderer input pin object
- FInputPin: TBCRendererInputPin;
- // Critical section for interfaces
- FInterfaceLock: TBCCritSec;
- // Controls access to internals
- FRendererLock: TBCCritSec;
- // QualityControl sink
- FQSink: IQualityControl;
- // Can we signal an EC_REPAINT
- FRepaintStatus: Boolean;
- // Avoid some deadlocks by tracking filter during stop
- // Inside Receive between PrepareReceive and actually processing the sample
- FInReceive: Boolean;
- // Time when we signal EC_COMPLETE
- FSignalTime: TReferenceTime;
- // Used to signal end of stream
- FEndOfStreamTimer: DWord;
- // This lock protects the creation and of FPosition and FInputPin.
- // It ensures that two threads cannot create either object simultaneously.
- FObjectCreationLock: TBCCritSec;
- // Milenko start (must be outside of the class and with stdcall; or it will crash)
- // procedure EndOfStreamTimer(
- // uID: UINT; // Timer identifier
- // uMsg: UINT; // Not currently used
- // dwUser: DWord; // User information
- // dw1: DWord; // Windows reserved
- // dw2: DWord // Is also reserved
- // ); stdcall;
- // Milenko end
- public
- {$IFDEF PERF}
- // Just before we started drawing
- // Set in OnRenderStart, Used in OnRenderEnd
- FRenderStart: TReferenceTime;
- // MSR_id for frame time stamp
- FBaseStamp: Integer;
- // MSR_id for true wait time
- FBaseRenderTime: Integer;
- // MSR_id for time frame is late (int)
- FBaseAccuracy: Integer;
- {$ENDIF}
- constructor Create(
- // CLSID for this renderer
- RendererClass: TGUID;
- // Debug ONLY description
- Name: PChar;
- // Aggregated owner object
- Unk: IUnknown;
- // General OLE return code
- hr: HResult);
- destructor Destroy; override;
- // milenko start (added as a workaround for the TBCRendererPosPAssThru/FPosition and Renderer destructor)
- function JoinFilterGraph(pGraph: IFilterGraph; pName: PWideChar): HRESULT; override;
- // milenko end
- // Overriden to say what interfaces we support and where
- function GetMediaPositionInterface(IID: TGUID; out Obj): HResult;
- virtual;
- function NonDelegatingQueryInterface(const IID: TGUID; out Obj): HResult;
- override; stdcall;
- function SourceThreadCanWait(CanWait: Boolean): HResult; virtual;
- {$IFDEF DEBUG}
- // Debug only dump of the renderer state
- procedure DisplayRendererState;
- {$ENDIF}
- function WaitForRenderTime: HResult; virtual;
- function CompleteStateChange(OldState: TFilterState): HResult; virtual;
- // Return internal information about this filter
- property IsEndOfStream: Boolean read FIsEOS;
- property IsEndOfStreamDelivered: Boolean read FIsEOSDelivered;
- property IsStreaming: Boolean read FIsStreaming;
- procedure SetAbortSignal(Abort_: Boolean);
- procedure OnReceiveFirstSample(MediaSample: IMediaSample); virtual;
- property RenderEvent: TBCAMEvent read FRenderEvent;
- // Permit access to the transition state
- procedure Ready;
- procedure NotReady;
- function CheckReady: Boolean;
- function GetPinCount: Integer; override;
- function GetPin(n: integer): TBCBasePin; override;
- function GetRealState: TFilterState;
- procedure SendRepaint;
- procedure SendNotifyWindow(Pin: IPin; Handle: HWND);
- function OnDisplayChange: Boolean;
- procedure SetRepaintStatus(Repaint: Boolean);
- // Override the filter and pin interface functions
- function Stop: HResult; override; stdcall;
- function Pause: HResult; override; stdcall;
- function Run(StartTime: TReferenceTime): HResult; override; stdcall;
- function GetState(MSecs: DWord; out State: TFilterState): HResult;
- override; stdcall;
- function FindPin(id: PWideChar; out Pin: IPin): HResult;
- override; stdcall;
- // These are available for a quality management implementation
- procedure OnRenderStart(MediaSample: IMediaSample); virtual;
- procedure OnRenderEnd(MediaSample: IMediaSample); virtual;
- function OnStartStreaming: HResult; virtual;
- function OnStopStreaming: HResult; virtual;
- procedure OnWaitStart; virtual;
- procedure OnWaitEnd; virtual;
- procedure PrepareRender; virtual;
- // Quality management implementation for scheduling rendering
- function ScheduleSample(MediaSample: IMediaSample): Boolean; virtual;
- function GetSampleTimes(MediaSample: IMediaSample;
- out StartTime: TReferenceTime; out EndTime: TReferenceTime): HResult;
- virtual;
- function ShouldDrawSampleNow(MediaSample: IMediaSample;
- StartTime: TReferenceTime; out EndTime: TReferenceTime): HResult; virtual;
- // Lots of end of stream complexities
- procedure TimerCallback;
- procedure ResetEndOfStreamTimer;
- function NotifyEndOfStream: HResult;
- function SendEndOfStream: HResult; virtual;
- function ResetEndOfStream: HResult; virtual;
- function EndOfStream: HResult; virtual;
- // Rendering is based around the clock
- procedure SignalTimerFired;
- function CancelNotification: HResult; virtual;
- function ClearPendingSample: HResult; virtual;
- // Called when the filter changes state
- function Active: HResult; virtual;
- function Inactive: HResult; virtual;
- function StartStreaming: HResult; virtual;
- function StopStreaming: HResult; virtual;
- function BeginFlush: HResult; virtual;
- function EndFlush: HResult; virtual;
- // Deal with connections and type changes
- function BreakConnect: HResult; virtual;
- function SetMediaType(MediaType: PAMMediaType): HResult; virtual;
- function CompleteConnect(ReceivePin: IPin): HResult; virtual;
- // These look after the handling of data samples
- function PrepareReceive(MediaSample: IMediaSample): HResult; virtual;
- function Receive(MediaSample: IMediaSample): HResult; virtual;
- function HaveCurrentSample: Boolean; virtual;
- function GetCurrentSample: IMediaSample; virtual;
- function Render(MediaSample: IMediaSample): HResult; virtual;
- // Derived classes MUST override these
- function DoRenderSample(MediaSample: IMediaSample): HResult;
- virtual; abstract;
- function CheckMediaType(MediaType: PAMMediaType): HResult;
- virtual; abstract;
- // Helper
- procedure WaitForReceiveToComplete;
- (*
- // callback
- property EndOfStreamTimerCB: TFNTimeCallBack read FEndOfStreamTimerCB
- write FEndOfStreamTimerCB;
- *)
- end;
- const
- AVGPERIOD = 4;
- type
- // CBaseVideoRenderer is a renderer class (see its ancestor class) and
- // it handles scheduling of media samples so that they are drawn at the
- // correct time by the reference clock. It implements a degradation
- // strategy. Possible degradation modes are:
- // Drop frames here (only useful if the drawing takes significant time)
- // Signal supplier (upstream) to drop some frame(s) - i.e. one-off skip.
- // Signal supplier to change the frame rate - i.e. ongoing skipping.
- // Or any combination of the above.
- // In order to determine what's useful to try we need to know what's going
- // on. This is done by timing various operations (including the supplier).
- // This timing is done by using timeGetTime as it is accurate enough and
- // usually cheaper than calling the reference clock. It also tells the
- // truth if there is an audio break and the reference clock stops.
- // We provide a number of public entry points (named OnXxxStart, OnXxxEnd)
- // which the rest of the renderer calls at significant moments. These do
- // the timing.
- // the number of frames that the sliding averages are averaged over.
- // the rule is (1024*NewObservation + (AVGPERIOD-1) * PreviousAverage)/AVGPERIOD
- // #define DO_MOVING_AVG(avg,obs) (avg = (1024*obs + (AVGPERIOD-1)*avg)/AVGPERIOD)
- // Spot the bug in this macro - I can't. but it doesn't work!
- TBCBaseVideoRenderer = class(
- // Base renderer class
- TBCBaseRenderer,
- // Property page guff
- IQualProp,
- // Allow throttling
- IQualityControl)
- protected
- //******************************************************************
- // State variables to control synchronisation
- //******************************************************************
- // Control of sending Quality messages. We need to know whether
- // we are in trouble (e.g. frames being dropped) and where the time
- // is being spent.
- // When we drop a frame we play the next one early.
- // The frame after that is likely to wait before drawing and counting this
- // wait as spare time is unfair, so we count it as a zero wait.
- // We therefore need to know whether we are playing frames early or not.
- // The number of consecutive frames drawn at their normal time (not early)
- // -1 means we just dropped a frame.
- FNormal: Integer;
- {$IFDEF PERF}
- // Don't drop any frames (debug and I'm not keen on people using it!)
- FDrawLateFrames: Bool;
- {$ENDIF}
- // The response to Quality messages says our supplier is handling things.
- // We will allow things to go extra late before dropping frames.
- // We will play very early after he has dropped one.
- FSupplierHandlingQuality: Boolean;
- // Control of scheduling, frame dropping etc.
- // We need to know where the time is being spent so as to tell whether
- // we should be taking action here, signalling supplier or what.
- // The variables are initialised to a mode of NOT dropping frames.
- // They will tell the truth after a few frames.
- // We typically record a start time for an event, later we get the time
- // again and subtract to get the elapsed time, and we average this over
- // a few frames. The average is used to tell what mode we are in.
- // Although these are reference times (64 bit) they are all DIFFERENCES
- // between times which are small. An int will go up to 214 secs before
- // overflow. Avoiding 64 bit multiplications and divisions seems
- // worth while.
- // Audio-video throttling. If the user has turned up audio quality
- // very high (in principle it could be any other stream, not just audio)
- // then we can receive cries for help via the graph manager. In this case
- // we put in a wait for some time after rendering each frame.
- FThrottle: Integer;
- // The time taken to render (i.e. BitBlt) frames controls which component
- // needs to degrade. If the blt is expensive, the renderer degrades.
- // If the blt is cheap it's done anyway and the supplier degrades.
- // Time frames are taking to blt
- FRenderAvg: Integer;
- // Time for last frame blt
- FRenderLast: Integer;
- // Just before we started drawing (mSec) derived from timeGetTime.
- FRenderStart: Integer;
- // When frames are dropped we will play the next frame as early as we can.
- // If it was a false alarm and the machine is fast we slide gently back to
- // normal timing. To do this, we record the offset showing just how early
- // we really are. This will normally be negative meaning early or zero.
- FEarliness: Integer;
- // Target provides slow long-term feedback to try to reduce the
- // average sync offset to zero. Whenever a frame is actually rendered
- // early we add a msec or two, whenever late we take off a few.
- // We add or take off 1/32 of the error time.
- // Eventually we should be hovering around zero. For a really bad case
- // where we were (say) 300mSec off, it might take 100 odd frames to
- // settle down. The rate of change of this is intended to be slower
- // than any other mechanism in Quartz, thereby avoiding hunting.
- FTarget: Integer;
- // The proportion of time spent waiting for the right moment to blt
- // controls whether we bother to drop a frame or whether we reckon that
- // we're doing well enough that we can stand a one-frame glitch.
- // Average of last few wait times (actually we just average how early we were).
- // Negative here means LATE.
- FWaitAvg: Integer;
- // The average inter-frame time.
- // This is used to calculate the proportion of the time used by the
- // three operations (supplying us, waiting, rendering)
- // Average inter-frame time
- FFrameAvg: Integer;
- // duration of last frame.
- FDuration: Integer;
- {$IFDEF PERF}
- // Performance logging identifiers
- // MSR_id for frame time stamp
- FTimeStamp: Integer;
- // MSR_id for true wait time
- FWaitReal: Integer;
- // MSR_id for wait time recorded
- FWait: Integer;
- // MSR_id for time frame is late (int)
- FFrameAccuracy: Integer;
- // MSR_id for lateness at scheduler
- FSchLateTime: Integer;
- // MSR_id for Quality rate requested
- FQualityRate: Integer;
- // MSR_id for Quality time requested
- FQualityTime: Integer;
- // MSR_id for decision code
- FDecision: Integer;
- // MSR_id for trace style debugging
- FDebug: Integer;
- // MSR_id for timing the notifications per se
- FSendQuality: Integer;
- {$ENDIF}
- // original time stamp of frame with no earliness fudges etc.
- FRememberStampforPerf: TReferenceTime;
- {$IFDEF PERF}
- // time when previous frame rendered
- FRememberFrameForPerf: TReferenceTime;
- {$ENDIF}
- // PROPERTY PAGE
- // This has edit fields that show the user what's happening
- // These member variables hold these counts.
- // cumulative frames dropped IN THE RENDERER
- FFramesDropped: Integer;
- // Frames since streaming started seen BY THE RENDERER
- // (some may be dropped upstream)
- FFramesDrawn: Integer;
- // Next two support average sync offset and standard deviation of sync offset.
- // Sum of accuracies in mSec
- FTotAcc: Int64;
- // Sum of squares of (accuracies in mSec)
- FSumSqAcc: Int64;
- // Next two allow jitter calculation. Jitter is std deviation of frame time.
- // Time of prev frame (for inter-frame times)
- FLastDraw: TReferenceTime;
- // Sum of squares of (inter-frame time in mSec)
- FSumSqFrameTime: Int64;
- // Sum of inter-frame times in mSec
- FSumFrameTime: Int64;
- // To get performance statistics on frame rate, jitter etc, we need
- // to record the lateness and inter-frame time. What we actually need are the
- // data above (sum, sum of squares and number of entries for each) but the data
- // is generated just ahead of time and only later do we discover whether the
- // frame was actually drawn or not. So we have to hang on to the data
- // hold onto frame lateness
- FLate: Integer;
- // hold onto inter-frame time
- FFrame: Integer;
- // if streaming then time streaming started
- // else time of last streaming session
- // used for property page statistics
- FStreamingStart: Integer;
- {$IFDEF PERF}
- // timeGetTime*10000+m_llTimeOffset==ref time
- FTimeOffset: Int64;
- {$ENDIF}
- public
- constructor Create(
- // CLSID for this renderer
- RenderClass: TGUID;
- // Debug ONLY description
- Name: PChar;
- // Aggregated owner object
- Unk: IUnknown;
- // General OLE return code
- hr: HResult);
- destructor Destroy; override;
- function NonDelegatingQueryInterface(const IID: TGUID; out Obj): HResult;
- override; stdcall;
- // IQualityControl methods - Notify allows audio-video throttling
- function SetSink(QualityControl: IQualityControl): HResult; stdcall;
- function Notify(Filter: IBaseFilter; q: TQuality): HResult; stdcall;
- // These provide a full video quality management implementation
- procedure OnRenderStart(MediaSample: IMediaSample); override;
- procedure OnRenderEnd(MediaSample: IMediaSample); override;
- procedure OnWaitStart; reintroduce;
- procedure OnWaitEnd; reintroduce;
- function OnStartStreaming: HResult; reintroduce;
- function OnStopStreaming: HResult; reintroduce;
- procedure ThrottleWait;
- // Handle the statistics gathering for our quality management
- procedure PreparePerformanceData(Late, Frame: Integer);
- procedure RecordFrameLateness(Late, Frame: Integer); virtual;
- procedure OnDirectRender(MediaSample: IMediaSample); virtual;
- function ResetStreamingTimes: HResult; virtual;
- function ScheduleSample(MediaSample: IMediaSample): Boolean; override;
- function ShouldDrawSampleNow(MediaSample: IMediaSample;
- StartTime: TReferenceTime; out EndTime: TReferenceTime): HResult;
- override;
- function SendQuality(Late, RealStream: TReferenceTime): HResult; virtual;
- // milenko start (TBCBaseFilter made virtual, so just add override here)
- function JoinFilterGraph(Graph: IFilterGraph; Name: PWideChar): HResult; override;
- // milenko end
- //
- // Do estimates for standard deviations for per-frame
- // statistics
- //
- // *piResult = (llSumSq - iTot * iTot / m_cFramesDrawn - 1) /
- // (m_cFramesDrawn - 2)
- // or 0 if m_cFramesDrawn <= 3
- //
- function GetStdDev(Samples: Integer; out Res: Integer;
- SumSq, Tot: Int64): HResult;
- // IQualProp property page support
- // ??? out <- var function get_FramesDroppedInRenderer(out pcFrames : Integer) : HResult; stdcall;
- function get_FramesDroppedInRenderer(var FramesDropped: Integer): HResult;
- stdcall;
- function get_FramesDrawn(out FramesDrawn: Integer): HResult; stdcall;
- function get_AvgFrameRate(out AvgFrameRate: Integer): HResult; stdcall;
- function get_Jitter(out Jitter: Integer): HResult; stdcall;
- function get_AvgSyncOffset(out Avg: Integer): HResult; stdcall;
- function get_DevSyncOffset(out Dev: Integer): HResult; stdcall;
- end;
-
// milenko start (added TBCPullPin)
-
//
-
// CPullPin
- //
- // object supporting pulling data from an IAsyncReader interface.
- // Given a start/stop position, calls a pure Receive method with each
- // IMediaSample received.
- //
- // This is essentially for use in a MemInputPin when it finds itself
- // connected to an IAsyncReader pin instead of a pushing pin.
- //
- TThreadMsg = (
- TM_Pause, // stop pulling and wait for next message
- TM_Start, // start pulling
- TM_Exit // stop and exit
- );
- TBCPullPin = class(TBCAMThread)
- private
- FReader: IAsyncReader;
- FStart: TReferenceTime;
- FStop: TReferenceTime;
- FDuration: TReferenceTime;
- FSync: Boolean;
- FState: TThreadMsg;
- // running pull method (check m_bSync)
- procedure Process;
- // clean up any cancelled i/o after a flush
- procedure CleanupCancelled;
- // suspend thread from pulling, eg during seek
- function PauseThread: HRESULT;
- // start thread pulling - create thread if necy
- function StartThread: HRESULT;
- // stop and close thread
- function StopThread: HRESULT;
- // called from ProcessAsync to queue and collect requests
- function QueueSample(var tCurrent: TReferenceTime; tAlignStop: TReferenceTime; bDiscontinuity: Boolean): HRESULT;
- function CollectAndDeliver(tStart,tStop: TReferenceTime): HRESULT;
- function DeliverSample(pSample: IMediaSample; tStart,tStop: TReferenceTime): HRESULT;
- protected
- FAlloc: IMemAllocator;
- // override pure thread proc from CAMThread
- function ThreadProc: DWord; override;
- public
- constructor Create;
- destructor Destroy; override;
- // returns S_OK if successfully connected to an IAsyncReader interface
- // from this object
- // Optional allocator should be proposed as a preferred allocator if
- // necessary
- // bSync is TRUE if we are to use sync reads instead of the
- // async methods.
- function Connect(pUnk: IUnknown; pAlloc: IMemAllocator; bSync: Boolean): HRESULT;
- // disconnect any connection made in Connect
- function Disconnect: HRESULT;
- // agree an allocator using RequestAllocator - optional
- // props param specifies your requirements (non-zero fields).
- // returns an error code if fail to match requirements.
- // optional IMemAllocator interface is offered as a preferred allocator
- // but no error occurs if it can't be met.
- function DecideAllocator(pAlloc: IMemAllocator; pProps: PAllocatorProperties): HRESULT;
- // set start and stop position. if active, will start immediately at
- // the new position. Default is 0 to duration
- function Seek(tStart, tStop: TReferenceTime): HRESULT;
- // return the total duration
- function Duration(out ptDuration: TReferenceTime): HRESULT;
- // start pulling data
- function Active: HRESULT;
- // stop pulling data
- function Inactive: HRESULT;
- // helper functions
- function AlignDown(ll: Int64; lAlign: LongInt): Int64;
- function AlignUp(ll: Int64; lAlign: LongInt): Int64;
- // GetReader returns the (addrefed) IAsyncReader interface
- // for SyncRead etc
- function GetReader: IAsyncReader;
- // -- pure --
- // override this to handle data arrival
- // return value other than S_OK will stop data
- function Receive(Sample: IMediaSample): HRESULT; virtual; abstract;
- // override this to handle end-of-stream
- function EndOfStream: HRESULT; virtual; abstract;
- // called on runtime errors that will have caused pulling
- // to stop
- // these errors are all returned from the upstream filter, who
- // will have already reported any errors to the filtergraph.
- procedure OnError(hr: HRESULT); virtual; abstract;
- // flush this pin and all downstream
- function BeginFlush: HRESULT; virtual; abstract;
- function EndFlush: HRESULT; virtual; abstract;
- end;
- // milenko end
- // milenko start (needed to access functions outside. usefull for Filter Development)
- function CreateMemoryAllocator(out Allocator: IMemAllocator): HRESULT;
- function AMGetWideString(Source: WideString; out Dest: PWideChar): HRESULT;
- function CreatePosPassThru(Agg: IUnknown; Renderer: boolean; Pin: IPin; out PassThru: IUnknown): HRESULT; stdcall;
- // milenko end
- // milenko start reftime implementation
- //------------------------------------------------------------------------------
- // File: RefTime.h
- //
- // Desc: DirectShow base classes - defines CRefTime, a class that manages
- // reference times.
- //
- // Copyright (c) 1992-2002 Microsoft Corporation. All rights reserved.
- //------------------------------------------------------------------------------
- //
- // CRefTime
- //
- // Manage reference times.
- // Shares same data layout as REFERENCE_TIME, but adds some (nonvirtual)
- // functions providing simple comparison, conversion and arithmetic.
- //
- // A reference time (at the moment) is a unit of seconds represented in
- // 100ns units as is used in the Win32 FILETIME structure. BUT the time
- // a REFERENCE_TIME represents is NOT the time elapsed since 1/1/1601 it
- // will either be stream time or reference time depending upon context
- //
- // This class provides simple arithmetic operations on reference times
- //
- // keep non-virtual otherwise the data layout will not be the same as
- // REFERENCE_TIME
- // -----
- // note that you are safe to cast a CRefTime* to a REFERENCE_TIME*, but
- // you will need to do so explicitly
- // -----
- type
- TBCRefTime = object
- public
- // *MUST* be the only data member so that this class is exactly
- // equivalent to a REFERENCE_TIME.
- // Also, must be *no virtual functions*
- FTime: TReferenceTime;
- // DCODER: using Create_ as contructor replacement ...
- procedure Create_; overload;
- procedure Create_(msecs: Longint); overload;
- // delphi 5 doesn't like "const rt: TBCRefTime" ???
- function SetTime(var rt: TBCRefTime): TBCRefTime; overload;
- function SetTime(var ll: LONGLONG): TBCRefTime; overload;
- function AddTime(var rt: TBCRefTime): TBCRefTime; overload;
- function SubstractTime(var rt: TBCRefTime): TBCRefTime; overload;
- function Millisecs: Longint;
- function GetUnits: LONGLONG;
- end;
- // milenko end;
- // milenko start schedule implementation
- //------------------------------------------------------------------------------
- // File: Schedule.cpp
- //
- // Desc: DirectShow base classes.
- //
- // Copyright (c) 1996-2002 Microsoft Corporation. All rights reserved.
- //------------------------------------------------------------------------------
- type
- TBCAdvisePacket = class
- public
- FNext : TBCAdvisePacket;
- FAdviseCookie: DWORD;
- FEventTime : TReferenceTime; // Time at which event should be set
- FPeriod : TReferenceTime; // Periodic time
- FNotify : THandle; // Handle to event or semephore
- FPeriodic : Boolean; // TRUE => Periodic event
- constructor Create; overload;
- constructor Create(Next: TBCAdvisePacket; Time: LONGLONG); overload;
- procedure InsertAfter(Packet: TBCAdvisePacket);
- // That is, is it the node that represents the end of the list
- function IsZ: Boolean;
- function RemoveNext: TBCAdvisePacket;
- procedure DeleteNext;
- function Next: TBCAdvisePacket;
- function Cookie: DWORD;
- end;
- TBCAMSchedule = class(TBCBaseObject)
- private
- // Structure is:
- // head -> elmt1 -> elmt2 -> z -> null
- // So an empty list is: head -> z -> null
- // Having head & z as links makes insertaion,
- // deletion and shunting much easier.
- FHead,
- FZ : TBCAdvisePacket; // z is both a tail and a sentry
- FNextCookie : DWORD; // Strictly increasing
- FAdviseCount: DWORD; // Number of elements on list
- FSerialize : TBCCritSec;
- // Event that we should set if the packed added above will be the next to fire.
- FEvent : THandle;
- // Rather than delete advise packets, we cache them for future use
- FAdviseCache: TBCAdvisePacket;
- FCacheCount : DWORD;
- // AddAdvisePacket: adds the packet, returns the cookie (0 if failed)
- function AddAdvisePacket(Packet: TBCAdvisePacket): DWORD; overload;
- // A Shunt is where we have changed the first element in the
- // list and want it re-evaluating (i.e. repositioned) in
- // the list.
- procedure ShuntHead;
- procedure Delete(Packet: TBCAdvisePacket);// This "Delete" will cache the Link
- public
- // ev is the event we should fire if the advise time needs re-evaluating
- constructor Create(Event: THandle);
- destructor Destroy; override;
- function GetAdviseCount: DWORD;
- function GetNextAdviseTime: TReferenceTime;
- // We need a method for derived classes to add advise packets, we return the cookie
- function AddAdvisePacket(const Time1, Time2: TReferenceTime; h: THandle;
- Periodic: Boolean): DWORD; overload;
- // And a way to cancel
- function Unadvise(AdviseCookie: DWORD): HRESULT;
- // Tell us the time please, and we'll dispatch the expired events.
- // We return the time of the next event.
- // NB: The time returned will be "useless" if you start adding extra Advises.
- // But that's the problem of
- // whoever is using this helper class (typically a clock).
- function Advise(const Time_: TReferenceTime): TReferenceTime;
- // Get the event handle which will be set if advise time requires re-evaluation.
- function GetEvent: THandle;
- procedure DumpLinkedList;
- end;
- // milenko end
- // milenko start refclock implementation
- //------------------------------------------------------------------------------
- // File: RefClock.h
- //
- // Desc: DirectShow base classes - defines the IReferenceClock interface.
- //
- // Copyright (c) 1992-2002 Microsoft Corporation. All rights reserved.
- //------------------------------------------------------------------------------
- (* This class hierarchy will support an IReferenceClock interface so
- that an audio card (or other externally driven clock) can update the
- system wide clock that everyone uses.
- The interface will be pretty thin with probably just one update method
- This interface has not yet been defined.
- *)
- (* This abstract base class implements the IReferenceClock
- * interface. Classes that actually provide clock signals (from
- * whatever source) have to be derived from this class.
- *
- * The abstract class provides implementations for:
- * CUnknown support
- * locking support (CCritSec)
- * client advise code (creates a thread)
- *
- * Question: what can we do about quality? Change the timer
- * resolution to lower the system load? Up the priority of the
- * timer thread to force more responsive signals?
- *
- * During class construction we create a worker thread that is destroyed during
- * destuction. This thread executes a series of WaitForSingleObject calls,
- * waking up when a command is given to the thread or the next wake up point
- * is reached. The wakeup points are determined by clients making Advise
- * calls.
- *
- * Each advise call defines a point in time when they wish to be notified. A
- * periodic advise is a series of these such events. We maintain a list of
- * advise links and calculate when the nearest event notification is due for.
- * We then call WaitForSingleObject with a timeout equal to this time. The
- * handle we wait on is used by the class to signal that something has changed
- * and that we must reschedule the next event. This typically happens when
- * someone comes in and asks for an advise link while we are waiting for an
- * event to timeout.
- *
- * While we are modifying the list of advise requests we
- * are protected from interference through a critical section. Clients are NOT
- * advised through callbacks. One shot clients have an event set, while
- * periodic clients have a semaphore released for each event notification. A
- * semaphore allows a client to be kept up to date with the number of events
- * actually triggered and be assured that they can't miss multiple events being
- * set.
- *
- * Keeping track of advises is taken care of by the CAMSchedule class.
- *)
- type
- TBCBaseReferenceClock = class(TBCUnknown, IReferenceClock)
- private
- FLock : TBCCritSec;
- FAbort : Boolean; // Flag used for thread shutdown
- FThread : THandle; // Thread handle
- FPrivateTime : TReferenceTime; // Current best estimate of time
- FPrevSystemTime : DWORD; // Last vaule we got from timeGetTime
- FLastGotTime : TReferenceTime; // Last time returned by GetTime
- FNextAdvise : TReferenceTime; // Time of next advise
- FTimerResolution: DWORD;
- {$IFDEF PERF}
- FGetSystemTime : integer;
- {$ENDIF}
- function AdviseThread: HRESULT; // Method in which the advise thread runs
- protected
- FSchedule : TBCAMSchedule;
- public
- constructor Create(Name: String; Unk: IUnknown; out hr: HRESULT; Sched:
- TBCAMSchedule = nil);
- destructor Destroy; override; // Don't let me be created on the stack!
- function NonDelegatingQueryInterface(const IID: TGUID; out Obj): HResult; override; stdcall;
- // IReferenceClock methods
- // Derived classes must implement GetPrivateTime(). All our GetTime
- // does is call GetPrivateTime and then check so that time does not
- // go backwards. A return code of S_FALSE implies that the internal
- // clock has gone backwards and GetTime time has halted until internal
- // time has caught up. (Don't know if this will be much use to folk,
- // but it seems odd not to use the return code for something useful.)
- function GetTime(out Time: int64): HResult; stdcall;
- // When this is called, it sets m_rtLastGotTime to the time it returns.
- // Provide standard mechanisms for scheduling events
- // Ask for an async notification that a time has elapsed */
- function AdviseTime(
- BaseTime, // base reference time
- StreamTime: int64; // stream offset time
- Event: THandle; // advise via this event
- out AdviseCookie: DWORD // where your cookie goes
- ): HResult; stdcall;
- // Ask for an asynchronous periodic notification that a time has elapsed
- function AdvisePeriodic(
- const StartTime, // starting at this time
- PeriodTime: int64; // time between notifications
- Semaphore: THandle; // advise via a semaphore
- out AdviseCookie: DWORD // where your cookie goes
- ): HResult; stdcall;
- (* Cancel a request for notification(s) - if the notification was
- * a one shot timer then this function doesn't need to be called
- * as the advise is automatically cancelled, however it does no
- * harm to explicitly cancel a one-shot advise. It is REQUIRED that
- * clients call Unadvise to clear a Periodic advise setting.
- *)
- function Unadvise(AdviseCookie: DWORD): HResult; stdcall;
- // Methods for the benefit of derived classes or outer objects
- // GetPrivateTime() is the REAL clock. GetTime is just a cover for
- // it. Derived classes will probably override this method but not
- // GetTime() itself.
- // The important point about GetPrivateTime() is it's allowed to go
- // backwards. Our GetTime() will keep returning the LastGotTime
- // until GetPrivateTime() catches up.
- function GetPrivateTime: TReferenceTime; virtual;
- // Provide a method for correcting drift
- function SetTimeDelta(const TimeDelta: TReferenceTime): HRESULT; stdcall;
- function GetSchedule: TBCAMSchedule;
- // Thread stuff
- // Wakes thread up. Need to do this if time to next advise needs reevaluating.
- procedure TriggerThread;
- end;
- // milenko end
- // milenko start sysclock implementation
- //------------------------------------------------------------------------------
- // File: SysClock.h
- //
- // Desc: DirectShow base classes - defines a system clock implementation of
- // IReferenceClock.
- //
- // Copyright (c) 1992-2002 Microsoft Corporation. All rights reserved.
- //------------------------------------------------------------------------------
- const
- IID_IPersist : TGUID = '{0000010C-0000-0000-C000-000000000046}';
- type
- TBCSystemClock = class(TBCBaseReferenceClock, IAMClockAdjust, IPersist)
- public
- constructor Create(Name: WideString; Unk : IUnknown; out hr : HRESULT);
- function NonDelegatingQueryInterface(const IID: TGUID; out Obj): HResult; override; stdcall;
- // Yield up our class id so that we can be persisted
- // Implement required Ipersist method
- function GetClassID(out classID: TCLSID): HResult; stdcall;
- // IAMClockAdjust methods
- function SetClockDelta(rtDelta: TReferenceTime): HResult; stdcall;
- end;
- {$IFDEF DEBUG}
- procedure DbgLog(obj: TBCBaseObJect; const msg: string); overload;
- procedure DbgLog(const msg: string); overload;
- procedure DbgAssert(const Message, Filename: string; LineNumber: Integer;
- ErrorAddr: Pointer);
- {$ENDIF}
- function DllGetClassObject(const CLSID, IID: TGUID; var Obj): HResult; stdcall;
- function DllCanUnloadNow: HResult; stdcall;
- function DllRegisterServer: HResult; stdcall;
- function DllUnregisterServer: HResult; stdcall;
- (* milenko start (needed for TBCBaseReferenceClock and TBCVideoTransformFilter ) *)
- {$IFDEF PERF}
- procedure MSR_START(Id_: Integer);
- procedure MSR_STOP(Id_: Integer);
- procedure MSR_INTEGER(Id_, i: Integer);
- function MSR_REGISTER(s: String): Integer;
- {$ENDIF}
- (* milenko end *)
- implementation
- var
- ObjectCount : Integer;
- FactoryCount : Integer;
- TemplatesVar : TBCFilterTemplate;
- // milenko start (added global variables instead of local constants)
- IsCheckedVersion: Bool = False;
- IsTimeKillSynchronousFlagAvailable: Bool = False;
- MsgId: Cardinal = 0;
- // milenko end
- {$IFDEF DEBUG}
- {$IFNDEF MESSAGE}
- DebugFile : TextFile;
- {$ENDIF}
- procedure DbgLog(obj: TBCBaseObJect; const msg: string);
- begin
- {$IFNDEF MESSAGE}
- if (obj = nil) then
- Writeln(DebugFile, TimeToStr(time) +' > '+ msg) else
- Writeln(DebugFile, TimeToStr(time) +' > '+ format('Object: %s, msg: %s.',[obj.FName, msg]));
- Flush(DebugFile);
- {$ELSE}
- if (obj = nil) then OutputDebugString(PChar(TimeToStr(time) +' > '+ msg)) else
- OutputDebugString(PChar(TimeToStr(time) +' > '+ format('Object: %s, msg: %s.',[obj.FName, msg])));
- {$ENDIF}
- end;
- procedure DbgLog(const msg: string); overload;
- begin
- {$IFNDEF MESSAGE}
- Writeln(DebugFile, TimeToStr(time) +' > '+ msg);
- Flush(DebugFile);
- {$ELSE}
- OutputDebugString(PChar(TimeToStr(time) +' > '+ msg));
- {$ENDIF}
- end;
- procedure DbgAssert(const Message, Filename: string; LineNumber: Integer;
- ErrorAddr: Pointer);
- begin
- DbgLog(format('[ASSERT] %s (%s) line: %d, adr: $%x',
- [Message, Filename, LineNumber, Integer(ErrorAddr)]));
- end;
- {$ENDIF}
- // -----------------------------------------------------------------------------
- // TBCMediaType
- // -----------------------------------------------------------------------------
- function TBCMediaType.Equal(mt: TBCMediaType): boolean;
- begin
- result := ((IsEqualGUID(Mediatype.majortype,mt.MediaType.majortype) = True) and
- (IsEqualGUID(Mediatype.subtype,mt.MediaType.subtype) = True) and
- (IsEqualGUID(Mediatype.formattype,mt.MediaType.formattype) = True) and
- (Mediatype.cbFormat = mt.MediaType.cbFormat) and
- ( (Mediatype.cbFormat = 0) or
- (CompareMem(Mediatype.pbFormat, mt.MediaType.pbFormat, Mediatype.cbFormat))));
- end;
- function TBCMediaType.Equal(mt: PAMMediaType): boolean;
- begin
- result := ((IsEqualGUID(Mediatype.majortype,mt.majortype) = True) and
- (IsEqualGUID(Mediatype.subtype,mt.subtype) = True) and
- (IsEqualGUID(Mediatype.formattype,mt.formattype) = True) and
- (Mediatype.cbFormat = mt.cbFormat) and
- ( (Mediatype.cbFormat = 0) or
- (CompareMem(Mediatype.pbFormat, mt.pbFormat, Mediatype.cbFormat))));
- end;
- function TBCMediaType.MatchesPartial(Partial: PAMMediaType): boolean;
- begin
- result := false;
- if (not IsEqualGUID(partial.majortype, GUID_NULL) and
- not IsEqualGUID(MediaType.majortype, partial.majortype)) then exit;
- if (not IsEqualGUID(partial.subtype, GUID_NULL) and
- not IsEqualGUID(MediaType.subtype, partial.subtype)) then exit;
- if not IsEqualGUID(partial.formattype, GUID_NULL) then
- begin
- if not IsEqualGUID(MediaType.formattype, partial.formattype) then exit;
- if (MediaType.cbFormat <> partial.cbFormat) then exit;
- if ((MediaType.cbFormat <> 0) and
- (CompareMem(MediaType.pbFormat, partial.pbFormat, MediaType.cbFormat) <> false)) then exit;
- end;
- result := True;
- end;
- function TBCMediaType.IsPartiallySpecified: boolean;
- begin
- if (IsEqualGUID(Mediatype.majortype, GUID_NULL) or
- IsEqualGUID(Mediatype.formattype, GUID_NULL)) then result := True
- else result := false;
- end;
- function TBCMediaType.IsValid: boolean;
- begin
- result := not IsEqualGUID(MediaType.majortype,GUID_NULL);
- end;
- procedure TBCMediaType.InitMediaType;
- begin
- ZeroMemory(MediaType, sizeof(TAMMediaType));
- MediaType.lSampleSize := 1;
- MediaType.bFixedSizeSamples := True;
- end;
- function TBCMediaType.FormatLength: Cardinal;
- begin
- result := MediaType.cbFormat
- end;
- // -----------------------------------------------------------------------------
- // milenko start
- function AMGetWideString(Source: WideString; out Dest: PWideChar): HRESULT;
- var NameLen: Cardinal;
- begin
- if not assigned(@Dest) then
- begin
- Result := E_POINTER;
- Exit;
- end;
- nameLen := sizeof(WCHAR) * (length(source)+1);
- Dest := CoTaskMemAlloc(nameLen);
- if (Dest = nil) then
- begin
- Result := E_OUTOFMEMORY;
- Exit;
- end;
- CopyMemory(Dest, PWideChar(Source), nameLen);
- Result := NOERROR;
- end;
- {
- function AMGetWideString(Source: WideString; out Dest: PWideChar): HRESULT;
- type TWideCharArray = array of WideChar;
- var NameLen: Cardinal;
- begin
- if Source = '' then
- begin
- dest := nil;
- result := S_OK;
- exit;
- end;
- assert(@dest <> nil);
- nameLen := (length(Source)+1)*2;
- Dest := CoTaskMemAlloc(nameLen);
- if(Dest = nil) then
- begin
- result := E_OUTOFMEMORY;
- exit;
- end;
- CopyMemory(dest, pointer(Source), nameLen-1);
- TWideCharArray(dest)[(nameLen div 2)-1] := #0;
- result := NOERROR;
- end;
- }
- // milenko end
- // -----------------------------------------------------------------------------
- function CreateMemoryAllocator(out Allocator: IMemAllocator): HRESULT;
- begin
- result := CoCreateInstance(CLSID_MemoryAllocator, nil, CLSCTX_INPROC_SERVER,
- IID_IMemAllocator, Allocator);
- end;
- // Put this one here rather than in ctlutil.cpp to avoid linking
- // anything brought in by ctlutil.cpp
- function CreatePosPassThru(Agg: IUnknown; Renderer: boolean; Pin: IPin; out PassThru: IUnknown): HRESULT; stdcall;
- var
- UnkSeek: IUnknown;
- APassThru: ISeekingPassThru;
- begin
- PassThru := nil;
- result := CoCreateInstance(CLSID_SeekingPassThru, Agg, CLSCTX_INPROC_SERVER,
- IUnknown, UnkSeek);
- if FAILED(result) then exit;
- result := UnkSeek.QueryInterface(IID_ISeekingPassThru, APassThru);
- if FAILED(result) then
- begin
- UnkSeek := nil;
- exit;
- end;
- result := APassThru.Init(Renderer, Pin);
- APassThru := nil;
- if FAILED(result) then
- begin
- UnkSeek := nil;
- exit;
- end;
- PassThru := UnkSeek;
- result := S_OK;
- end;
- // -----------------------------------------------------------------------------
- function Templates: TBCFilterTemplate;
- begin
- if TemplatesVar = nil then TemplatesVar := TBCFilterTemplate.Create;
- result := TemplatesVar;
- end;
- function DllGetClassObject(const CLSID, IID: TGUID; var Obj): HResult; stdcall;
- var
- Factory: TBCClassFactory;
- begin
- Factory := Templates.GetFactoryFromClassID(CLSID);
- if Factory <> nil then
- if Factory.GetInterface(IID, Obj) then
- Result := S_OK
- else
- Result := E_NOINTERFACE
- else
- begin
- Pointer(Obj) := nil;
- Result := CLASS_E_CLASSNOTAVAILABLE;
- end;
- end;
- function DllCanUnloadNow: HResult; stdcall;
- begin
- if (ObjectCount = 0) and (FactoryCount = 0) then
- result := S_OK else result := S_FALSE;;
- end;
- function DllRegisterServer: HResult; stdcall;
- begin
- if Templates.RegisterServer(True) then result := S_OK else result := E_FAIL;
- end;
- function DllUnregisterServer: HResult; stdcall;
- begin
- if Templates.RegisterServer(false) then result := S_OK else result := E_FAIL;
- end;
- { TBCClassFactory }
- constructor TBCClassFactory.CreateFilter(ComClass: TBCUnknownClass; Name: string;
- const ClassID: TGUID; const Category: TGUID; Merit: LongWord;
- PinCount: Cardinal; Pins: PRegFilterPins);
- begin
- Templates.AddObjectFactory(Self);
- FComClass := ComClass;
- FName := Name;
- FClassID := ClassID;
- FCategory := Category;
- FMerit := Merit;
- FPinCount := PinCount;
- FPins := Pins;
- end;
- constructor TBCClassFactory.CreatePropertyPage(ComClass: TFormPropertyPageClass; const ClassID: TGUID);
- begin
- Templates.AddObjectFactory(Self);
- FPropClass := ComClass;
- FClassID := ClassID;
- FCategory := ClassID;
- end;
- function TBCClassFactory.CreateInstance(const unkOuter: IUnKnown;
- const iid: TIID; out obj): HResult;
- var
- ComObject: TBCUnknown;
- PropObject: TFormPropertyPage;
- begin
- if @obj = nil then
- begin
- Result := E_POINTER;
- Exit;
- end;
- Pointer(obj) := nil;
- if FPropClass <> nil then
- begin
- PropObject := TFormPropertyPageClass(FPropClass).Create(nil);
- PropObject.FPropertyPage := TBCBasePropertyPage.Create('',nil, PropObject);
- Result := PropObject.QueryInterface(IID, obj);
- end
- else
- begin
- ComObject := TBCUnknownClass(FComClass).CreateFromFactory(self, unkOuter);
- Result := ComObject.QueryInterface(IID, obj);
- if ComObject.FRefCount = 0 then ComObject.Free;
- end;
- end;
- procedure TBCClassFactory.UpdateRegistry(Register: Boolean);
- var
- FileName: array[0..MAX_PATH-1] of Char;
- ClassID, ServerKeyName: String;
- begin
- ClassID := GUIDToString(FClassID);
- ServerKeyName := 'CLSID\' + ClassID + '\' + 'InprocServer32';
- if Register then
- begin
- CreateRegKey('CLSID\' + ClassID, '', FName);
- GetModuleFileName(hinstance, FileName, MAX_PATH);
- CreateRegKey(ServerKeyName, '', FileName);
- CreateRegKey(ServerKeyName, 'ThreadingModel', 'Both');
- end else
- begin
- DeleteRegKey(ServerKeyName);
- DeleteRegKey('CLSID\' + ClassID);
- end;
- end;
- function TBCClassFactory.RegisterFilter(FilterMapper: IFilterMapper; Register: Boolean): boolean;
- type
- TDynArrayPins = array of TRegFilterPins;
- TDynArrayPinType = array of TRegPinTypes;
- var
- i, j: integer;
- FilterGUID: TGUID;
- begin
- result := Succeeded(FilterMapper.UnregisterFilter(FClassID));
- if Register then
- begin
- result := Succeeded(FilterMapper.RegisterFilter(FClassID, StringToOleStr(FName), FMerit));
- if result then
- begin
- for i := 0 to FPinCount - 1 do
- begin
- if TDynArrayPins(FPins)[i].oFilter = nil then
- FilterGUID := GUID_NULL else
- FilterGUID := TDynArrayPins(FPins)[i].oFilter^;
- result := Succeeded(FilterMapper.RegisterPin(FClassID,
- TDynArrayPins(FPins)[i].strName,
- TDynArrayPins(FPins)[i].bRendered,
- TDynArrayPins(FPins)[i].bOutput,
- TDynArrayPins(FPins)[i].bZero,
- TDynArrayPins(FPins)[i].bMany,
- FilterGUID,
- TDynArrayPins(FPins)[i].strConnectsToPin));
- if result then
- begin
- for j := 0 to TDynArrayPins(FPins)[i].nMediaTypes - 1 do
- begin
- result := Succeeded(FilterMapper.RegisterPinType(FClassID,
- TDynArrayPins(FPins)[i].strName,
- TDynArrayPinType(TDynArrayPins(FPins)[i].lpMediaType)[j].clsMajorType^,
- TDynArrayPinType(TDynArrayPins(FPins)[i].lpMediaType)[j].clsMinorType^));
- if not result then break;
- end;
- if not result then break;
- end;
- if not result then break;
- end;
- end;
- end;
- end;
- function TBCClassFactory.RegisterFilter(FilterMapper: IFilterMapper2; Register: Boolean): boolean;
- var
- RegFilter: TRegFilter2;
- begin
- result := Succeeded(FilterMapper.UnregisterFilter(FCategory, nil, FClassID));
- // milenko start (bugfix for Windows 98)
- // Windows 98 fails when unregistering a Property Page, so the whole
- // DLLUnregisterServer function fails without unregistering the Filter.
- if not result and not Register and (FName = '') then Result := True;
- // milenko end
- if Register then
- begin
- RegFilter.dwVersion := 1;
- RegFilter.dwMerit := FMerit;
- RegFilter.cPins := FPinCount;
- RegFilter.rgPins := FPins;
- result := Succeeded(FilterMapper.RegisterFilter(FClassID, PWideChar(WideString(FName)),
- nil, @FCategory, nil, RegFilter));
- end;
- end;
- function TBCClassFactory._AddRef: Integer;
- begin
- result := InterlockedIncrement(FactoryCount);
- end;
- function TBCClassFactory._Release: Integer;
- begin
- result := InterlockedDecrement(FactoryCount);
- end;
- function TBCClassFactory.LockServer(fLock: BOOL): HResult;
- begin
- Result := CoLockObjectExternal(Self, fLock, True);
- if flock then InterlockedIncrement(ObjectCount)
- else InterlockedDecrement(ObjectCount);
- end;
- function TBCClassFactory.QueryInterface(const IID: TGUID; out Obj): HResult;
- begin
- if GetInterface(IID, Obj) then Result := S_OK else Result := E_NOINTERFACE;
- end;
- { TBCFilterTemplate }
- procedure TBCFilterTemplate.AddObjectFactory(Factory: TBCClassFactory);
- begin
- Factory.FNext := FFactoryList;
- FFactoryList := Factory;
- end;
- constructor TBCFilterTemplate.Create;
- begin
- FFactoryList := nil;
- end;
- destructor TBCFilterTemplate.Destroy;
- var AFactory: TBCClassFactory;
- begin
- while FFactoryList <> nil do
- begin
- AFactory := FFactoryList;
- FFactoryList := AFactory.FNext;
- AFactory.Free;
- end;
- inherited Destroy;
- end;
- function TBCFilterTemplate.GetFactoryFromClassID(const CLSID: TGUID): TBCClassFactory;
- var AFactory: TBCClassFactory;
- begin
- result := nil;
- AFactory := FFactoryList;
- while AFactory <> nil do
- begin
- if IsEqualGUID(CLSID, AFactory.FClassID) then
- begin
- result := AFactory;
- break;
- end;
- AFactory := AFactory.FNext;
- end;
- end;
- function TBCFilterTemplate.RegisterServer(Register: Boolean): boolean;
- var
- {$IFDEF DEBUG}
- Filename: array[0..MAX_PATH-1] of Char;
- {$ENDIF}
- FilterMapper : IFilterMapper;
- FilterMapper2: IFilterMapper2;
- Factory: TBCClassFactory;
- begin
- result := false;
- {$IFDEF DEBUG}
- GetModuleFileName(hinstance, Filename, sizeof(Filename));
- DbgLog('TBCFilterTemplate.RegisterServer in ' + Filename);
- {$ENDIF}
- if Failed(CoCreateInstance(CLSID_FilterMapper2, nil, CLSCTX_INPROC_SERVER, IFilterMapper2, FilterMapper2)) then
- if Failed(CoCreateInstance(CLSID_FilterMapper, nil, CLSCTX_INPROC_SERVER, IFilterMapper, FilterMapper)) then exit;
- Factory := FFactoryList;
- while Factory <> nil do
- begin
- Factory.UpdateRegistry(false);
- if FilterMapper2 <> nil then
- result := Factory.RegisterFilter(FilterMapper2, Register)
- else result := Factory.RegisterFilter(FilterMapper, Register);
- if not result then break else Factory.UpdateRegistry(register);
- Factory := Factory.FNext;
- end;
- FilterMapper := nil;
- FilterMapper2 := nil;
- end;
- { TBCBaseObject }
- constructor TBCBaseObject.Create(Name: string);
- begin
- {$IFDEF DEBUG}
- DbgLog('[' + ClassName + ': ' + Name + '] CREATE');
- {$ENDIF}
- FName := name;
- end;
- destructor TBCBaseObject.Destroy;
- begin
- {$IFDEF DEBUG}
- DbgLog('[' + ClassName + ': ' + FName + '] FREE');
- {$ENDIF}
- inherited;
- end;
- procedure TBCBaseObject.FreeInstance;
- begin
- inherited;
- InterlockedDecrement(ObjectCount);
- end;
- class function TBCBaseObject.NewInstance: TObject;
- begin
- result := inherited NewInstance;
- InterlockedIncrement(ObjectCount);
- end;
- class function TBCBaseObject.ObjectsActive: integer;
- begin
- result := ObjectCount;
- end;
- { TBCUnknown }
- function TBCUnknown.QueryInterface(const IID: TGUID; out Obj): HResult;
- begin
- if FOwner <> nil then
- Result := IUnknown(FOwner).QueryInterface(IID, Obj)
- else
- Result := NonDelegatingQueryInterface(IID, Obj);
- end;
- function TBCUnknown._AddRef: Integer;
- begin
- if FOwner <> nil then
- Result := IUnknown(FOwner)._AddRef else
- Result := NonDelegatingAddRef;
- end;
- function TBCUnknown._Release: Integer;
- begin
- if FOwner <> nil then
- Result := IUnknown(FOwner)._Release else
- Result := NonDelegatingRelease;
- end;
- function TBCUnknown.NonDelegatingQueryInterface(const IID: TGUID;
- out Obj): HResult;
- begin
- if GetInterface(IID, Obj) then Result := S_OK else Result := E_NOINTERFACE;
- end;
- function TBCUnknown.NonDelegatingAddRef: Integer;
- begin
- Result := InterlockedIncrement(FRefCount);
- end;
- function TBCUnknown.NonDelegatingRelease: Integer;
- begin
- Result := InterlockedDecrement(FRefCount);
- if Result = 0 then Destroy;
- end;
- function TBCUnknown.GetOwner: IUnKnown;
- begin
- result := IUnKnown(FOwner);
- end;
- constructor TBCUnknown.Create(name: string; Unk: IUnKnown);
- begin
- inherited Create(name);
- FOwner := Pointer(Unk);
- end;
- constructor TBCUnknown.CreateFromFactory(Factory: TBCClassFactory;
- const Controller: IUnKnown);
- begin
- Create(Factory.FName, Controller);
- end;
- { TBCBaseFilter }
- constructor TBCBaseFilter.Create(Name: string; Unk: IUnKnown;
- Lock: TBCCritSec; const clsid: TGUID);
- begin
- inherited Create(Name, Unk);
- FLock := Lock;
- Fclsid := clsid;
- FState := State_Stopped;
- FClock := nil;
- FGraph := nil;
- FSink := nil;
- FFilterName := '';
- FPinVersion := 1;
- Assert(FLock <> nil, 'Lock = nil !');
- end;
- constructor TBCBaseFilter.Create(Name: string; Unk: IUnKnown;
- Lock: TBCCritSec; const clsid: TGUID; out hr: HRESULT);
- begin
- Create(Name, Unk, Lock, clsid);
- assert(@hr <> nil, 'Unreferenced parameter: hr');
- end;
- constructor TBCBaseFilter.CreateFromFactory(Factory: TBCClassFactory; const Controller: IUnknown);
- begin
- Create(Factory.FName,Controller, TBCCritSec.Create, Factory.FClassID);
- end;
- destructor TBCBaseFilter.destroy;
- begin
- FFilterName := '';
- FClock := nil;
- FLock.Free;
- inherited;
- end;
- function TBCBaseFilter.EnumPins(out ppEnum: IEnumPins): HRESULT;
- begin
- // Create a new ref counted enumerator
- ppEnum := TBCEnumPins.Create(self, nil);
- if ppEnum = nil then result := E_OUTOFMEMORY else result := NOERROR;
- end;
- function TBCBaseFilter.FindPin(Id: PWideChar; out Pin: IPin): HRESULT;
- var
- i: integer;
- APin: TBCBasePin;
- begin
- // We're going to search the pin list so maintain integrity
- FLock.Lock;
- try
- for i := 0 to GetPinCount - 1 do
- begin
- APin := GetPin(i);
- ASSERT(APin <> nil);
- if (APin.FPinName = WideString(Id)) then
- begin
- // Found one that matches
- // AddRef() and return it
- Pin := APin;
- result := S_OK;
- exit;
- end;
- end;
- Pin := nil;
- result := VFW_E_NOT_FOUND;
- finally
- FLock.UnLock;
- end;
- end;
- function TBCBaseFilter.GetClassID(out classID: TCLSID): HResult;
- begin
- classID := FCLSID;
- result := NOERROR;
- end;
- function TBCBaseFilter.GetFilterGraph: IFilterGraph;
- begin
- result := FGRaph;
- end;
- function TBCBaseFilter.GetPinVersion: LongInt;
- begin
- result := FPinVersion;
- end;
- function TBCBaseFilter.GetState(dwMilliSecsTimeout: DWORD;
- out State: TFilterState): HRESULT;
- begin
- State := FState;
- result := S_OK;
- end;
- function TBCBaseFilter.GetSyncSource(out pClock: IReferenceClock): HRESULT;
- begin
- FLock.Lock;
- try
- pClock := FClock;
- finally
- result := NOERROR;
- FLock.UnLock;
- end;
- end;
- procedure TBCBaseFilter.IncrementPinVersion;
- begin
- InterlockedIncrement(FPinVersion)
- end;
- function TBCBaseFilter.IsActive: boolean;
- begin
- FLock.Lock;
- try
- result := ((FState = State_Paused) or (FState = State_Running));
- finally
- FLock.UnLock;
- end;
- end;
- function TBCBaseFilter.IsStopped: boolean;
- begin
- result := (FState = State_Stopped);
- end;
- function TBCBaseFilter.JoinFilterGraph(pGraph: IFilterGraph;
- pName: PWideChar): HRESULT;
- begin
- FLock.Lock;
- try
- //Henri: This implementation seem to be stupid but it's the exact conversion ?????
- // NOTE: we no longer hold references on the graph (m_pGraph, m_pSink)
- Pointer(FGraph) := Pointer(pGraph);
- if (FGraph <> nil) then
- begin
- if FAILED(FGraph.QueryInterface(IID_IMediaEventSink, FSink)) then
- ASSERT(FSink = nil)
- else FSink._Release; // we do NOT keep a reference on it.
- end
- else
- begin
- // if graph pointer is null, then we should
- // also release the IMediaEventSink on the same object - we don't
- // refcount it, so just set it to null
- Pointer(FSink) := nil;
- end;
- FFilterName := '';
- if assigned(pName) then FFilterName := WideString(pName);
- result := NOERROR;
- finally
- FLock.UnLock;
- end;
- end;
- function TBCBaseFilter.NotifyEvent(EventCode, EventParam1,
- EventParam2: Integer): HRESULT;
- var
- Filter : IBaseFilter;
- begin
- // Snapshot so we don't have to lock up
- if assigned(FSink) then
- begin
- QueryInterface(IID_IBaseFilter,Filter);
- if (EC_COMPLETE = EventCode) then EventParam2 := LongInt(Filter);
- result := FSink.Notify(EventCode, EventParam1, EventParam2);
- Filter := nil;
- end
- else
- result := E_NOTIMPL;
- end;
- function TBCBaseFilter.Pause: HRESULT;
- var
- c: integer;
- pin: TBCBasePin;
- begin
- FLock.Lock;
- try
- if FState = State_Stopped then
- begin
- for c := 0 to GetPinCount - 1 do
- begin
- Pin := GetPin(c);
- // Disconnected pins are not activated - this saves pins
- // worrying about this state themselves
- if Pin.IsConnected then
- begin
- result := Pin.Active;
- if FAILED(result) then exit;
- end;
- end;
- end;
- // notify all pins of the change to active state
- FState := State_Paused;
- result := S_OK;
- finally
- FLock.UnLock;
- end;
- end;
- function TBCBaseFilter.QueryFilterInfo(out pInfo: TFilterInfo): HRESULT;
- var
- len: Integer;
- begin
- len := Length(pInfo.achName)-1;
- if (Length(FFilterName) > 0) then
- if (Length(FFilterName) > len) then
- begin
- CopyMemory(@pInfo.achName, PWideChar(FFilterName), len * SizeOf(WCHAR));
- pInfo.achName[len] := #0;
- end
- else
- CopyMemory(@pInfo.achName, PWideChar(FFilterName), (Length(FFilterName)+1) * SizeOf(WCHAR))
- else
- pInfo.achName[0] := #0;
- pInfo.pGraph := FGraph;
- result := NOERROR;
- end;
- function TBCBaseFilter.QueryVendorInfo(out pVendorInfo: PWideChar): HRESULT;
- begin
- result := E_NOTIMPL;
- end;
- function TBCBaseFilter.ReconnectPin(Pin: IPin; pmt: PAMMediaType): HRESULT;
- var Graph2: IFilterGraph2;
- begin
- if (FGraph <> nil) then
- begin
- result := FGraph.QueryInterface(IID_IFilterGraph2, Graph2);
- if Succeeded(result) then
- begin
- result := Graph2.ReconnectEx(Pin, pmt);
- Graph2 := nil;
- end
- else
- result := FGraph.Reconnect(Pin);
- end
- else
- result := E_NOINTERFACE;
- end;
- function TBCBaseFilter.Register: HRESULT;
- var
- {$IFDEF DEBUG}
- Filename: array[0..MAX_PATH-1] of Char;
- {$ENDIF}
- FilterMapper : IFilterMapper;
- FilterMapper2: IFilterMapper2;
- Factory: TBCClassFactory;
- AResult : boolean;
- begin
- Aresult := false;
- Result := S_FALSE;
- Factory := Templates.GetFactoryFromClassID(FCLSID);
- if Factory <> nil then
- begin
- {$IFDEF DEBUG}
- GetModuleFileName(hinstance, Filename, sizeof(Filename));
- DbgLog(Self,'Register in ' + Filename);
- {$ENDIF}
- if Failed(CoCreateInstance(CLSID_FilterMapper2, nil, CLSCTX_INPROC_SERVER, IFilterMapper2, FilterMapper2)) then
- if Failed(CoCreateInstance(CLSID_FilterMapper, nil, CLSCTX_INPROC_SERVER, IFilterMapper, FilterMapper)) then exit;
- Factory.UpdateRegistry(false);
- if FilterMapper2 <> nil then
- AResult := Factory.RegisterFilter(FilterMapper2, True)
- else AResult := Factory.RegisterFilter(FilterMapper, True);
- if Aresult then Factory.UpdateRegistry(True);
- FilterMapper := nil;
- FilterMapper2 := nil;
- end;
- if AResult then result := S_OK else result := S_False;
- end;
- function TBCBaseFilter.Run(tStart: TReferenceTime): HRESULT;
- var
- c: integer;
- Pin: TBCBasePin;
- begin
- FLock.Lock;
- try
- // remember the stream time offset
- FStart := tStart;
- if FState = State_Stopped then
- begin
- result := Pause;
- if FAILED(result) then exit;
- end;
- // notify all pins of the change to active state
- if (FState <> State_Running) then
- begin
- for c := 0 to GetPinCount - 1 do
- begin
- Pin := GetPin(c);
- // Disconnected pins are not activated - this saves pins
- // worrying about this state themselves
- if Pin.IsConnected then
- begin
- result := Pin.Run(tStart);
- if FAILED(result) then exit;
- end;
- end;
- end;
- FState := State_Running;
- result := S_OK;
- finally
- FLock.UnLock;
- end;
- end;
- function TBCBaseFilter.SetSyncSource(pClock: IReferenceClock): HRESULT;
- begin
- FLock.Lock;
- try
- FClock := pClock;
- finally
- result := NOERROR;
- FLock.UnLock;
- end;
- end;
- function TBCBaseFilter.Stop: HRESULT;
- var
- c: integer;
- Pin: TBCBasePin;
- hr: HResult;
- begin
- FLock.Lock;
- try
- result := NOERROR;
- // notify all pins of the state change
- if (FState <> State_Stopped) then
- begin
- for c := 0 to GetPinCount - 1 do
- begin
- Pin := GetPin(c);
- // Disconnected pins are not activated - this saves pins worrying
- // about this state themselves. We ignore the return code to make
- // sure everyone is inactivated regardless. The base input pin
- // class can return an error if it has no allocator but Stop can
- // be used to resync the graph state after something has gone bad
- if Pin.IsConnected then
- begin
- hr := Pin.Inactive;
- if (Failed(hr) and SUCCEEDED(result)) then result := hr;
- end;
- end;
- end;
- FState := State_Stopped;
- finally
- FLock.UnLock;
- end;
- end;
- function TBCBaseFilter.StreamTime(out rtStream: TReferenceTime): HRESULT;
- begin
- // Caller must lock for synchronization
- // We can't grab the filter lock because we want to be able to call
- // this from worker threads without deadlocking
- if FClock = nil then
- begin
- result := VFW_E_NO_CLOCK;
- exit;
- end;
- // get the current reference time
- result := FClock.GetTime(PInt64(@rtStream)^);
- if FAILED(result) then exit;
- // subtract the stream offset to get stream time
- rtStream := rtStream - FStart;
- result := S_OK;
- end;
- function TBCBaseFilter.Unregister: HRESULT;
- var
- {$IFDEF DEBUG}
- Filename: array[0..MAX_PATH-1] of Char;
- {$ENDIF}
- FilterMapper : IFilterMapper;
- FilterMapper2: IFilterMapper2;
- Factory: TBCClassFactory;
- AResult : boolean;
- begin
- Aresult := false;
- Result := S_FALSE;
- Factory := Templates.GetFactoryFromClassID(FCLSID);
- if Factory <> nil then
- begin
- {$IFDEF DEBUG}
- GetModuleFileName(hinstance, Filename, sizeof(Filename));
- DbgLog(Self,'Unregister in ' + Filename);
- {$ENDIF}
- if Failed(CoCreateInstance(CLSID_FilterMapper2, nil, CLSCTX_INPROC_SERVER, IFilterMapper2, FilterMapper2)) then
- if Failed(CoCreateInstance(CLSID_FilterMapper, nil, CLSCTX_INPROC_SERVER, IFilterMapper, FilterMapper)) then exit;
- Factory.UpdateRegistry(false);
- if FilterMapper2 <> nil then
- AResult := Factory.RegisterFilter(FilterMapper2, false)
- else AResult := Factory.RegisterFilter(FilterMapper, false);
- if Aresult then Factory.UpdateRegistry(false);
- FilterMapper := nil;
- FilterMapper2 := nil;
- end;
- if AResult then result := S_OK else result := S_False;
- end;
- { TBCEnumPins }
- constructor TBCEnumPins.Create(Filter: TBCBaseFilter; EnumPins: TBCEnumPins);
- var i: integer;
- begin
- FPosition := 0;
- FPinCount := 0;
- FFilter := Filter;
- FPinCache := TList.Create;
- // We must be owned by a filter derived from CBaseFilter
- ASSERT(FFilter <> nil);
- // Hold a reference count on our filter
- FFilter._AddRef;
- // Are we creating a new enumerator
- if (EnumPins = nil) then
- begin
- FVersion := FFilter.GetPinVersion;
- FPinCount := FFilter.GetPinCount;
- end
- else
- begin
- ASSERT(FPosition <= FPinCount);
- FPosition := EnumPins.FPosition;
- FPinCount := EnumPins.FPinCount;
- FVersion := EnumPins.FVersion;
- FPinCache.Clear;
- if EnumPins.FPinCache.Count > 0 then
- for i := 0 to EnumPins.FPinCache.Count - 1 do
- FPinCache.Add(EnumPins.FPinCache.Items[i]);
- end;
- end;
- destructor TBCEnumPins.Destroy;
- begin
- FPinCache.Free;
- FFilter._Release;
- inherited Destroy;
- end;
- function TBCEnumPins.Clone(out ppEnum: IEnumPins): HRESULT;
- begin
- result := NOERROR;
- // Check we are still in sync with the filter
- if AreWeOutOfSync then
- begin
- ppEnum := nil;
- result := VFW_E_ENUM_OUT_OF_SYNC;
- end
- else
- begin
- ppEnum := TBCEnumPins.Create(FFilter, self);
- if ppEnum = nil then result := E_OUTOFMEMORY;
- end;
- end;
- function TBCEnumPins.Next(cPins: ULONG; out ppPins: IPin;
- pcFetched: PULONG): HRESULT;
- type
- TPointerDynArray = array of Pointer;
- TIPinDynArray = array of IPin;
- var
- Fetched: cardinal;
- RealPins: integer;
- Pin: TBCBasePin;
- begin
- if pcFetched <> nil then
- pcFetched^ := 0
- else
- if (cPins>1) then
- begin
- result := E_INVALIDARG;
- exit;
- end;
- Fetched := 0; // increment as we get each one.
- // Check we are still in sync with the filter
- // If we are out of sync, we should refresh the enumerator.
- // This will reset the position and update the other members, but
- // will not clear cache of pins we have already returned.
- if AreWeOutOfSync then
- Refresh;
- // Calculate the number of available pins
- RealPins := min(FPinCount - FPosition, cPins);
- if RealPins = 0 then
- begin
- result := S_FALSE;
- exit;
- end;
- { Return each pin interface NOTE GetPin returns CBasePin * not addrefed
- so we must QI for the IPin (which increments its reference count)
- If while we are retrieving a pin from the filter an error occurs we
- assume that our internal state is stale with respect to the filter
- (for example someone has deleted a pin) so we
- return VFW_E_ENUM_OUT_OF_SYNC }
- while RealPins > 0 do
- begin
- // Get the next pin object from the filter */
- inc(FPosition);
- Pin := FFilter.GetPin(FPosition-1);
- if Pin = nil then
- begin
- // If this happend, and it's not the first time through, then we've got a problem,
- // since we should really go back and release the iPins, which we have previously
- // AddRef'ed.
- ASSERT(Fetched = 0);
- result := VFW_E_ENUM_OUT_OF_SYNC;
- exit;
- end;
- // We only want to return this pin, if it is not in our cache
- if FPinCache.IndexOf(Pin) = -1 then
- begin
- // From the object get an IPin interface
- TPointerDynArray(@ppPins)[Fetched] := nil;
- TIPinDynArray(@ppPins)[Fetched] := Pin;
- inc(Fetched);
- FPinCache.Add(Pin);
- dec(RealPins);
- end;
- end;
- if (pcFetched <> nil) then pcFetched^ := Fetched;
- if (cPins = Fetched) then result := NOERROR else result := S_FALSE;
- end;
- function TBCEnumPins.Skip(cPins: ULONG): HRESULT;
- var PinsLeft: Cardinal;
- begin
- // Check we are still in sync with the filter
- if AreWeOutOfSync then
- begin
- result := VFW_E_ENUM_OUT_OF_SYNC;
- exit;
- end;
- // Work out how many pins are left to skip over
- // We could position at the end if we are asked to skip too many...
- // ..which would match the base implementation for CEnumMediaTypes::Skip
- PinsLeft := FPinCount - FPosition;
- if (cPins > PinsLeft) then
- begin
- result := S_FALSE;
- exit;
- end;
- inc(FPosition, cPins);
- result := NOERROR;
- end;
- function TBCEnumPins.Reset: HRESULT;
- begin
- FVersion := FFilter.GetPinVersion;
- FPinCount := FFilter.GetPinCount;
- FPosition := 0;
- FPinCache.Clear;
- result := S_OK;
- end;
- function TBCEnumPins.Refresh: HRESULT;
- begin
- FVersion := FFilter.GetPinVersion;
- FPinCount := FFilter.GetPinCount;
- Fposition := 0;
- result := S_OK;
- end;
- function TBCEnumPins.AreWeOutOfSync: boolean;
- begin
- if FFilter.GetPinVersion = FVersion then result:= FALSE else result := True;
- end;
- { TBCBasePin }
- { Called by IMediaFilter implementation when the state changes from Stopped
- to either paused or running and in derived classes could do things like
- commit memory and grab hardware resource (the default is to do nothing) }
- function TBCBasePin.Active: HRESULT;
- begin
- result := NOERROR;
- end;
- { This is called to make the connection, including the task of finding
- a media type for the pin connection. pmt is the proposed media type
- from the Connect call: if this is fully specified, we will try that.
- Otherwise we enumerate and try all the input pin's types first and
- if that fails we then enumerate and try all our preferred media types.
- For each media type we check it against pmt (if non-null and partially
- specified) as well as checking that both pins will accept it. }
- function TBCBasePin.AgreeMediaType(ReceivePin: IPin; pmt: PAMMediaType): HRESULT;
- var
- EnumMT: IEnumMediaTypes;
- hrFailure: HResult;
- i: integer;
- begin
- ASSERT(ReceivePin <> nil);
- // if the media type is fully specified then use that
- if ((pmt <> nil) and (not TBCMediaType(pmt).IsPartiallySpecified)) then
- begin
- // if this media type fails, then we must fail the connection
- // since if pmt is nonnull we are only allowed to connect
- // using a type that matches it.
- result := AttemptConnection(ReceivePin, pmt);
- exit;
- end;
- // Try the other pin's enumerator
- hrFailure := VFW_E_NO_ACCEPTABLE_TYPES;
- for i := 0 to 1 do
- begin
- if (i = byte(FTryMyTypesFirst)) then
- result := ReceivePin.EnumMediaTypes(EnumMT)
- else result := EnumMediaTypes(EnumMT);
- if Succeeded(Result) then
- begin
- Assert(EnumMT <> nil);
- result := TryMediaTypes(ReceivePin,pmt,EnumMT);
- EnumMT := nil;
- if Succeeded(result) then
- begin
- result := NOERROR;
- exit;
- end
- else
- begin
- // try to remember specific error codes if there are any
- if ((result <> E_FAIL) and
- (result <> E_INVALIDARG) and
- (result <> VFW_E_TYPE_NOT_ACCEPTED)) then hrFailure := result;
- end;
- end;
- end;
- result := hrFailure;
- end;
- function TBCBasePin.AttemptConnection(ReceivePin: IPin; pmt: PAMMediaType): HRESULT;
- begin
- // The caller should hold the filter lock becasue this function
- // uses m_Connected. The caller should also hold the filter lock
- // because this function calls SetMediaType(), IsStopped() and
- // CompleteConnect().
- ASSERT(FLock.CritCheckIn);
- // Check that the connection is valid -- need to do this for every
- // connect attempt since BreakConnect will undo it.
- result := CheckConnect(ReceivePin);
- if FAILED(result) then
- begin
- {$IFDEF DEBUG}
- DbgLog(self, 'CheckConnect failed');
- {$ENDIF}
- // Since the procedure is already returning an error code, there
- // is nothing else this function can do to report the error.
- Assert(SUCCEEDED(BreakConnect));
- exit;
- end;
- DisplayTypeInfo(ReceivePin, pmt);
- // Check we will accept this media type
- result := CheckMediaType(pmt);
- if (result = NOERROR) then
- begin
- // Make ourselves look connected otherwise ReceiveConnection
- // may not be able to complete the connection
- FConnected := ReceivePin;
- result := SetMediaType(pmt);
- if Succeeded(result) then
- begin
- // See if the other pin will accept this type */
- result := ReceivePin.ReceiveConnection(self, pmt^);
- if Succeeded(result) then
- begin
- // Complete the connection
- result := CompleteConnect(ReceivePin);
- if Succeeded(result) then exit
- else
- begin
- {$IFDEF DEBUG}
- DbgLog(self, 'Failed to complete connection');
- {$ENDIF}
- ReceivePin.Disconnect;
- end;
- end;
- end;
- end
- else
- begin
- // we cannot use this media type
- // return a specific media type error if there is one
- // or map a general failure code to something more helpful
- // (in particular S_FALSE gets changed to an error code)
- if (SUCCEEDED(result) or (result = E_FAIL) or (result = E_INVALIDARG)) then
- result := VFW_E_TYPE_NOT_ACCEPTED;
- end;
- // BreakConnect and release any connection here in case CheckMediaType
- // failed, or if we set anything up during a call back during
- // ReceiveConnection.
- // Since the procedure is already returning an error code, there
- // is nothing else this function can do to report the error.
- Assert(Succeeded(BreakConnect));
- // If failed then undo our state
- FConnected := nil;
- end;
- { This is called when we realise we can't make a connection to the pin and
- must undo anything we did in CheckConnect - override to release QIs done }
- function TBCBasePin.BreakConnect: HRESULT;
- begin
- result := NOERROR;
- end;
- { This is called during Connect() to provide a virtual method that can do
- any specific check needed for connection such as QueryInterface. This
- base class method just checks that the pin directions don't match }
- function TBCBasePin.CheckConnect(Pin: IPin): HRESULT;
- var pd: TPinDirection;
- begin
- // Check that pin directions DONT match
- Pin.QueryDirection(pd);
- ASSERT((pd = PINDIR_OUTPUT) or (pd = PINDIR_INPUT));
- ASSERT((Fdir = PINDIR_OUTPUT) or (Fdir = PINDIR_INPUT));
- // we should allow for non-input and non-output connections?
- if (pd = Fdir) then result := VFW_E_INVALID_DIRECTION
- else result := NOERROR;
- end;
- { Called when we want to complete a connection to another filter. Failing
- this will also fail the connection and disconnect the other pin as well }
- function TBCBasePin.CompleteConnect(ReceivePin: IPin): HRESULT;
- begin
- result := NOERROR;
- end;
- { Asked to connect to a pin. A pin is always attached to an owning filter
- object so we always delegate our locking to that object. We first of all
- retrieve a media type enumerator for the input pin and see if we accept
- any of the formats that it would ideally like, failing that we retrieve
- our enumerator and see if it will accept any of our preferred types }
- function TBCBasePin.Connect(pReceivePin: IPin; const pmt: PAMMediaType): HRESULT;
- var HR: HResult;
- begin
- FLock.Lock;
- try
- DisplayPinInfo(pReceivePin);
- // See if we are already connected
- if FConnected <> nil then
- begin
- {$IFDEF DEBUG}
- DbgLog(self, 'Already connected');
- {$ENDIF}
- result := VFW_E_ALREADY_CONNECTED;
- // milenko start
- Exit;
- // milenko end
- end;
- // See if the filter is active
- if (not IsStopped) and (not FCanReconnectWhenActive) then
- begin
- result := VFW_E_NOT_STOPPED;
- exit;
- end;
- // Find a mutually agreeable media type -
- // Pass in the template media type. If this is partially specified,
- // each of the enumerated media types will need to be checked against
- // it. If it is non-null and fully specified, we will just try to connect
- // with this.
- Hr := AgreeMediaType(pReceivePin, pmt);
- if Failed(hr) then
- begin
- {$IFDEF DEBUG}
- DbgLog(self, 'Failed to agree type');
- {$ENDIF}
- // Since the procedure is already returning an error code, there
- // is nothing else this function can do to report the error.
- ASSERT(SUCCEEDED(BreakConnect));
- result := HR;
- exit;
- end;
- {$IFDEF DEBUG}
- DbgLog(self, 'Connection succeeded');
- {$ENDIF}
- result := NOERROR;
- finally
- FLock.UnLock;
- end;
- end;
- // Return an AddRef()'d pointer to the connected pin if there is one
- function TBCBasePin.ConnectedTo(out pPin: IPin): HRESULT;
- begin
- // It's pointless to lock here.
- // The caller should ensure integrity.
- pPin := FConnected;
- if (pPin <> nil) then
- result := S_OK
- else result := VFW_E_NOT_CONNECTED;
- end;
- function TBCBasePin.ConnectionMediaType(out pmt: TAMMediaType): HRESULT;
- begin
- FLock.Lock;
- try
- // Copy constructor of m_mt allocates the memory
- if IsConnected then
- begin
- CopyMediaType(@pmt,@Fmt);
- result := S_OK;
- end
- else
- begin
- zeromemory(@pmt, SizeOf(TAMMediaType));
- pmt.lSampleSize := 1;
- pmt.bFixedSizeSamples := True;
- result := VFW_E_NOT_CONNECTED;
- end;
- finally
- FLock.UnLock;
- end;
- end;
- constructor TBCBasePin.Create(ObjectName: string; Filter: TBCBaseFilter;
- Lock: TBCCritSec; out hr: HRESULT; Name: WideString;
- dir: TPinDirection);
- begin
- inherited Create(ObjectName, nil);
- FFilter := Filter;
- FLock := Lock;
- FPinName := Name;
- FConnected := nil;
- Fdir := dir;
- FRunTimeError := FALSE;
- FQSink := nil;
- FTypeVersion := 1;
- FStart := 0;
- FStop := MAX_TIME;
- FCanReconnectWhenActive := false;
- FTryMyTypesFirst := false;
- FRate := 1.0;
- { WARNING - Filter is often not a properly constituted object at
- this state (in particular QueryInterface may not work) - this
- is because its owner is often its containing object and we
- have been called from the containing object's constructor so
- the filter's owner has not yet had its CUnknown constructor
- called.}
- FRef := 0; // debug
- ZeroMemory(@fmt, SizeOf(TAMMediaType));
- ASSERT(Filter <> nil);
- ASSERT(Lock <> nil);
- end;
- destructor TBCBasePin.destroy;
- begin
- // We don't call disconnect because if the filter is going away
- // all the pins must have a reference count of zero so they must
- // have been disconnected anyway - (but check the assumption)
- ASSERT(FConnected = nil);
- FPinName := '';
- Assert(FRef = 0);
- FreeMediaType(@fmt);
- inherited Destroy;
- end;
- // Called when we want to terminate a pin connection
- function TBCBasePin.Disconnect: HRESULT;
- begin
- FLock.Lock;
- try
- // See if the filter is active
- if not IsStopped then
- result := VFW_E_NOT_STOPPED
- else result := DisconnectInternal;
- finally
- FLock.UnLock;
- end;
- end;
- function TBCBasePin.DisconnectInternal: HRESULT;
- begin
- ASSERT(FLock.CritCheckIn);
- if (FConnected <> nil) then
- begin
- result := BreakConnect;
- if FAILED(result) then
- begin
- // There is usually a bug in the program if BreakConnect() fails.
- {$IFDEF DEBUG}
- DbgLog(self, 'WARNING: BreakConnect() failed in CBasePin::Disconnect().');
- {$ENDIF}
- exit;
- end;
- FConnected := nil;
- result := S_OK;
- exit;
- end
- else
- // no connection - not an error
- result := S_FALSE;
- end;
- procedure TBCBasePin.DisplayPinInfo(ReceivePin: IPin);
- {$IFDEF DEBUG}
- const
- BadPin : WideString = 'Bad Pin';
- var
- ConnectPinInfo, ReceivePinInfo: TPinInfo;
- begin
- if FAILED(QueryPinInfo(ConnectPinInfo)) then
- move(Pointer(BadPin)^, ConnectPinInfo.achName, length(BadPin) * 2 +2)
- else ConnectPinInfo.pFilter := nil;
- if FAILED(ReceivePin.QueryPinInfo(ReceivePinInfo)) then
- move(Pointer(BadPin)^, ReceivePinInfo.achName, length(BadPin) * 2 +2)
- else ReceivePinInfo.pFilter := nil;
- DbgLog(self, 'Trying to connect Pins :');
- DbgLog(self, format(' <%s>', [ConnectPinInfo.achName]));
- DbgLog(self, format(' <%s>', [ReceivePinInfo.achName]));
- {$ELSE}
- begin
- {$ENDIF}
- end;
- procedure TBCBasePin.DisplayTypeInfo(Pin: IPin; pmt: PAMMediaType);
- begin
- {$IFDEF DEBUG}
- DbgLog(self, 'Trying media type:');
- DbgLog(self, ' major type: '+ GuidToString(pmt.majortype));
- DbgLog(self, ' sub type : '+ GuidToString(pmt.subtype));
- DbgLog(self, GetMediaTypeDescription(pmt));
- {$ENDIF}
- end;
- // Called when no more data will arrive
- function TBCBasePin.EndOfStream: HRESULT;
- begin
- result := S_OK;
- end;
- { This can be called to return an enumerator for the pin's list of preferred
- media types. An input pin is not obliged to have any preferred formats
- although it can do. For example, the window renderer has a preferred type
- which describes a video image that matches the current window size. All
- output pins should expose at least one preferred format otherwise it is
- possible that neither pin has any types and so no connection is possible }
- function TBCBasePin.EnumMediaTypes(out ppEnum: IEnumMediaTypes): HRESULT;
- begin
- // Create a new ref counted enumerator
- ppEnum := TBCEnumMediaTypes.Create(self, nil);
- if (ppEnum = nil) then result := E_OUTOFMEMORY
- else result := NOERROR;
- end;
- { This is a virtual function that returns a media type corresponding with
- place iPosition in the list. This base class simply returns an error as
- we support no media types by default but derived classes should override }
- function TBCBasePin.GetMediaType(Position: integer;
- out MediaType: PAMMediaType): HRESULT;
- begin
- result := E_UNEXPECTED;;
- end;
- { This is a virtual function that returns the current media type version.
- The base class initialises the media type enumerators with the value 1
- By default we always returns that same value. A Derived class may change
- the list of media types available and after doing so it should increment
- the version either in a method derived from this, or more simply by just
- incrementing the m_TypeVersion base pin variable. The type enumerators
- call this when they want to see if their enumerations are out of date }
- function TBCBasePin.GetMediaTypeVersion: longint;
- begin
- result := FTypeVersion;
- end;
- { Also called by the IMediaFilter implementation when the state changes to
- Stopped at which point you should decommit allocators and free hardware
- resources you grabbed in the Active call (default is also to do nothing) }
- function TBCBasePin.Inactive: HRESULT;
- begin
- FRunTimeError := FALSE;
- result := NOERROR;
- end;
- // Increment the cookie representing the current media type version
- procedure TBCBasePin.IncrementTypeVersion;
- begin
- InterlockedIncrement(FTypeVersion);
- end;
- function TBCBasePin.IsConnected: boolean;
- begin
- result := FConnected <> nil;
- end;
- function TBCBasePin.IsStopped: boolean;
- begin
- result := FFilter.FState = State_Stopped;
- end;
- // NewSegment notifies of the start/stop/rate applying to the data
- // about to be received. Default implementation records data and
- // returns S_OK.
- // Override this to pass downstream.
- function TBCBasePin.NewSegment(tStart, tStop: TReferenceTime;
- dRate: double): HRESULT;
- begin
- FStart := tStart;
- FStop := tStop;
- FRate := dRate;
- result := S_OK;
- end;
- function TBCBasePin.NonDelegatingAddRef: Integer;
- begin
- ASSERT(InterlockedIncrement(FRef) > 0);
- result := FFilter._AddRef;
- end;
- function TBCBasePin.NonDelegatingRelease: Integer;
- begin
- ASSERT(InterlockedDecrement(FRef) >= 0);
- result := FFilter._Release
- end;
- function TBCBasePin.Notify(pSelf: IBaseFilter; q: TQuality): HRESULT;
- begin
- {$IFDEF DEBUG}
- DbgLog(self, 'IQualityControl::Notify not over-ridden from CBasePin. (IGNORE is OK)');
- {$ENDIF}
- result := E_NOTIMPL;
- end;
- { Does this pin support this media type WARNING this interface function does
- not lock the main object as it is meant to be asynchronous by nature - if
- the media types you support depend on some internal state that is updated
- dynamically then you will need to implement locking in a derived class }
- function TBCBasePin.QueryAccept(const pmt: TAMMediaType): HRESULT;
- begin
- { The CheckMediaType method is valid to return error codes if the media
- type is horrible, an example might be E_INVALIDARG. What we do here
- is map all the error codes into either S_OK or S_FALSE regardless }
- result := CheckMediaType(@pmt);
- if FAILED(result) then result := S_FALSE;
- end;
- function TBCBasePin.QueryDirection(out pPinDir: TPinDirection): HRESULT;
- begin
- pPinDir := Fdir;
- result := NOERROR;
- end;
- function TBCBasePin.QueryId(out Id: PWideChar): HRESULT;
- begin
- result := AMGetWideString(FPinName, id);
- end;
- function TBCBasePin.QueryInternalConnections(out apPin: IPin;
- var nPin: ULONG): HRESULT;
- begin
- result := E_NOTIMPL;
- end;
- // Return information about the filter we are connect to
- function TBCBasePin.QueryPinInfo(out pInfo: TPinInfo): HRESULT;
- begin
- pInfo.pFilter := FFilter;
- if (FPinName <> '') then
- begin
- move(Pointer(FPinName)^, pInfo.achName, length(FPinName)*2);
- pInfo.achName[length(FPinName)] := #0;
- end
- else pInfo.achName[0] := #0;
- pInfo.dir := Fdir;
- result := NOERROR;
- end;
- { Called normally by an output pin on an input pin to try and establish a
- connection. }
- function TBCBasePin.ReceiveConnection(pConnector: IPin;
- const pmt: TAMMediaType): HRESULT;
- begin
- FLock.Lock;
- try
- // Are we already connected
- if (FConnected <> nil) then
- begin
- result := VFW_E_ALREADY_CONNECTED;
- exit;
- end;
- // See if the filter is active
- if (not IsStopped) and (not FCanReconnectWhenActive) then
- begin
- result := VFW_E_NOT_STOPPED;
- exit;
- end;
- result := CheckConnect(pConnector);
- if FAILED(result) then
- begin
- // Since the procedure is already returning an error code, there
- // is nothing else this function can do to report the error.
- ASSERT(SUCCEEDED(BreakConnect));
- exit;
- end;
- // Ask derived class if this media type is ok
- //CMediaType * pcmt = (CMediaType*) pmt;
- result := CheckMediaType(@pmt);
- if (result <> NOERROR) then
- begin
- // no -we don't support this media type
- // Since the procedure is already returning an error code, there
- // is nothing else this function can do to report the error.
- ASSERT(SUCCEEDED(BreakConnect));
- // return a specific media type error if there is one
- // or map a general failure code to something more helpful
- // (in particular S_FALSE gets changed to an error code)
- if (SUCCEEDED(result) or
- (result = E_FAIL) or
- (result = E_INVALIDARG)) then
- result := VFW_E_TYPE_NOT_ACCEPTED;
- exit;
- end;
- // Complete the connection
- FConnected := pConnector;
- result := SetMediaType(@pmt);
- if SUCCEEDED(result) then
- begin
- result := CompleteConnect(pConnector);
- if SUCCEEDED(result) then
- begin
- result := S_OK;
- exit;
- end;
- end;
- {$IFDEF DEBUG}
- DbgLog(self, 'Failed to set the media type or failed to complete the connection.');
- {$ENDIF}
- FConnected := nil;
- // Since the procedure is already returning an error code, there
- // is nothing else this function can do to report the error.
- ASSERT(SUCCEEDED(BreakConnect));
- finally
- FLock.UnLock;
- end;
- end;
- { Called by IMediaFilter implementation when the state changes from
- to either paused to running and in derived classes could do things like
- commit memory and grab hardware resource (the default is to do nothing) }
- function TBCBasePin.Run(Start: TReferenceTime): HRESULT;
- begin
- result := NOERROR;
- end;
- function TBCBasePin.GetCurrentMediaType: TBCMediaType;
- begin
- result := TBCMediaType(@FMT);
- end;
- function TBCBasePin.GetAMMediaType: PAMMediaType;
- begin
- result := @FMT;
- end;
- { This is called to set the format for a pin connection - CheckMediaType
- will have been called to check the connection format and if it didn't
- return an error code then this (virtual) function will be invoked }
- function TBCBasePin.SetMediaType(mt: PAMMediaType): HRESULT;
- begin
- FreeMediaType(@Fmt);
- CopyMediaType(@Fmt, mt);
- result := NOERROR;
- end;
- function TBCBasePin.SetSink(piqc: IQualityControl): HRESULT;
- begin
- FLock.Lock;
- try
- FQSink := piqc;
- result := NOERROR;
- finally
- FLock.UnLock;
- end;
- end;
- { Given an enumerator we cycle through all the media types it proposes and
- firstly suggest them to our derived pin class and if that succeeds try
- them with the pin in a ReceiveConnection call. This means that if our pin
- proposes a media type we still check in here that we can support it. This
- is deliberate so that in simple cases the enumerator can hold all of the
- media types even if some of them are not really currently available }
- function TBCBasePin.TryMediaTypes(ReceivePin: IPin; pmt: PAMMediaType;
- Enum: IEnumMediaTypes): HRESULT;
- var
- MediaCount: Cardinal;
- hrFailure : HResult;
- MediaType : PAMMediaType;
- begin
- // Reset the current enumerator position
- result := Enum.Reset;
- if Failed(result) then exit;
- MediaCount := 0;
- // attempt to remember a specific error code if there is one
- hrFailure := S_OK;
- while True do
- begin
- { Retrieve the next media type NOTE each time round the loop the
- enumerator interface will allocate another AM_MEDIA_TYPE structure
- If we are successful then we copy it into our output object, if
- not then we must delete the memory allocated before returning }
- result := Enum.Next(1, MediaType, @MediaCount);
- if (result <> S_OK) then
- begin
- if (S_OK = hrFailure) then
- hrFailure := VFW_E_NO_ACCEPTABLE_TYPES;
- result := hrFailure;
- exit;
- end;
- ASSERT(MediaCount = 1);
- ASSERT(MediaType <> nil);
- // check that this matches the partial type (if any)
- if (pmt = nil) or TBCMediaType(MediaType).MatchesPartial(pmt) then
- begin
- result := AttemptConnection(ReceivePin, MediaType);
- // attempt to remember a specific error code
- if FAILED(result) and
- SUCCEEDED(hrFailure) and
- (result <> E_FAIL) and
- (result <> E_INVALIDARG) and
- (result <> VFW_E_TYPE_NOT_ACCEPTED) then hrFailure := result;
- end
- else result := VFW_E_NO_ACCEPTABLE_TYPES;
- DeleteMediaType(MediaType);
- if result = S_OK then exit;
- end;
- end;
- { TBCEnumMediaTypes }
- { The media types a filter supports can be quite dynamic so we add to
- the general IEnumXXXX interface the ability to be signaled when they
- change via an event handle the connected filter supplies. Until the
- Reset method is called after the state changes all further calls to
- the enumerator (except Reset) will return E_UNEXPECTED error code. }
- function TBCEnumMediaTypes.AreWeOutOfSync: boolean;
- begin
- if FPin.GetMediaTypeVersion = FVersion then result := FALSE else result := True;
- end;
- { One of an enumerator's basic member functions allows us to create a cloned
- interface that initially has the same state. Since we are taking a snapshot
- of an object (current position and all) we must lock access at the start }
- function TBCEnumMediaTypes.Clone(out ppEnum: IEnumMediaTypes): HRESULT;
- begin
- result := NOERROR;
- // Check we are still in sync with the pin
- if AreWeOutOfSync then
- begin
- ppEnum := nil;
- result := VFW_E_ENUM_OUT_OF_SYNC;
- exit;
- end
- else
- begin
- ppEnum := TBCEnumMediaTypes.Create(FPin, self);
- if (ppEnum = nil) then result := E_OUTOFMEMORY;
- end;
- end;
- constructor TBCEnumMediaTypes.Create(Pin: TBCBasePin;
- EnumMediaTypes: TBCEnumMediaTypes);
- begin
- FPosition := 0;
- FPin := Pin;
- {$IFDEF DEBUG}
- DbgLog('TBCEnumMediaTypes.Create');
- {$ENDIF}
- // We must be owned by a pin derived from CBasePin */
- ASSERT(Pin <> nil);
- // Hold a reference count on our pin
- FPin._AddRef;
- // Are we creating a new enumerator
- if (EnumMediaTypes = nil) then
- begin
- FVersion := FPin.GetMediaTypeVersion;
- exit;
- end;
- FPosition := EnumMediaTypes.FPosition;
- FVersion := EnumMediaTypes.FVersion;
- end;
- { Destructor releases the reference count on our base pin. NOTE since we hold
- a reference count on the pin who created us we know it is safe to release
- it, no access can be made to it afterwards though as we might have just
- caused the last reference count to go and the object to be deleted }
- destructor TBCEnumMediaTypes.Destroy;
- begin
- {$IFDEF DEBUG}
- DbgLog('TBCEnumMediaTypes.Destroy');
- {$ENDIF}
- FPin._Release;
- inherited;
- end;
- { Enumerate the next pin(s) after the current position. The client using this
- interface passes in a pointer to an array of pointers each of which will
- be filled in with a pointer to a fully initialised media type format
- Return NOERROR if it all works,
- S_FALSE if fewer than cMediaTypes were enumerated.
- VFW_E_ENUM_OUT_OF_SYNC if the enumerator has been broken by
- state changes in the filter
- The actual count always correctly reflects the number of types in the array.}
- function TBCEnumMediaTypes.Next(cMediaTypes: ULONG;
- out ppMediaTypes: PAMMediaType; pcFetched: PULONG): HRESULT;
- type TMTDynArray = array of PAMMediaType;
- var
- Fetched: Cardinal;
- cmt: PAMMediaType;
- begin
- // Check we are still in sync with the pin
- if AreWeOutOfSync then
- begin
- result := VFW_E_ENUM_OUT_OF_SYNC;
- exit;
- end;
- if (pcFetched <> nil) then
- pcFetched^ := 0 // default unless we succeed
- // now check that the parameter is valid
- else
- if (cMediaTypes > 1) then
- begin // pcFetched == NULL
- result := E_INVALIDARG;
- exit;
- end;
- Fetched := 0; // increment as we get each one.
- { Return each media type by asking the filter for them in turn - If we
- have an error code retured to us while we are retrieving a media type
- we assume that our internal state is stale with respect to the filter
- (for example the window size changing) so we return
- VFW_E_ENUM_OUT_OF_SYNC }
- new(cmt);
- while (cMediaTypes > 0) do
- begin
- TBCMediaType(cmt).InitMediaType;
- inc(FPosition);
- result := FPin.GetMediaType(FPosition-1, cmt);
- if (S_OK <> result) then Break;
- { We now have a CMediaType object that contains the next media type
- but when we assign it to the array position we CANNOT just assign
- the AM_MEDIA_TYPE structure because as soon as the object goes out of
- scope it will delete the memory we have just copied. The function
- we use is CreateMediaType which allocates a task memory block }
- { Transfer across the format block manually to save an allocate
- and free on the format block and generally go faster }
- TMTDynArray(@ppMediaTypes)[Fetched] := CoTaskMemAlloc(sizeof(TAMMediaType));
- if TMTDynArray(@ppMediaTypes)[Fetched] = nil then Break;
- { Do a regular copy }
- //CopyMediaType(TMTDynArray(@ppMediaTypes)[Fetched], cmt);
- Move(cmt^,TMTDynArray(@ppMediaTypes)[Fetched]^,SizeOf(TAMMediaType));
- // Make sure the destructor doesn't free these
- cmt.pbFormat := nil;
- cmt.cbFormat := 0;
- Pointer(cmt.pUnk) := nil;
- inc(Fetched);
- dec(cMediaTypes);
- end;
- dispose(cmt);
- if (pcFetched <> nil) then pcFetched^ := Fetched;
- if cMediaTypes = 0 then result := NOERROR else result := S_FALSE;
- end;
- { Set the current position back to the start
- Reset has 3 simple steps:
- set position to head of list
- sync enumerator with object being enumerated
- return S_OK }
- function TBCEnumMediaTypes.Reset: HRESULT;
- begin
- FPosition := 0;
- // Bring the enumerator back into step with the current state. This
- // may be a noop but ensures that the enumerator will be valid on the
- // next call.
- FVersion := FPin.GetMediaTypeVersion;
- result := NOERROR;
- end;
- // Skip over one or more entries in the enumerator
- function TBCEnumMediaTypes.Skip(cMediaTypes: ULONG): HRESULT;
- var cmt: PAMMediaType;
- begin
- cmt := nil;
- // If we're skipping 0 elements we're guaranteed to skip the
- // correct number of elements
- if (cMediaTypes = 0) then
- begin
- result := S_OK;
- exit;
- end;
- // Check we are still in sync with the pin
- if AreWeOutOfSync then
- begin
- result := VFW_E_ENUM_OUT_OF_SYNC;
- exit;
- end;
- FPosition := FPosition + cMediaTypes;
- // See if we're over the end
- if (S_OK = FPin.GetMediaType(FPosition - 1, cmt)) then result := S_OK else result := S_FALSE;
- end;
- { TBCBaseOutputPin }
- // Commit the allocator's memory, this is called through IMediaFilter
- // which is responsible for locking the object before calling us
- function TBCBaseOutputPin.Active: HRESULT;
- begin
- if (FAllocator = nil) then
- result := VFW_E_NO_ALLOCATOR
- else result := FAllocator.Commit;
- end;
- function TBCBaseOutputPin.BeginFlush: HRESULT;
- begin
- result := E_UNEXPECTED;
- end;
- // Overriden from CBasePin
- function TBCBaseOutputPin.BreakConnect: HRESULT;
- begin
- // Release any allocator we hold
- if (FAllocator <> nil) then
- begin
- // Always decommit the allocator because a downstream filter may or
- // may not decommit the connection's allocator. A memory leak could
- // occur if the allocator is not decommited when a connection is broken.
- result := FAllocator.Decommit;
- if FAILED(result) then exit;
- FAllocator := nil;
- end;
- // Release any input pin interface we hold
- if (FInputPin <> nil) then FInputPin := nil;
- result := NOERROR;
- end;
- { This method is called when the output pin is about to try and connect to
- an input pin. It is at this point that you should try and grab any extra
- interfaces that you need, in this case IMemInputPin. Because this is
- only called if we are not currently connected we do NOT need to call
- BreakConnect. This also makes it easier to derive classes from us as
- BreakConnect is only called when we actually have to break a connection
- (or a partly made connection) and not when we are checking a connection }
- function TBCBaseOutputPin.CheckConnect(Pin: IPin): HRESULT;
- begin
- result := inherited CheckConnect(Pin);
- if FAILED(result) then exit;
- // get an input pin and an allocator interface
- result := Pin.QueryInterface(IID_IMemInputPin, FInputPin);
- if FAILED(result) then exit;
- result := NOERROR;
- end;
- // This is called after a media type has been proposed
- // Try to complete the connection by agreeing the allocator
- function TBCBaseOutputPin.CompleteConnect(ReceivePin: IPin): HRESULT;
- begin
- result := DecideAllocator(FInputPin, FAllocator);
- end;
- constructor TBCBaseOutputPin.Create(ObjectName: string;
- Filter: TBCBaseFilter; Lock: TBCCritSec; out hr: HRESULT;
- const Name: WideString);
- begin
- inherited Create(ObjectName, Filter, Lock, hr, Name, PINDIR_OUTPUT);
- FAllocator := nil;
- FInputPin := nil;
- ASSERT(FFilter <> nil);
- end;
- { Decide on an allocator, override this if you want to use your own allocator
- Override DecideBufferSize to call SetProperties. If the input pin fails
- the GetAllocator call then this will construct a CMemAllocator and call
- DecideBufferSize on that, and if that fails then we are completely hosed.
- If the you succeed the DecideBufferSize call, we will notify the input
- pin of the selected allocator. NOTE this is called during Connect() which
- therefore looks after grabbing and locking the object's critical section }
- // We query the input pin for its requested properties and pass this to
- // DecideBufferSize to allow it to fulfill requests that it is happy
- // with (eg most people don't care about alignment and are thus happy to
- // use the downstream pin's alignment request).
- function TBCBaseOutputPin.DecideAllocator(Pin: IMemInputPin;
- out Alloc: IMemAllocator): HRESULT;
- var
- prop: TAllocatorProperties;
- begin
- Alloc := nil;
- // get downstream prop request
- // the derived class may modify this in DecideBufferSize, but
- // we assume that he will consistently modify it the same way,
- // so we only get it once
- ZeroMemory(@prop, sizeof(TAllocatorProperties));
- // whatever he returns, we assume prop is either all zeros
- // or he has filled it out.
- Pin.GetAllocatorRequirements(prop);
- // if he doesn't care about alignment, then set it to 1
- if (prop.cbAlign = 0) then prop.cbAlign := 1;
- // Try the allocator provided by the input pin
- result := Pin.GetAllocator(Alloc);
- if SUCCEEDED(result) then
- begin
- result := DecideBufferSize(Alloc, @prop);
- if SUCCEEDED(result) then
- begin
- result := Pin.NotifyAllocator(Alloc, FALSE);
- if SUCCEEDED(result) then
- begin
- result := NOERROR;
- exit;
- end;
- end;
- end;
- // If the GetAllocator failed we may not have an interface
- if (Alloc <> nil) then Alloc := nil;
- // Try the output pin's allocator by the same method
- result := InitAllocator(Alloc);
- if SUCCEEDED(result) then
- begin
- // note - the properties passed here are in the same
- // structure as above and may have been modified by
- // the previous call to DecideBufferSize
- result := DecideBufferSize(Alloc, @prop);
- if SUCCEEDED(result) then
- begin
- result := Pin.NotifyAllocator(Alloc, FALSE);
- if SUCCEEDED(result) then
- begin
- result := NOERROR;
- exit;
- end;
- end;
- end;
- // Likewise we may not have an interface to release
- if (Alloc <> nil) then Alloc := nil;
- end;
- function TBCBaseOutputPin.DecideBufferSize(Alloc: IMemAllocator;
- propInputRequest: PAllocatorProperties): HRESULT;
- begin
- result := S_OK; // ???
- end;
- { Deliver a filled-in sample to the connected input pin. NOTE the object must
- have locked itself before calling us otherwise we may get halfway through
- executing this method only to find the filter graph has got in and
- disconnected us from the input pin. If the filter has no worker threads
- then the lock is best applied on Receive(), otherwise it should be done
- when the worker thread is ready to deliver. There is a wee snag to worker
- threads that this shows up. The worker thread must lock the object when
- it is ready to deliver a sample, but it may have to wait until a state
- change has completed, but that may never complete because the state change
- is waiting for the worker thread to complete. The way to handle this is for
- the state change code to grab the critical section, then set an abort event
- for the worker thread, then release the critical section and wait for the
- worker thread to see the event we set and then signal that it has finished
- (with another event). At which point the state change code can complete }
- // note (if you've still got any breath left after reading that) that you
- // need to release the sample yourself after this call. if the connected
- // input pin needs to hold onto the sample beyond the call, it will addref
- // the sample itself.
- // of course you must release this one and call GetDeliveryBuffer for the
- // next. You cannot reuse it directly.
- function TBCBaseOutputPin.Deliver(Sample: IMediaSample): HRESULT;
- begin
- if (FInputPin = nil) then result := VFW_E_NOT_CONNECTED
- else result := FInputPin.Receive(Sample);
- end;
- // call BeginFlush on the connected input pin
- function TBCBaseOutputPin.DeliverBeginFlush: HRESULT;
- begin
- // remember this is on IPin not IMemInputPin
- if (FConnected = nil) then
- result := VFW_E_NOT_CONNECTED
- else result := FConnected.BeginFlush;
- end;
- // call EndFlush on the connected input pin
- function TBCBaseOutputPin.DeliverEndFlush: HRESULT;
- begin
- // remember this is on IPin not IMemInputPin
- if (FConnected = nil) then
- result := VFW_E_NOT_CONNECTED
- else result := FConnected.EndFlush;
- end;
- // called from elsewhere in our filter to pass EOS downstream to
- // our connected input pin
- function TBCBaseOutputPin.DeliverEndOfStream: HRESULT;
- begin
- // remember this is on IPin not IMemInputPin
- if (FConnected = nil) then
- result := VFW_E_NOT_CONNECTED
- else result := FConnected.EndOfStream;
- end;
- // deliver NewSegment to connected pin
- function TBCBaseOutputPin.DeliverNewSegment(Start, Stop: TReferenceTime;
- Rate: double): HRESULT;
- begin
- if (FConnected = nil) then
- result := VFW_E_NOT_CONNECTED
- else result := FConnected.NewSegment(Start, Stop, Rate);
- end;
- function TBCBaseOutputPin.EndFlush: HRESULT;
- begin
- result := E_UNEXPECTED;
- end;
- // we have a default handling of EndOfStream which is to return
- // an error, since this should be called on input pins only
- function TBCBaseOutputPin.EndOfStream: HRESULT;
- begin
- result := E_UNEXPECTED;
- end;
- // This returns an empty sample buffer from the allocator WARNING the same
- // dangers and restrictions apply here as described below for Deliver()
- function TBCBaseOutputPin.GetDeliveryBuffer(out Sample: IMediaSample;
- StartTime, EndTime: PReferenceTime; Flags: Longword): HRESULT;
- begin
- if (FAllocator <> nil) then
- result := FAllocator.GetBuffer(Sample, StartTime, EndTime, Flags)
- else result := E_NOINTERFACE;
- end;
- { Free up or unprepare allocator's memory, this is called through
- IMediaFilter which is responsible for locking the object first }
- function TBCBaseOutputPin.Inactive: HRESULT;
- begin
- FRunTimeError := FALSE;
- if (FAllocator = nil) then
- result := VFW_E_NO_ALLOCATOR
- else result := FAllocator.Decommit;
- end;
- // This is called when the input pin didn't give us a valid allocator
- function TBCBaseOutputPin.InitAllocator(out Alloc: IMemAllocator): HRESULT;
- begin
- result := CoCreateInstance(CLSID_MemoryAllocator, nil, CLSCTX_INPROC_SERVER,
- IID_IMemAllocator, Alloc);
- end;
- { TBCBaseInputPin }
- // Default handling for BeginFlush - call at the beginning
- // of your implementation (makes sure that all Receive calls
- // fail). After calling this, you need to free any queued data
- // and then call downstream.
- function TBCBaseInputPin.BeginFlush: HRESULT;
- begin
- // BeginFlush is NOT synchronized with streaming but is part of
- // a control action - hence we synchronize with the filter
- FLock.Lock;
- try
- // if we are already in mid-flush, this is probably a mistake
- // though not harmful - try to pick it up for now so I can think about it
- ASSERT(not FFlushing);
- // first thing to do is ensure that no further Receive calls succeed
- FFlushing := True;
- // now discard any data and call downstream - must do that
- // in derived classes
- result := S_OK;
- finally
- FLock.UnLock;
- end;
- end;
- function TBCBaseInputPin.BreakConnect: HRESULT;
- begin
- // We don't need our allocator any more
- if (FAllocator <> nil) then
- begin
- // Always decommit the allocator because a downstream filter may or
- // may not decommit the connection's allocator. A memory leak could
- // occur if the allocator is not decommited when a pin is disconnected.
- result := FAllocator.Decommit;
- if FAILED(result) then exit;
- FAllocator := nil;
- end;
- result := S_OK;
- end;
- // Check if it's OK to process data
- function TBCBaseInputPin.CheckStreaming: HRESULT;
- begin
- // Shouldn't be able to get any data if we're not connected!
- ASSERT(IsConnected);
- // Don't process stuff in Stopped state
- if IsStopped then begin result := VFW_E_WRONG_STATE; exit end;
- if FFlushing then begin result := S_FALSE; exit end;
- if FRunTimeError then begin result := VFW_E_RUNTIME_ERROR; exit end;
- result := S_OK;
- end;
- // Constructor creates a default allocator object
- constructor TBCBaseInputPin.Create(ObjectName: string;
- Filter: TBCBaseFilter; Lock: TBCCritSec; out hr: HRESULT;
- Name: WideString);
- begin
- inherited create(ObjectName, Filter, Lock, hr, Name, PINDIR_INPUT);
- FAllocator := nil;
- FReadOnly := false;
- FFlushing := false;
- ZeroMemory(@FSampleProps, sizeof(FSampleProps));
- end;
- destructor TBCBaseInputPin.Destroy;
- begin
- if FAllocator <> nil then FAllocator := nil;
- inherited;
- end;
- // default handling for EndFlush - call at end of your implementation
- // - before calling this, ensure that there is no queued data and no thread
- // pushing any more without a further receive, then call downstream,
- // then call this method to clear the m_bFlushing flag and re-enable
- // receives
- function TBCBaseInputPin.EndFlush: HRESULT;
- begin
- // Endlush is NOT synchronized with streaming but is part of
- // a control action - hence we synchronize with the filter
- FLock.Lock;
- try
- // almost certainly a mistake if we are not in mid-flush
- ASSERT(FFlushing);
- // before calling, sync with pushing thread and ensure
- // no more data is going downstream, then call EndFlush on
- // downstream pins.
- // now re-enable Receives
- FFlushing := FALSE;
- // No more errors
- FRunTimeError := FALSE;
- result := S_OK;
- finally
- FLock.UnLock;
- end;
- end;
- { Return the allocator interface that this input pin would like the output
- pin to use. NOTE subsequent calls to GetAllocator should all return an
- interface onto the SAME object so we create one object at the start
- Note:
- The allocator is Release()'d on disconnect and replaced on
- NotifyAllocator().
- Override this to provide your own allocator.}
- function TBCBaseInputPin.GetAllocator(
- out ppAllocator: IMemAllocator): HRESULT;
- begin
- FLock.Lock;
- try
- if (FAllocator = nil) then
- begin
- result := CoCreateInstance(CLSID_MemoryAllocator, nil, CLSCTX_INPROC_SERVER,
- IID_IMemAllocator, FAllocator);
- if FAILED(result) then exit;
- end;
- ASSERT(FAllocator <> nil);
- ppAllocator := FAllocator;
- result := NOERROR;
- finally
- FLock.UnLock;
- end;
- end;
- // what requirements do we have of the allocator - override if you want
- // to support other people's allocators but need a specific alignment
- // or prefix.
- function TBCBaseInputPin.GetAllocatorRequirements(
- out pProps: TAllocatorProperties): HRESULT;
- begin
- result := E_NOTIMPL;
- end;
- { Free up or unprepare allocator's memory, this is called through
- IMediaFilter which is responsible for locking the object first. }
- function TBCBaseInputPin.Inactive: HRESULT;
- begin
- FRunTimeError := FALSE;
- if (FAllocator = nil) then
- begin
- result := VFW_E_NO_ALLOCATOR;
- exit;
- end;
- FFlushing := FALSE;
- result := FAllocator.Decommit;
- end;
- function TBCBaseInputPin.Notify(pSelf: IBaseFilter; q: TQuality): HRESULT;
- begin
- {$IFDEF DEBUG}
- DbgLog(self, 'IQuality.Notify called on an input pin');
- {$ENDIF}
- result := NOERROR;
- end;
- { Tell the input pin which allocator the output pin is actually going to use
- Override this if you care - NOTE the locking we do both here and also in
- GetAllocator is unnecessary but derived classes that do something useful
- will undoubtedly have to lock the object so this might help remind people }
- function TBCBaseInputPin.NotifyAllocator(pAllocator: IMemAllocator;
- bReadOnly: BOOL): HRESULT;
- begin
- FLock.Lock;
- try
- FAllocator := pAllocator;
- // the readonly flag indicates whether samples from this allocator should
- // be regarded as readonly - if True, then inplace transforms will not be
- // allowed.
- FReadOnly := bReadOnly;
- result := NOERROR;
- finally
- FLock.UnLock;
- end;
- end;
- // Pass on the Quality notification q to
- // a. Our QualityControl sink (if we have one) or else
- // b. to our upstream filter
- // and if that doesn't work, throw it away with a bad return code
- function TBCBaseInputPin.PassNotify(const q: TQuality): HRESULT;
- var IQC: IQualityControl;
- begin
- // We pass the message on, which means that we find the quality sink
- // for our input pin and send it there
- {$IFDEF DEBUG}
- DbgLog(self, 'Passing Quality notification through transform');
- {$ENDIF}
- if (FQSink <> nil) then
- begin
- result := FQSink.Notify(FFilter, q);
- exit;
- end
- else
- begin
- // no sink set, so pass it upstream
- result := VFW_E_NOT_FOUND; // default
- if (FConnected <> nil) then
- begin
- FConnected.QueryInterface(IID_IQualityControl, IQC);
- if (IQC <> nil) then
- begin
- result := IQC.Notify(FFilter, q);
- IQC := nil;
- end;
- end;
- end;
- end;
- { Do something with this media sample - this base class checks to see if the
- format has changed with this media sample and if so checks that the filter
- will accept it, generating a run time error if not. Once we have raised a
- run time error we set a flag so that no more samples will be accepted
- It is important that any filter should override this method and implement
- synchronization so that samples are not processed when the pin is
- disconnected etc. }
- function TBCBaseInputPin.Receive(pSample: IMediaSample): HRESULT;
- var Sample2: IMediaSample2;
- begin
- ASSERT(pSample <> nil);
- result := CheckStreaming;
- if (S_OK <> result) then exit;
- // Check for IMediaSample2
- if SUCCEEDED(pSample.QueryInterface(IID_IMediaSample2, Sample2)) then
- begin
- result := Sample2.GetProperties(sizeof(FSampleProps), FSampleProps);
- Sample2 := nil;
- if FAILED(result) then exit;
- end
- else
- begin
- // Get the properties the hard way
- FSampleProps.cbData := sizeof(FSampleProps);
- FSampleProps.dwTypeSpecificFlags := 0;
- FSampleProps.dwStreamId := AM_STREAM_MEDIA;
- FSampleProps.dwSampleFlags := 0;
- if (S_OK = pSample.IsDiscontinuity) then
- FSampleProps.dwSampleFlags := FSampleProps.dwSampleFlags or AM_SAMPLE_DATADISCONTINUITY;
- if (S_OK = pSample.IsPreroll) then
- FSampleProps.dwSampleFlags := FSampleProps.dwSampleFlags or AM_SAMPLE_PREROLL;
- if (S_OK = pSample.IsSyncPoint) then
- FSampleProps.dwSampleFlags := FSampleProps.dwSampleFlags or AM_SAMPLE_SPLICEPOINT;
- if SUCCEEDED(pSample.GetTime(FSampleProps.tStart, FSampleProps.tStop)) then
- FSampleProps.dwSampleFlags := FSampleProps.dwSampleFlags or AM_SAMPLE_TIMEVALID or AM_SAMPLE_STOPVALID;
- if (S_OK = pSample.GetMediaType(FSampleProps.pMediaType)) then
- FSampleProps.dwSampleFlags := FSampleProps.dwSampleFlags or AM_SAMPLE_TYPECHANGED;
- pSample.GetPointer(PByte(FSampleProps.pbBuffer));
- FSampleProps.lActual := pSample.GetActualDataLength;
- FSampleProps.cbBuffer := pSample.GetSize;
- end;
- // Has the format changed in this sample
- if (not BOOL(FSampleProps.dwSampleFlags and AM_SAMPLE_TYPECHANGED)) then
- begin
- result := NOERROR;
- exit;
- end;
- // Check the derived class accepts this format */
- // This shouldn't fail as the source must call QueryAccept first */
- result := CheckMediaType(FSampleProps.pMediaType);
- if (result = NOERROR) then exit;
- // Raise a runtime error if we fail the media type
- FRunTimeError := True;
- EndOfStream;
- FFilter.NotifyEvent(EC_ERRORABORT,VFW_E_TYPE_NOT_ACCEPTED,0);
- result := VFW_E_INVALIDMEDIATYPE;
- end;
- // See if Receive() might block
- function TBCBaseInputPin.ReceiveCanBlock: HRESULT;
- var
- c, Pins, OutputPins: Integer;
- Pin: TBCBasePin;
- pd: TPinDirection;
- Connected: IPin;
- InputPin: IMemInputPin;
- begin
- { Ask all the output pins if they block
- If there are no output pin assume we do block. }
- Pins := FFilter.GetPinCount;
- OutputPins := 0;
- for c := 0 to Pins - 1 do
- begin
- Pin := FFilter.GetPin(c);
- result := Pin.QueryDirection(pd);
- if FAILED(result) then exit;
- if (pd = PINDIR_OUTPUT) then
- begin
- result := Pin.ConnectedTo(Connected);
- if SUCCEEDED(result) then
- begin
- assert(Connected <> nil);
- inc(OutputPins);
- result := Connected.QueryInterface(IID_IMemInputPin, InputPin);
- Connected := nil;
- if SUCCEEDED(result) then
- begin
- result := InputPin.ReceiveCanBlock;
- InputPin := nil;
- if (result <> S_FALSE) then
- begin
- result := S_OK;
- exit;
- end;
- end
- else
- begin
- // There's a transport we don't understand here
- result := S_OK;
- exit;
- end;
- end;
- end;
- end;
- if OutputPins = 0 then result := S_OK else result := S_FALSE;
- end;
- // Receive multiple samples
- function TBCBaseInputPin.ReceiveMultiple(var pSamples: IMediaSample;
- nSamples: Integer; out nSamplesProcessed: Integer): HRESULT;
- type
- TMediaSampleDynArray = array of IMediaSample;
- begin
- result := S_OK;
- nSamplesProcessed := 0;
- dec(nSamples);
- while (nSamples >= 0) do
- begin
- result := Receive(TMediaSampleDynArray(@pSamples)[nSamplesProcessed]);
- // S_FALSE means don't send any more
- if (result <> S_OK) then break;
- inc(nSamplesProcessed);
- dec(nSamples)
- end;
- end;
- function TBCBaseInputPin.SampleProps: PAMSample2Properties;
- begin
- ASSERT(FSampleProps.cbData <> 0);
- result := @FSampleProps;
- end;
- // milenko start (added TBCDynamicOutputPin conversion)
- { TBCDynamicOutputPin }
- //
- // The streaming thread calls IPin::NewSegment(), IPin::EndOfStream(),
- // IMemInputPin::Receive() and IMemInputPin::ReceiveMultiple() on the
- // connected input pin. The application thread calls Block(). The
- // following class members can only be called by the streaming thread.
- //
- // Deliver()
- // DeliverNewSegment()
- // StartUsingOutputPin()
- // StopUsingOutputPin()
- // ChangeOutputFormat()
- // ChangeMediaType()
- // DynamicReconnect()
- //
- // The following class members can only be called by the application thread.
- //
- // Block()
- // SynchronousBlockOutputPin()
- // AsynchronousBlockOutputPin()
- //
- constructor TBCDynamicOutputPin.Create(ObjectName: WideString; Filter: TBCBaseFilter;
- Lock: TBCCritSec; out hr: HRESULT; Name: WideString);
- begin
- inherited Create(ObjectName,Filter,Lock,hr,Name);
- FStopEvent := 0;
- FGraphConfig := nil;
- FPinUsesReadOnlyAllocator := False;
- FBlockState := NOT_BLOCKED;
- FUnblockOutputPinEvent := 0;
- FNotifyCallerPinBlockedEvent := 0;
- FBlockCallerThreadID := 0;
- FNumOutstandingOutputPinUsers := 0;
- FBlockStateLock := TBCCritSec.Create;
- hr := Initialize;
- end;
- destructor TBCDynamicOutputPin.Destroy;
- begin
- if(FUnblockOutputPinEvent <> 0) then
- begin
- // This call should not fail because we have access to m_hUnblockOutputPinEvent
- // and m_hUnblockOutputPinEvent is a valid event.
- ASSERT(CloseHandle(FUnblockOutputPinEvent));
- end;
- if(FNotifyCallerPinBlockedEvent <> 0) then
- begin
- // This call should not fail because we have access to m_hNotifyCallerPinBlockedEvent
- // and m_hNotifyCallerPinBlockedEvent is a valid event.
- ASSERT(CloseHandle(FNotifyCallerPinBlockedEvent));
- end;
- if Assigned(FBlockStateLock) then FreeAndNil(FBlockStateLock);
- inherited Destroy;
- end;
- function TBCDynamicOutputPin.NonDelegatingQueryInterface(const IID: TGUID; out Obj): HResult;
- begin
- if IsEqualGUID(IID,IID_IPinFlowControl) then
- begin
- if GetInterface(IID_IPinFlowControl, Obj) then Result := S_OK
- else Result := E_NOINTERFACE;
- end else
- begin
- Result := inherited NonDelegatingQueryInterface(IID,Obj);
- end;
- end;
- function TBCDynamicOutputPin.Disconnect: HRESULT;
- begin
- FLock.Lock;
- try
- Result := DisconnectInternal;
- finally
- FLock.Unlock;
- end;
- end;
- function TBCDynamicOutputPin.Block(dwBlockFlags: DWORD; hEvent: THandle): HResult;
- begin
- // Check for illegal flags.
- if BOOL(dwBlockFlags and not AM_PIN_FLOW_CONTROL_BLOCK) then
- begin
- Result := E_INVALIDARG;
- Exit;
- end;
- // Make sure the event is unsignaled.
- if(BOOL(dwBlockFlags and AM_PIN_FLOW_CONTROL_BLOCK) and (hEvent <> 0)) then
- begin
- if not ResetEvent(hEvent) then
- begin
- Result := AmGetLastErrorToHResult;
- Exit
- end;
- end;
- // No flags are set if we are unblocking the output pin.
- if(dwBlockFlags = 0) then
- begin
- // This parameter should be NULL because unblock operations are always synchronous.
- // There is no need to notify the caller when the event is done.
- if(hEvent <> 0) then
- begin
- Result := E_INVALIDARG;
- Exit;
- end;
- end;
- {$IFDEF DEBUG}
- AssertValid;
- {$ENDIF} // DEBUG
- if BOOL(dwBlockFlags and AM_PIN_FLOW_CONTROL_BLOCK) then
- begin
- // IPinFlowControl::Block()'s hEvent parameter is NULL if the block is synchronous.
- // If hEvent is not NULL, the block is asynchronous.
- if(hEvent = 0) then Result := SynchronousBlockOutputPin
- else Result := AsynchronousBlockOutputPin(hEvent);
- end else
- begin
- Result := UnblockOutputPin;
- end;
- {$IFDEF DEBUG}
- AssertValid;
- {$ENDIF} // DEBUG
- if(FAILED(Result)) then Exit;
- Result := S_OK;
- end;
- procedure TBCDynamicOutputPin.SetConfigInfo(GraphConfig: IGraphConfig; StopEvent: THandle);
- begin
- // This pointer is not addrefed because filters are not allowed to
- // hold references to the filter graph manager. See the documentation for
- // IBaseFilter::JoinFilterGraph() in the Direct Show SDK for more information.
- Pointer(FGraphConfig) := Pointer(GraphConfig);
- FStopEvent := StopEvent;
- end;
- {$IFDEF DEBUG}
- function TBCDynamicOutputPin.Deliver(Sample: IMediaSample): HRESULT;
- begin
- // The caller should call StartUsingOutputPin() before calling this
- // method.
- ASSERT(StreamingThreadUsingOutputPin);
- Result := inherited Deliver(Sample);
- end;
- function TBCDynamicOutputPin.DeliverEndOfStream: HRESULT;
- begin
- // The caller should call StartUsingOutputPin() before calling this
- // method.
- ASSERT(StreamingThreadUsingOutputPin);
- Result := inherited DeliverEndOfStream;
- end;
- function TBCDynamicOutputPin.DeliverNewSegment(Start, Stop: TReferenceTime; Rate: Double): HRESULT;
- begin
- // The caller should call StartUsingOutputPin() before calling this
- // method.
- ASSERT(StreamingThreadUsingOutputPin);
- Result := inherited DeliverNewSegment(Start, Stop, Rate);
- end;
- {$ENDIF}
- function TBCDynamicOutputPin.DeliverBeginFlush: HRESULT;
- begin
- // If this ASSERT fires, the user may have passed an invalid event handle to SetConfigInfo().
- // The ASSERT can also fire if the event if destroyed and then DeliverBeginFlush() is called.
- // An event handle is invalid if 1) the event does not exist or the user does not have the security
- // permissions to use the event.
- ASSERT(SetEvent(FStopEvent));
- Result := inherited DeliverBeginFlush;
- end;
- function TBCDynamicOutputPin.DeliverEndFlush: HRESULT;
- begin
- // If this ASSERT fires, the user may have passed an invalid event handle to SetConfigInfo().
- // The ASSERT can also fire if the event if destroyed and then DeliverBeginFlush() is called.
- // An event handle is invalid if 1) the event does not exist or the user does not have the security
- // permissions to use the event.
- ASSERT(ResetEvent(FStopEvent));
- Result := inherited DeliverEndFlush;
- end;
- function TBCDynamicOutputPin.Active: HRESULT;
- begin
- // Make sure the user initialized the object by calling SetConfigInfo().
- if(FStopEvent = 0) or (FGraphConfig = nil) then
- begin
- {$IFDEF DEBUG}
- DbgLog('ERROR: TBCDynamicOutputPin.Active() failed because m_pGraphConfig' +
- ' and m_hStopEvent were not initialized. Call SetConfigInfo() to initialize them.');
- {$ENDIF} // DEBUG
- Result := E_FAIL;
- Exit;
- end;
- // If this ASSERT fires, the user may have passed an invalid event handle to SetConfigInfo().
- // The ASSERT can also fire if the event if destroyed and then Active() is called. An event
- // handle is invalid if 1) the event does not exist or the user does not have the security
- // permissions to use the event.
- ASSERT(ResetEvent(FStopEvent));
- Result := inherited Active;
- end;
- function TBCDynamicOutputPin.Inactive: HRESULT;
- begin
- // If this ASSERT fires, the user may have passed an invalid event handle to SetConfigInfo().
- // The ASSERT can also fire if the event if destroyed and then Active() is called. An event
- // handle is invalid if 1) the event does not exist or the user does not have the security
- // permissions to use the event.
- ASSERT(SetEvent(FStopEvent));
- Result := inherited Inactive;
- end;
- function TBCDynamicOutputPin.CompleteConnect(ReceivePin: IPin): HRESULT;
- begin
- Result := inherited CompleteConnect(ReceivePin);
- if(SUCCEEDED(Result)) then
- begin
- if (not IsStopped) and (FAllocator <> nil) then
- begin
- Result := FAllocator.Commit;
- ASSERT(Result <> VFW_E_ALREADY_COMMITTED);
- end;
- end;
- end;
- function TBCDynamicOutputPin.StartUsingOutputPin: HRESULT;
- var
- WaitEvents: array[0..1] of THandle;
- NumWaitEvents: DWORD;
- ReturnValue: DWORD;
- begin
- // The caller should not hold m_BlockStateLock. If the caller does,
- // a deadlock could occur.
- ASSERT(FBlockStateLock.CritCheckIn);
- FBlockStateLock.Lock;
- try
- {$IFDEF DEBUG}
- AssertValid;
- {$ENDIF} // DEBUG
- // Are we in the middle of a block operation?
- while(BLOCKED = FBlockState) do
- begin
- FBlockStateLock.Unlock;
- // If this ASSERT fires, a deadlock could occur. The caller should make sure
- // that this thread never acquires the Block State lock more than once.
- ASSERT(FBlockStateLock.CritCheckIn);
- // WaitForMultipleObjects() returns WAIT_OBJECT_0 if the unblock event
- // is fired. It returns WAIT_OBJECT_0 + 1 if the stop event if fired.
- // See the Windows SDK documentation for more information on
- // WaitForMultipleObjects().
- WaitEvents[0] := FUnblockOutputPinEvent;
- WaitEvents[0] := FStopEvent;
- NumWaitEvents := sizeof(WaitEvents) div sizeof(THANDLE);
- ReturnValue := WaitForMultipleObjects(NumWaitEvents, @WaitEvents, False, INFINITE);
- FBlockStateLock.Lock;
- {$IFDEF DEBUG}
- AssertValid;
- {$ENDIF} // DEBUG
- case ReturnValue of
- WAIT_OBJECT_0: break;
- WAIT_OBJECT_0 + 1:
- begin
- Result := VFW_E_STATE_CHANGED;
- Exit;
- end;
- WAIT_FAILED:
- begin
- Result := AmGetLastErrorToHResult;
- Exit;
- end;
- else
- begin
- {$IFDEF DEBUG}
- DbgLog('An Unexpected case occured in TBCDynamicOutputPin.StartUsingOutputPin().');
- {$ENDIF} // DEBUG
- Result := E_UNEXPECTED;
- Exit;
- end;
- end;
- end;
- inc(FNumOutstandingOutputPinUsers);
- {$IFDEF DEBUG}
- AssertValid;
- {$ENDIF} // DEBUG
- Result := S_OK;
- finally
- FBlockStateLock.Unlock;
- end;
- end;
- procedure TBCDynamicOutputPin.StopUsingOutputPin;
- begin
- FBlockStateLock.Lock;
- try
- {$IFDEF DEBUG}
- AssertValid;
- {$ENDIF} // DEBUG
- dec(FNumOutstandingOutputPinUsers);
- if(FNumOutstandingOutputPinUsers = 0) and (NOT_BLOCKED <> FBlockState)
- then BlockOutputPin;
- {$IFDEF DEBUG}
- AssertValid;
- {$ENDIF} // DEBUG
- finally
- FBlockStateLock.Unlock;
- end;
- end;
- function TBCDynamicOutputPin.StreamingThreadUsingOutputPin: Boolean;
- begin
- FBlockStateLock.Lock;
- try
- Result := (FNumOutstandingOutputPinUsers > 0);
- finally
- FBlockStateLock.UnLock;
- end;
- end;
- function TBCDynamicOutputPin.ChangeOutputFormat(const pmt: PAMMEdiaType; tSegmentStart, tSegmentStop:
- TreferenceTime; dSegmentRate: Double): HRESULT;
- begin
- // The caller should call StartUsingOutputPin() before calling this
- // method.
- ASSERT(StreamingThreadUsingOutputPin);
- // Callers should always pass a valid media type to ChangeOutputFormat() .
- ASSERT(pmt <> nil);
- Result := ChangeMediaType(pmt);
- if (FAILED(Result)) then Exit;
- Result :=DeliverNewSegment(tSegmentStart, tSegmentStop, dSegmentRate);
- if(FAILED(Result)) then Exit;
- Result := S_OK;
- end;
- function TBCDynamicOutputPin.ChangeMediaType(const pmt: PAMMediaType): HRESULT;
- var
- pConnection: IPinConnection;
- begin
- // The caller should call StartUsingOutputPin() before calling this
- // method.
- ASSERT(StreamingThreadUsingOutputPin);
- // This function assumes the filter graph is running.
- ASSERT(not IsStopped);
- if (not IsConnected) then
- begin
- Result := VFW_E_NOT_CONNECTED;
- Exit;
- end;
- // First check if the downstream pin will accept a dynamic
- // format change
- FConnected.QueryInterface(IID_IPinConnection, pConnection);
- if(pConnection <> nil) then
- begin
- if(S_OK = pConnection.DynamicQueryAccept(pmt^)) then
- begin
- Result := ChangeMediaTypeHelper(pmt);
- if(FAILED(Result)) then Exit;
- Result := S_OK;
- Exit;
- end;
- end;
- // Can't do the dynamic connection
- Result := DynamicReconnect(pmt);
- end;
- // this method has to be called from the thread that is pushing data,
- // and it's the caller's responsibility to make sure that the thread
- // has no outstand samples because they cannot be delivered after a
- // reconnect
- //
- function TBCDynamicOutputPin.DynamicReconnect(const pmt: PAMMediaType): HRESULT;
- begin
- // The caller should call StartUsingOutputPin() before calling this
- // method.
- ASSERT(StreamingThreadUsingOutputPin);
- if(FGraphConfig = nil) or (FStopEvent = 0) then
- begin
- Result := E_FAIL;
- Exit;
- end;
- Result := FGraphConfig.Reconnect(Self,nil,pmt,nil,FStopEvent,
- AM_GRAPH_CONFIG_RECONNECT_CACHE_REMOVED_FILTERS);
- end;
- function TBCDynamicOutputPin.SynchronousBlockOutputPin: HRESULT;
- var
- NotifyCallerPinBlockedEvent: THandle;
- begin
- NotifyCallerPinBlockedEvent := CreateEvent(nil, // The event will have the default security attributes.
- False, // This is an automatic reset event.
- False, // The event is initially unsignaled.
- nil); // The event is not named.
- // CreateEvent() returns NULL if an error occurs.
- if(NotifyCallerPinBlockedEvent = 0) then
- begin
- Result := AmGetLastErrorToHResult;
- Exit;
- end;
- Result := AsynchronousBlockOutputPin(NotifyCallerPinBlockedEvent);
- if(FAILED(Result)) then
- begin
- // This call should not fail because we have access to hNotifyCallerPinBlockedEvent
- // and hNotifyCallerPinBlockedEvent is a valid event.
- ASSERT(CloseHandle(NotifyCallerPinBlockedEvent));
- Exit;
- end;
- Result := WaitEvent(NotifyCallerPinBlockedEvent);
- // This call should not fail because we have access to hNotifyCallerPinBlockedEvent
- // and hNotifyCallerPinBlockedEvent is a valid event.
- ASSERT(CloseHandle(NotifyCallerPinBlockedEvent));
- if(FAILED(Result)) then Exit;
- Result := S_OK;
- end;
- function TBCDynamicOutputPin.AsynchronousBlockOutputPin(NotifyCallerPinBlockedEvent: THandle): HRESULT;
- var
- Success : Boolean;
- begin
- // This function holds the m_BlockStateLock because it uses
- // m_dwBlockCallerThreadID, m_BlockState and
- // m_hNotifyCallerPinBlockedEvent.
- FBlockStateLock.Lock;
- try
- if (NOT_BLOCKED <> FBlockState) then
- begin
- if(FBlockCallerThreadID = GetCurrentThreadId)
- then Result := VFW_E_PIN_ALREADY_BLOCKED_ON_THIS_THREAD
- else Result := VFW_E_PIN_ALREADY_BLOCKED;
- Exit;
- end;
- Success := DuplicateHandle(GetCurrentProcess,
- NotifyCallerPinBlockedEvent,
- GetCurrentProcess,
- @FNotifyCallerPinBlockedEvent,
- EVENT_MODIFY_STATE,
- False,
- 0);
- if not Success then
- begin
- Result := AmGetLastErrorToHResult;
- Exit;
- end;
- FBlockState := PENDING;
- FBlockCallerThreadID := GetCurrentThreadId;
- // The output pin cannot be blocked if the streaming thread is
- // calling IPin::NewSegment(), IPin::EndOfStream(), IMemInputPin::Receive()
- // or IMemInputPin::ReceiveMultiple() on the connected input pin. Also, it
- // cannot be blocked if the streaming thread is calling DynamicReconnect(),
- // ChangeMediaType() or ChangeOutputFormat().
- // The output pin can be immediately blocked.
- if not StreamingThreadUsingOutputPin then BlockOutputPin();
-
- Result := S_OK;
- finally
- FBlockStateLock.Unlock;
- end;
- end;
- function TBCDynamicOutputPin.UnblockOutputPin: HRESULT;
- begin
- // UnblockOutputPin() holds the m_BlockStateLock because it
- // uses m_BlockState, m_dwBlockCallerThreadID and
- // m_hNotifyCallerPinBlockedEvent.
- FBlockStateLock.Lock;
- try
- if (NOT_BLOCKED = FBlockState) then
- begin
- Result := S_FALSE;
- Exit;
- end;
- // This should not fail because we successfully created the event
- // and we have the security permissions to change it's state.
- ASSERT(SetEvent(FUnblockOutputPinEvent));
- // Cancel the block operation if it's still pending.
- if (FNotifyCallerPinBlockedEvent <> 0) then
- begin
- // This event should not fail because AsynchronousBlockOutputPin() successfully
- // duplicated this handle and we have the appropriate security permissions.
- ASSERT(SetEvent(FNotifyCallerPinBlockedEvent));
- ASSERT(CloseHandle(FNotifyCallerPinBlockedEvent));
- end;
- FBlockState := NOT_BLOCKED;
- FBlockCallerThreadID := 0;
- FNotifyCallerPinBlockedEvent := 0;
- Result := S_OK;
- finally
- FBlockStateLock.Unlock;
- end;
- end;
- procedure TBCDynamicOutputPin.BlockOutputPin;
- begin
- // The caller should always hold the m_BlockStateLock because this function
- // uses m_BlockState and m_hNotifyCallerPinBlockedEvent.
- ASSERT(FBlockStateLock.CritCheckIn);
- // This function should not be called if the streaming thread is modifying
- // the connection state or it's passing data downstream.
- ASSERT(not StreamingThreadUsingOutputPin);
- // This should not fail because we successfully created the event
- // and we have the security permissions to change it's state.
- ASSERT(ResetEvent(FUnblockOutputPinEvent));
- // This event should not fail because AsynchronousBlockOutputPin() successfully
- // duplicated this handle and we have the appropriate security permissions.
- ASSERT(SetEvent(FNotifyCallerPinBlockedEvent));
- ASSERT(CloseHandle(FNotifyCallerPinBlockedEvent));
- FBlockState := BLOCKED;
- FNotifyCallerPinBlockedEvent := 0;
- end;
- procedure TBCDynamicOutputPin.ResetBlockState;
- begin
- end;
- class function TBCDynamicOutputPin.WaitEvent(Event: THandle): HRESULT;
- var
- ReturnValue: DWORD;
- begin
- ReturnValue := WaitForSingleObject(Event, INFINITE);
- case ReturnValue of
- WAIT_OBJECT_0: Result := S_OK;
- WAIT_FAILED : Result := AmGetLastErrorToHResult;
- else
- begin
- {$IFDEF DEBUG}
- DbgLog('An Unexpected case occured in TBCDynamicOutputPin::WaitEvent.');
- {$ENDIF}
- Result := E_UNEXPECTED;
- end;
- end;
- end;
- function TBCDynamicOutputPin.Initialize: HRESULT;
- begin
- FUnblockOutputPinEvent := CreateEvent(nil, // The event will have the default security descriptor.
- True, // This is a manual reset event.
- True, // The event is initially signaled.
- nil); // The event is not named.
- // CreateEvent() returns NULL if an error occurs.
- if (FUnblockOutputPinEvent = 0) then
- begin
- Result := AmGetLastErrorToHResult;
- Exit;
- end;
- // Set flag to say we can reconnect while streaming.
- CanReconnectWhenActive := True;
- Result := S_OK;
- end;
- function TBCDynamicOutputPin.ChangeMediaTypeHelper(const pmt: PAMMediaType): HRESULT;
- var
- InputPinRequirements: ALLOCATOR_PROPERTIES;
- begin
- // The caller should call StartUsingOutputPin() before calling this
- // method.
- ASSERT(StreamingThreadUsingOutputPin);
- Result := FConnected.ReceiveConnection(Self,pmt^);
- if(FAILED(Result)) then Exit;
- Result := SetMediaType(pmt);
- if(FAILED(Result)) then Exit;
- // Does this pin use the local memory transport?
- if(FInputPin <> nil) then
- begin
- // This function assumes that m_pInputPin and m_Connected are
- // two different interfaces to the same object.
- ASSERT(IsEqualObject(FConnected, FInputPin));
- InputPinRequirements.cbAlign := 0;
- InputPinRequirements.cbBuffer := 0;
- InputPinRequirements.cbPrefix := 0;
- InputPinRequirements.cBuffers := 0;
- FInputPin.GetAllocatorRequirements(InputPinRequirements);
- // A zero allignment does not make any sense.
- if(0 = InputPinRequirements.cbAlign)
- then InputPinRequirements.cbAlign := 1;
- Result := FAllocator.Decommit;
- if(FAILED(Result)) then Exit;
- Result := DecideBufferSize(FAllocator, @InputPinRequirements);
- if(FAILED(Result)) then Exit;
- Result := FAllocator.Commit;
- if(FAILED(Result)) then Exit;
- Result := FInputPin.NotifyAllocator(FAllocator, FPinUsesReadOnlyAllocator);
- if(FAILED(Result)) then Exit;
- end;
- Result := S_OK;
- end;
- {$IFDEF DEBUG}
- procedure TBCDynamicOutputPin.AssertValid;
- begin
- // Make sure the object was correctly initialized.
- // This ASSERT only fires if the object failed to initialize
- // and the user ignored the constructor's return code (phr).
- ASSERT(FUnblockOutputPinEvent <> 0);
- // If either of these ASSERTs fire, the user did not correctly call
- // SetConfigInfo().
- ASSERT(FStopEvent <> 0);
- ASSERT(FGraphConfig <> nil);
- // Make sure the block state is consistent.
- FBlockStateLock.Lock;
- try
- // BLOCK_STATE variables only have three legal values: PENDING, BLOCKED and NOT_BLOCKED.
- ASSERT((NOT_BLOCKED = FBlockState) or (PENDING = FBlockState) or (BLOCKED = FBlockState));
- // m_hNotifyCallerPinBlockedEvent is only needed when a block operation cannot complete
- // immediately.
- ASSERT(((FNotifyCallerPinBlockedEvent = 0) and (PENDING <> FBlockState)) or
- ((FNotifyCallerPinBlockedEvent <> 0) and (PENDING = FBlockState)) );
- // m_dwBlockCallerThreadID should always be 0 if the pin is not blocked and
- // the user is not trying to block the pin.
- ASSERT((0 = FBlockCallerThreadID) or (NOT_BLOCKED <> FBlockState));
- // If this ASSERT fires, the streaming thread is using the output pin and the
- // output pin is blocked.
- ASSERT(((0 <> FNumOutstandingOutputPinUsers) and (BLOCKED <> FBlockState)) or
- ((0 = FNumOutstandingOutputPinUsers) and (NOT_BLOCKED <> FBlockState)) or
- ((0 = FNumOutstandingOutputPinUsers) and (NOT_BLOCKED = FBlockState)) );
- finally
- FBlockStateLock.UnLock;
- end;
- end;
- {$ENDIF}
- // milenko end
- { TBCTransformInputPin }
- // enter flushing state. Call default handler to block Receives, then
- // pass to overridable method in filter
- function TBCTransformInputPin.BeginFlush: HRESULT;
- begin
- FTransformFilter.FcsFilter.Lock;
- try
- // Are we actually doing anything?
- ASSERT(FTransformFilter.FOutput <> nil);
- if ((not IsConnected) or (not FTransformFilter.FOutput.IsConnected)) then
- begin
- result := VFW_E_NOT_CONNECTED;
- exit;
- end;
- result := inherited BeginFlush;
- if FAILED(result) then exit;
- result := FTransformFilter.BeginFlush;
- finally
- FTransformFilter.FcsFilter.UnLock;
- end;
- end;
- // provides derived filter a chance to release it's extra interfaces
- function TBCTransformInputPin.BreakConnect: HRESULT;
- begin
- ASSERT(IsStopped);
- FTransformFilter.BreakConnect(PINDIR_INPUT);
- result := inherited BreakConnect;
- end;
- function TBCTransformInputPin.CheckConnect(Pin: IPin): HRESULT;
- begin
- result := FTransformFilter.CheckConnect(PINDIR_INPUT, Pin);
- if FAILED(result) then exit;
- result := inherited CheckConnect(Pin);
- end;
- // check that we can support a given media type
- function TBCTransformInputPin.CheckMediaType(
- mtIn: PAMMediaType): HRESULT;
- begin
- // Check the input type
- result := FTransformFilter.CheckInputType(mtIn);
- if (S_OK <> result) then exit;
- // if the output pin is still connected, then we have
- // to check the transform not just the input format
- if ((FTransformFilter.FOutput <> nil) and
- (FTransformFilter.FOutput.IsConnected)) then
- begin
- result := FTransformFilter.CheckTransform(mtIn,
- FTransformFilter.FOutput.AMMediaType);
- end;
- end;
- function TBCTransformInputPin.CheckStreaming: HRESULT;
- begin
- ASSERT(FTransformFilter.FOutput <> nil);
- if(not FTransformFilter.FOutput.IsConnected) then
- begin
- result := VFW_E_NOT_CONNECTED;
- exit;
- end
- else
- begin
- // Shouldn't be able to get any data if we're not connected!
- ASSERT(IsConnected);
- // we're flushing
- if FFlushing then
- begin
- result := S_FALSE;
- exit;
- end;
- // Don't process stuff in Stopped state
- if IsStopped then
- begin
- result := VFW_E_WRONG_STATE;
- exit;
- end;
- if FRunTimeError then
- begin
- result := VFW_E_RUNTIME_ERROR;
- exit;
- end;
- result := S_OK;
- end;
- end;
- function TBCTransformInputPin.CompleteConnect(ReceivePin: IPin): HRESULT;
- begin
- result := FTransformFilter.CompleteConnect(PINDIR_INPUT, ReceivePin);
- if FAILED(result) then exit;
- result := inherited CompleteConnect(ReceivePin);
- end;
- constructor TBCTransformInputPin.Create(ObjectName: string;
- TransformFilter: TBCTransformFilter; out hr: HRESULT; Name: WideString);
- begin
- inherited Create(ObjectName, TransformFilter, TransformFilter.FcsFilter, hr, Name);
- {$IFDEF DEBUG}
- DbgLog(self, 'TBCTransformInputPin.Create');
- {$ENDIF}
- FTransformFilter := TransformFilter;
- end;
- // leave flushing state.
- // Pass to overridable method in filter, then call base class
- // to unblock receives (finally)
- destructor TBCTransformInputPin.destroy;
- begin
- {$IFDEF DEBUG}
- DbgLog(self, 'TBCTransformInputPin.destroy');
- {$ENDIF}
- inherited;
- end;
- function TBCTransformInputPin.EndFlush: HRESULT;
- begin
- FTransformFilter.FcsFilter.Lock;
- try
- // Are we actually doing anything?
- ASSERT(FTransformFilter.FOutput <> nil);
- if((not IsConnected) or (not FTransformFilter.FOutput.IsConnected)) then
- begin
- result := VFW_E_NOT_CONNECTED;
- exit;
- end;
- result := FTransformFilter.EndFlush;
- if FAILED(result) then exit;
- result := inherited EndFlush;
- finally
- FTransformFilter.FcsFilter.UnLock;
- end;
- end;
- // provide EndOfStream that passes straight downstream
- // (there is no queued data)
- function TBCTransformInputPin.EndOfStream: HRESULT;
- begin
- FTransformFilter.FcsReceive.Lock;
- try
- result := CheckStreaming;
- if (S_OK = result) then
- result := FTransformFilter.EndOfStream;
- finally
- FTransformFilter.FcsReceive.UnLock;
- end;
- end;
- function TBCTransformInputPin.NewSegment(Start, Stop: TReferenceTime;
- Rate: double): HRESULT;
- begin
- // Save the values in the pin
- inherited NewSegment(Start, Stop, Rate);
- result := FTransformFilter.NewSegment(Start, Stop, Rate);
- end;
- function TBCTransformInputPin.QueryId(out id: PWideChar): HRESULT;
- begin
- // milenko start (AMGetWideString was bugged, now the second line is not needed)
- Result := AMGetWideString('In', Id);
- // if id <> nil then result := S_OK else result := S_FALSE;
- // milenko end
- end;
- // here's the next block of data from the stream.
- // AddRef it yourself if you need to hold it beyond the end
- // of this call.
- function TBCTransformInputPin.Receive(pSample: IMediaSample): HRESULT;
- begin
- FTransformFilter.FcsReceive.Lock;
- try
- ASSERT(pSample <> nil);
- // check all is well with the base class
- result := inherited Receive(pSample);
- if (result = S_OK) then
- result := FTransformFilter.Receive(pSample);
- finally
- FTransformFilter.FcsReceive.Unlock;
- end;
- end;
- // set the media type for this connection
- function TBCTransformInputPin.SetMediaType(mt: PAMMediaType): HRESULT;
- begin
- // Set the base class media type (should always succeed)
- result := inherited SetMediaType(mt);
- if FAILED(result) then exit;
- // check the transform can be done (should always succeed)
- ASSERT(SUCCEEDED(FTransformFilter.CheckInputType(mt)));
- result := FTransformFilter.SetMediaType(PINDIR_INPUT,mt);
- end;
- { TBCCritSec }
- constructor TBCCritSec.Create;
- begin
- InitializeCriticalSection(FCritSec);
- {$IFDEF DEBUG}
- FcurrentOwner := 0;
- FlockCount := 0;
- // {$IFDEF TRACE}
- // FTrace := True;
- // {$ELSE}
- // FTrace := FALSE;
- // {$ENDIF}
- {$ENDIF}
- end;
- function TBCCritSec.CritCheckIn: boolean;
- begin
- {$IFDEF DEBUG}
- result := (GetCurrentThreadId = Self.FcurrentOwner);
- {$ELSE}
- result := True;
- {$ENDIF}
- end;
- function TBCCritSec.CritCheckOut: boolean;
- begin
- {$IFDEF DEBUG}
- result := (GetCurrentThreadId <> Self.FcurrentOwner);
- {$ELSE}
- result := false;
- {$ENDIF}
- end;
- destructor TBCCritSec.Destroy;
- begin
- DeleteCriticalSection(FCritSec)
- end;
- procedure TBCCritSec.Lock;
- begin
- {$IFDEF DEBUG}
- if ((FCurrentOwner <> 0) and (FCurrentOwner <> GetCurrentThreadId)) then
- begin
- // already owned, but not by us
- {$IFDEF TRACE}
- DbgLog(format('Thread %d about to wait for lock %x owned by %d',
- [GetCurrentThreadId, longint(self), FCurrentOwner]));
- {$ENDIF}
- end;
- {$ENDIF}
- EnterCriticalSection(FCritSec);
- {$IFDEF DEBUG}
- inc(FLockCount);
- if (FLockCount > 0) then
- begin
- // we now own it for the first time. Set owner information
- FcurrentOwner := GetCurrentThreadId;
- {$IFDEF TRACE}
- DbgLog(format('Thread %d now owns lock %x', [FcurrentOwner, LongInt(self)]));
- {$ENDIF}
- end;
- {$ENDIF}
- end;
- procedure TBCCritSec.UnLock;
- begin
- {$IFDEF DEBUG}
- dec(FlockCount);
- if(FlockCount = 0) then
- begin
- // about to be unowned
- {$IFDEF TRACE}
- DbgLog(format('Thread %d releasing lock %x', [FcurrentOwner, LongInt(Self)]));
- {$ENDIF}
- FcurrentOwner := 0;
- end;
- {$ENDIF}
- LeaveCriticalSection(FCritSec)
- end;
- { TBCTransformFilter }
- // Return S_FALSE to mean "pass the note on upstream"
- // Return NOERROR (Same as S_OK)
- // to mean "I've done something about it, don't pass it on"
- function TBCTransformFilter.AlterQuality(const q: TQuality): HRESULT;
- begin
- result := S_FALSE;
- end;
- // enter flush state. Receives already blocked
- // must override this if you have queued data or a worker thread
- function TBCTransformFilter.BeginFlush: HRESULT;
- begin
- result := NOERROR;
- if (FOutput <> nil) then
- // block receives -- done by caller (CBaseInputPin::BeginFlush)
- // discard queued data -- we have no queued data
- // free anyone blocked on receive - not possible in this filter
- // call downstream
- result := FOutput.DeliverBeginFlush;
- end;
- function TBCTransformFilter.BreakConnect(dir: TPinDirection): HRESULT;
- begin
- result := NOERROR;
- end;
- function TBCTransformFilter.CheckConnect(dir: TPinDirection;
- Pin: IPin): HRESULT;
- begin
- result := NOERROR;
- end;
- function TBCTransformFilter.CompleteConnect(direction: TPinDirection;
- ReceivePin: IPin): HRESULT;
- begin
- result := NOERROR;
- end;
- constructor TBCTransformFilter.Create(ObjectName: string; unk: IUnKnown;
- const clsid: TGUID);
- begin
- FcsFilter := TBCCritSec.Create;
- FcsReceive := TBCCritSec.Create;
- inherited Create(ObjectName,Unk,FcsFilter, clsid);
- FInput := nil;
- FOutput := nil;
- FEOSDelivered := FALSE;
- FQualityChanged:= FALSE;
- FSampleSkipped := FALSE;
- {$ifdef PERF}
- // RegisterPerfId;
- {$endif}
- end;
- constructor TBCTransformFilter.CreateFromFactory(Factory: TBCClassFactory; const Controller: IUnknown);
- begin
- Create(Factory.FName, Controller, Factory.FClassID);
- end;
- destructor TBCTransformFilter.destroy;
- begin
- if FInput <> nil then FInput.Free;
- if FOutput <> nil then FOutput.Free;
- {$IFDEF DEBUG}
- DbgLog(self, 'TBCTransformFilter.destroy');
- {$ENDIF}
- FcsReceive.Free;
- inherited;
- end;
- // leave flush state. must override this if you have queued data
- // or a worker thread
- function TBCTransformFilter.EndFlush: HRESULT;
- begin
- // sync with pushing thread -- we have no worker thread
- // ensure no more data to go downstream -- we have no queued data
- // call EndFlush on downstream pins
- ASSERT(FOutput <> nil);
- result := FOutput.DeliverEndFlush;
- // caller (the input pin's method) will unblock Receives
- end;
- // EndOfStream received. Default behaviour is to deliver straight
- // downstream, since we have no queued data. If you overrode Receive
- // and have queue data, then you need to handle this and deliver EOS after
- // all queued data is sent
- function TBCTransformFilter.EndOfStream: HRESULT;
- begin
- result := NOERROR;
- if (FOutput <> nil) then
- result := FOutput.DeliverEndOfStream;
- end;
- // If Id is In or Out then return the IPin* for that pin
- // creating the pin if need be. Otherwise return NULL with an error.
- function TBCTransformFilter.FindPin(Id: PWideChar; out ppPin: IPin): HRESULT;
- begin
- if(WideString(Id) = 'In') then ppPin := GetPin(0) else
- if(WideString(Id) = 'Out') then ppPin := GetPin(1) else
- begin
- ppPin := nil;
- result := VFW_E_NOT_FOUND;
- exit;
- end;
- result := NOERROR;
- if(ppPin = nil) then result := E_OUTOFMEMORY;
- end;
- // return a non-addrefed CBasePin * for the user to addref if he holds onto it
- // for longer than his pointer to us. We create the pins dynamically when they
- // are asked for rather than in the constructor. This is because we want to
- // give the derived class an oppportunity to return different pin objects
- // We return the objects as and when they are needed. If either of these fails
- // then we return NULL, the assumption being that the caller will realise the
- // whole deal is off and destroy us - which in turn will delete everything.
- function TBCTransformFilter.GetPin(n: integer): TBCBasePin;
- var hr: HRESULT;
- begin
- hr := S_OK;
- // Create an input pin if necessary
- if(FInput = nil) then
- begin
- FInput := TBCTransformInputPin.Create('Transform input pin',
- self, // Owner filter
- hr, // Result code
- 'XForm In'); // Pin name
- // Can't fail
- ASSERT(SUCCEEDED(hr));
- if(FInput = nil) then
- begin
- result := nil;
- exit;
- end;
- FOutput := TBCTransformOutputPin.Create('Transform output pin',
- self, // Owner filter
- hr, // Result code
- 'XForm Out'); // Pin name
- // Can't fail
- ASSERT(SUCCEEDED(hr));
- if(FOutput = nil) then FreeAndNil(FInput);
- end;
- // Return the appropriate pin
- case n of
- 0 : result := FInput;
- 1 : result := FOutput;
- else
- result := nil;
- end;
- end;
- function TBCTransformFilter.GetPinCount: integer;
- begin
- result := 2;
- end;
- // Set up our output sample
- function TBCTransformFilter.InitializeOutputSample(Sample: IMediaSample;
- out OutSample: IMediaSample): HRESULT;
- var
- Props: PAMSample2Properties;
- Flags: DWORD;
- Start, Stop: PReferenceTime;
- OutSample2: IMediaSample2;
- OutProps: TAMSample2Properties;
- MediaStart, MediaEnd: Int64;
- begin
- // default - times are the same
- Props := FInput.SampleProps;
- if FSampleSkipped then Flags := AM_GBF_PREVFRAMESKIPPED else Flags := 0;
- // This will prevent the image renderer from switching us to DirectDraw
- // when we can't do it without skipping frames because we're not on a
- // keyframe. If it really has to switch us, it still will, but then we
- // will have to wait for the next keyframe
- if(not BOOL(Props.dwSampleFlags and AM_SAMPLE_SPLICEPOINT)) then Flags := Flags or AM_GBF_NOTASYNCPOINT;
- ASSERT(FOutput.FAllocator <> nil);
- if BOOL(Props.dwSampleFlags and AM_SAMPLE_TIMEVALID) then Start := @Props.tStart else Start := nil;
- if BOOL(Props.dwSampleFlags and AM_SAMPLE_STOPVALID) then Stop := @Props.tStop else Stop := nil;
- result := FOutput.FAllocator.GetBuffer(OutSample, Start, Stop, Flags);
- if FAILED(result) then exit;
- ASSERT(OutSample <> nil);
- if SUCCEEDED(OutSample.QueryInterface(IID_IMediaSample2, OutSample2)) then
- begin
- ASSERT(SUCCEEDED(OutSample2.GetProperties(4*4, OutProps)));
- OutProps.dwTypeSpecificFlags := Props.dwTypeSpecificFlags;
- OutProps.dwSampleFlags := (OutProps.dwSampleFlags and AM_SAMPLE_TYPECHANGED) or
- (Props.dwSampleFlags and (not AM_SAMPLE_TYPECHANGED));
- OutProps.tStart := Props.tStart;
- OutProps.tStop := Props.tStop;
- OutProps.cbData := (4*4) + (2*8);
- OutSample2.SetProperties((4*4)+(2*8), OutProps);
- if BOOL(Props.dwSampleFlags and AM_SAMPLE_DATADISCONTINUITY) then FSampleSkipped := FALSE;
- OutSample2 := nil;
- end
- else
- begin
- if BOOL(Props.dwSampleFlags and AM_SAMPLE_TIMEVALID) then
- OutSample.SetTime(@Props.tStart, @Props.tStop);
- if BOOL(Props.dwSampleFlags and AM_SAMPLE_SPLICEPOINT) then
- OutSample.SetSyncPoint(True);
- if BOOL(Props.dwSampleFlags and AM_SAMPLE_DATADISCONTINUITY) then
- begin
- OutSample.SetDiscontinuity(True);
- FSampleSkipped := FALSE;
- end;
- // Copy the media times
- if (Sample.GetMediaTime(MediaStart,MediaEnd) = NOERROR) then
- OutSample.SetMediaTime(@MediaStart, @MediaEnd);
- end;
- result := S_OK;
- end;
- function TBCTransformFilter.NewSegment(Start, Stop: TReferenceTime;
- Rate: double): HRESULT;
- begin
- result := S_OK;
- if (FOutput <> nil) then
- result := FOutput.DeliverNewSegment(Start, Stop, Rate);
- end;
- function TBCTransformFilter.Pause: HRESULT;
- begin
- FcsFilter.Lock;
- try
- result := NOERROR;
- if (FState = State_Paused) then
- begin
- // (This space left deliberately blank)
- end
- // If we have no input pin or it isn't yet connected then when we are
- // asked to pause we deliver an end of stream to the downstream filter.
- // This makes sure that it doesn't sit there forever waiting for
- // samples which we cannot ever deliver without an input connection.
- else
- if ((FInput = nil) or (FInput.IsConnected = FALSE)) then
- begin
- if ((FOutput <> nil) and (FEOSDelivered = FALSE)) then
- begin
- FOutput.DeliverEndOfStream;
- FEOSDelivered := True;
- end;
- FState := State_Paused;
- end
- // We may have an input connection but no output connection
- // However, if we have an input pin we do have an output pin
- else
- if (FOutput.IsConnected = FALSE) then
- FState := State_Paused
- else
- begin
- if(FState = State_Stopped) then
- begin
- // allow a class derived from CTransformFilter
- // to know about starting and stopping streaming
- FcsReceive.Lock;
- try
- result := StartStreaming;
- finally
- FcsReceive.UnLock;
- end;
- end;
- if SUCCEEDED(result) then result := inherited Pause;
- end;
- FSampleSkipped := FALSE;
- FQualityChanged := FALSE;
- finally
- FcsFilter.UnLock;
- end;
- end;
- // override this to customize the transform process
- function TBCTransformFilter.Receive(Sample: IMediaSample): HRESULT;
- var
- Props: PAMSample2Properties;
- OutSample: IMediaSample;
- begin
- // Check for other streams and pass them on
- Props := FInput.SampleProps;
- if(Props.dwStreamId <> AM_STREAM_MEDIA) then
- begin
- result := FOutput.FInputPin.Receive(Sample);
- exit;
- end;
- // If no output to deliver to then no point sending us data
- ASSERT(FOutput <> nil) ;
- // Set up the output sample
- result := InitializeOutputSample(Sample, OutSample);
- if FAILED(result) then exit;
- result := Transform(Sample, OutSample);
- if FAILED(result) then
- begin
- {$IFDEF DEBUG}
- DbgLog(self, 'Error from transform');
- {$ENDIF}
- exit;
- end
- else
- begin
- // the Transform() function can return S_FALSE to indicate that the
- // sample should not be delivered; we only deliver the sample if it's
- // really S_OK (same as NOERROR, of course.)
- if (result = NOERROR) then
- begin
- result := FOutput.FInputPin.Receive(OutSample);
- FSampleSkipped := FALSE; // last thing no longer dropped
- end
- else
- begin
- // S_FALSE returned from Transform is a PRIVATE agreement
- // We should return NOERROR from Receive() in this cause because returning S_FALSE
- // from Receive() means that this is the end of the stream and no more data should
- // be sent.
- if (result = S_FALSE) then
- begin
- // Release the sample before calling notify to avoid
- // deadlocks if the sample holds a lock on the system
- // such as DirectDraw buffers do
- OutSample := nil;
- FSampleSkipped := True;
- if not FQualityChanged then
- begin
- NotifyEvent(EC_QUALITY_CHANGE,0,0);
- FQualityChanged := True;
- end;
- result := NOERROR;
- exit;
- end;
- end;
- end;
- // release the output buffer. If the connected pin still needs it,
- // it will have addrefed it itself.
- OutSample := nil;
- end;
- function TBCTransformFilter.SetMediaType(direction: TPinDirection;
- pmt: PAMMediaType): HRESULT;
- begin
- result := NOERROR;
- end;
- // override these two functions if you want to inform something
- // about entry to or exit from streaming state.
- function TBCTransformFilter.StartStreaming: HRESULT;
- begin
- result := NOERROR;
- end;
- // override these so that the derived filter can catch them
- function TBCTransformFilter.Stop: HRESULT;
- begin
- FcsFilter.Lock;
- try
- if(FState = State_Stopped) then
- begin
- result := NOERROR;
- exit;
- end;
- // Succeed the Stop if we are not completely connected
- ASSERT((FInput = nil) or (FOutput <> nil));
- if((FInput = nil) or (FInput.IsConnected = FALSE) or (FOutput.IsConnected = FALSE)) then
- begin
- FState := State_Stopped;
- FEOSDelivered := FALSE;
- result := NOERROR;
- exit;
- end;
- ASSERT(FInput <> nil);
- ASSERT(FOutput <> nil);
- // decommit the input pin before locking or we can deadlock
- FInput.Inactive;
- // synchronize with Receive calls
- FcsReceive.Lock;
- try
- FOutput.Inactive;
- // allow a class derived from CTransformFilter
- // to know about starting and stopping streaming
- result := StopStreaming;
- if SUCCEEDED(result) then
- begin
- // complete the state transition
- FState := State_Stopped;
- FEOSDelivered := FALSE;
- end;
- finally
- FcsReceive.UnLock;
- end;
- finally
- FcsFilter.UnLock;
- end;
- end;
- function TBCTransformFilter.StopStreaming: HRESULT;
- begin
- result := NOERROR;
- end;
- function TBCTransformFilter.Transform(msIn, msout: IMediaSample): HRESULT;
- begin
- {$IFDEF DEBUG}
- DbgLog(self, 'TBCTransformFilter.Transform should never be called');
- {$ENDIF}
- result := E_UNEXPECTED;
- end;
- { TBCTransformOutputPin }
- // provides derived filter a chance to release it's extra interfaces
- function TBCTransformOutputPin.BreakConnect: HRESULT;
- begin
- // Can't disconnect unless stopped
- ASSERT(IsStopped);
- FTransformFilter.BreakConnect(PINDIR_OUTPUT);
- result := inherited BreakConnect;
- end;
- // provides derived filter a chance to grab extra interfaces
- function TBCTransformOutputPin.CheckConnect(Pin: IPin): HRESULT;
- begin
- // we should have an input connection first
- ASSERT(FTransformFilter.FInput <> nil);
- if(FTransformFilter.FInput.IsConnected = FALSE) then
- begin
- result := E_UNEXPECTED;
- exit;
- end;
- result := FTransformFilter.CheckConnect(PINDIR_OUTPUT, Pin);
- if FAILED(result) then exit;
- result := inherited CheckConnect(Pin);
- end;
- // check a given transform - must have selected input type first
- function TBCTransformOutputPin.CheckMediaType(
- mtOut: PAMMediaType): HRESULT;
- begin
- // must have selected input first
- ASSERT(FTransformFilter.FInput <> nil);
- if(FTransformFilter.FInput.IsConnected = FALSE) then
- begin
- result := E_INVALIDARG;
- exit;
- end;
- result := FTransformFilter.CheckTransform(FTransformFilter.FInput.AMMediaType, mtOut);
- end;
- // Let derived class know when the output pin is connected
- function TBCTransformOutputPin.CompleteConnect(ReceivePin: IPin): HRESULT;
- begin
- result := FTransformFilter.CompleteConnect(PINDIR_OUTPUT, ReceivePin);
- if FAILED(result) then exit;
- result := inherited CompleteConnect(ReceivePin);
- end;
- constructor TBCTransformOutputPin.Create(ObjectName: string;
- TransformFilter: TBCTransformFilter; out hr: HRESULT; Name: WideString);
- begin
- inherited create(ObjectName, TransformFilter, TransformFilter.FcsFilter, hr, Name);
- FPosition := nil;
- {$IFDEF DEBUG}
- DbgLog(self, 'TBCTransformOutputPin.Create');
- {$ENDIF}
- FTransformFilter := TransformFilter;
- end;
- function TBCTransformOutputPin.DecideBufferSize(Alloc: IMemAllocator;
- Prop: PAllocatorProperties): HRESULT;
- begin
- result := FTransformFilter.DecideBufferSize(Alloc, Prop);
- end;
- destructor TBCTransformOutputPin.destroy;
- begin
- {$IFDEF DEBUG}
- DbgLog(self, 'TBCTransformOutputPin.Destroy');
- {$ENDIF}
- FPosition := nil;
- inherited;
- end;
- function TBCTransformOutputPin.GetMediaType(Position: integer;
- out MediaType: PAMMediaType): HRESULT;
- begin
- ASSERT(FTransformFilter.FInput <> nil);
- // We don't have any media types if our input is not connected
- if(FTransformFilter.FInput.IsConnected) then
- begin
- result := FTransformFilter.GetMediaType(Position, MediaType);
- exit;
- end
- else
- result := VFW_S_NO_MORE_ITEMS;
- end;
- function TBCTransformOutputPin.NonDelegatingQueryInterface(
- const IID: TGUID; out Obj): HResult;
- begin
- if IsEqualGUID(iid, IID_IMediaPosition) or IsEqualGUID(iid, IID_IMediaSeeking) then
- begin
- // we should have an input pin by now
- ASSERT(FTransformFilter.FInput <> nil);
- if (FPosition = nil) then
- begin
- result := CreatePosPassThru(GetOwner, FALSE, FTransformFilter.FInput, FPosition);
- if FAILED(result) then exit;
- end;
- result := FPosition.QueryInterface(iid, obj);
- end
- else
- result := inherited NonDelegatingQueryInterface(iid, obj);
- end;
- // Override this if you can do something constructive to act on the
- // quality message. Consider passing it upstream as well
- // Pass the quality mesage on upstream.
- function TBCTransformOutputPin.Notify(Sendr: IBaseFilter; q: TQuality): HRESULT;
- begin
- // First see if we want to handle this ourselves
- result := FTransformFilter.AlterQuality(q);
- if (result <> S_FALSE) then exit;
- // S_FALSE means we pass the message on.
- // Find the quality sink for our input pin and send it there
- ASSERT(FTransformFilter.FInput <> nil);
- result := FTransformFilter.FInput.PassNotify(q);
- end;
- function TBCTransformOutputPin.QueryId(out Id: PWideChar): HRESULT;
- begin
- result := AMGetWideString('Out', Id);
- end;
- // called after we have agreed a media type to actually set it in which case
- // we run the CheckTransform function to get the output format type again
- function TBCTransformOutputPin.SetMediaType(pmt: PAMMediaType): HRESULT;
- begin
- ASSERT(FTransformFilter.FInput <> nil);
- ASSERT(not IsEqualGUID(FTransformFilter.FInput.AMMediaType.majortype,GUID_NULL));
- // Set the base class media type (should always succeed)
- result := inherited SetMediaType(pmt);
- if FAILED(result) then exit;
- {$ifdef DEBUG}
- if(FAILED(FTransformFilter.CheckTransform(FTransformFilter.FInput.AMMediaType, pmt))) then
- begin
- DbgLog(self, '*** This filter is accepting an output media type');
- DbgLog(self, ' that it can''t currently transform to. I hope');
- DbgLog(self, ' it''s smart enough to reconnect its input.');
- end;
- {$endif}
- result := FTransformFilter.SetMediaType(PINDIR_OUTPUT,pmt);
- end;
- // milenko start (added TBCVideoTransformFilter conversion)
- { TBCVideoTransformFilter }
- // This class is derived from CTransformFilter, but is specialised to handle
- // the requirements of video quality control by frame dropping.
- // This is a non-in-place transform, (i.e. it copies the data) such as a decoder.
- constructor TBCVideoTransformFilter.Create(Name: WideString; Unk: IUnknown; clsid: TGUID);
- begin
- inherited Create(name, Unk, clsid);
- FitrLate := 0;
- FKeyFramePeriod := 0; // No QM until we see at least 2 key frames
- FFramesSinceKeyFrame := 0;
- FSkipping := False;
- FtDecodeStart := 0;
- FitrAvgDecode := 300000; // 30mSec - probably allows skipping
- FQualityChanged := False;
- {$IFDEF PERF}
- RegisterPerfId();
- {$ENDIF} // PERF
- end;
- destructor TBCVideoTransformFilter.Destroy;
- begin
- inherited Destroy;
- end;
- // Overriden to reset quality management information
- function TBCVideoTransformFilter.EndFlush: HRESULT;
- begin
- FcsReceive.Lock;
- try
- // Reset our stats
- //
- // Note - we don't want to call derived classes here,
- // we only want to reset our internal variables and this
- // is a convenient way to do it
- StartStreaming;
- Result := inherited EndFlush;
- finally
- FcsReceive.UnLock;
- end;
- end;
- {$IFDEF PERF}
- procedure TBCVideoTransformFilter.RegisterPerfId;
- begin
- FidSkip := MSR_REGISTER('Video Transform Skip frame');
- FidFrameType := MSR_REGISTER('Video transform frame type');
- FidLate := MSR_REGISTER('Video Transform Lateness');
- FidTimeTillKey := MSR_REGISTER('Video Transform Estd. time to next key');
- // inherited RegisterPerfId;
- end;
- {$ENDIF}
- function TBCVideoTransformFilter.StartStreaming: HRESULT;
- begin
- FitrLate := 0;
- FKeyFramePeriod := 0; // No QM until we see at least 2 key frames
- FFramesSinceKeyFrame := 0;
- FSkipping := False;
- FtDecodeStart := 0;
- FitrAvgDecode := 300000; // 30mSec - probably allows skipping
- FQualityChanged := False;
- FSampleSkipped := False;
- Result := NOERROR;
- end;
- // Reset our quality management state
- function TBCVideoTransformFilter.AbortPlayback(hr: HRESULT): HRESULT;
- begin
- NotifyEvent(EC_ERRORABORT, hr, 0);
- FOutput.DeliverEndOfStream;
- Result := hr;
- end;
- // Receive()
- //
- // Accept a sample from upstream, decide whether to process it
- // or drop it. If we process it then get a buffer from the
- // allocator of the downstream connection, transform it into the
- // new buffer and deliver it to the downstream filter.
- // If we decide not to process it then we do not get a buffer.
- // Remember that although this code will notice format changes coming into
- // the input pin, it will NOT change its output format if that results
- // in the filter needing to make a corresponding output format change. Your
- // derived filter will have to take care of that. (eg. a palette change if
- // the input and output is an 8 bit format). If the input sample is discarded
- // and nothing is sent out for this Receive, please remember to put the format
- // change on the first output sample that you actually do send.
- // If your filter will produce the same output type even when the input type
- // changes, then this base class code will do everything you need.
- function TBCVideoTransformFilter.Receive(Sample: IMediaSample): HRESULT;
- var
- pmtOut, pmt: PAMMediaType;
- pOutSample: IMediaSample;
- {$IFDEF DEBUG}
- fccOut: TGUID;
- lCompression: LongInt;
- lBitCount: LongInt;
- lStride: LongInt;
- rcS: TRect;
- rcT: TRect;
- rcS1: TRect;
- rcT1: TRect;
- {$ENDIF}
- begin
- // If the next filter downstream is the video renderer, then it may
- // be able to operate in DirectDraw mode which saves copying the data
- // and gives higher performance. In that case the buffer which we
- // get from GetDeliveryBuffer will be a DirectDraw buffer, and
- // drawing into this buffer draws directly onto the display surface.
- // This means that any waiting for the correct time to draw occurs
- // during GetDeliveryBuffer, and that once the buffer is given to us
- // the video renderer will count it in its statistics as a frame drawn.
- // This means that any decision to drop the frame must be taken before
- // calling GetDeliveryBuffer.
- ASSERT(FcsReceive.CritCheckIn);
- ASSERT(Sample <> nil);
- // If no output pin to deliver to then no point sending us data
- ASSERT (FOutput <> nil) ;
- // The source filter may dynamically ask us to start transforming from a
- // different media type than the one we're using now. If we don't, we'll
- // draw garbage. (typically, this is a palette change in the movie,
- // but could be something more sinister like the compression type changing,
- // or even the video size changing)
- Sample.GetMediaType(pmt);
- if (pmt <> nil) and (pmt.pbFormat <> nil) then
- begin
- // spew some debug output
- ASSERT(not IsEqualGUID(pmt.majortype, GUID_NULL));
- {$IFDEF DEBUG}
- fccOut := pmt.subtype;
- lCompression := PVideoInfoHeader(pmt.pbFormat).bmiHeader.biCompression;
- lBitCount := PVideoInfoHeader(pmt.pbFormat).bmiHeader.biBitCount;
- lStride := (PVideoInfoHeader(pmt.pbFormat).bmiHeader.biWidth * lBitCount + 7) div 8;
- lStride := (lStride + 3) and not 3;
- rcS1 := PVideoInfoHeader(pmt.pbFormat).rcSource;
- rcT1 := PVideoInfoHeader(pmt.pbFormat).rcTarget;
- DbgLog(Self,'Changing input type on the fly to');
- DbgLog(Self,'FourCC: ' + inttohex(fccOut.D1,8) + ' Compression: ' + inttostr(lCompression) +
- ' BitCount: ' + inttostr(lBitCount));
- DbgLog(Self,'biHeight: ' + inttostr(PVideoInfoHeader(pmt.pbFormat).bmiHeader.biHeight) +
- ' rcDst: (' + inttostr(rcT1.left) + ', ' + inttostr(rcT1.top) + ', ' +
- inttostr(rcT1.right) + ', ' + inttostr(rcT1.bottom) + ')');
- DbgLog(Self,'rcSrc: (' + inttostr(rcS1.left) + ', ' + inttostr(rcS1.top) + ', ' +
- inttostr(rcS1.right) + ', ' + inttostr(rcS1.bottom) + ') Stride' + inttostr(lStride));
- {$ENDIF}
- // now switch to using the new format. I am assuming that the
- // derived filter will do the right thing when its media type is
- // switched and streaming is restarted.
- StopStreaming();
- CopyMediaType(FInput.AMMediaType,pmt);
- DeleteMediaType(pmt);
- // if this fails, playback will stop, so signal an error
- Result := StartStreaming;
- if (FAILED(Result)) then
- begin
- Result := AbortPlayback(Result);
- Exit;
- end;
- end;
- // Now that we have noticed any format changes on the input sample, it's
- // OK to discard it.
- if ShouldSkipFrame(Sample) then
- begin
- {$IFDEF PERF}
- // MSR_NOTE(m_idSkip);
- {$ENDIF}
- FSampleSkipped := True;
- Result := NOERROR;
- Exit;
- end;
- // Set up the output sample
- Result := InitializeOutputSample(Sample, pOutSample);
- if (FAILED(Result)) then Exit;
- FSampleSkipped := False;
- // The renderer may ask us to on-the-fly to start transforming to a
- // different format. If we don't obey it, we'll draw garbage
- pOutSample.GetMediaType(pmtOut);
- if (pmtOut <> nil) and (pmtOut.pbFormat <> nil) then
- begin
- // spew some debug output
- ASSERT(not IsEqualGUID(pmtOut.majortype, GUID_NULL));
- {$IFDEF DEBUG}
- fccOut := pmtOut.subtype;
- lCompression := PVideoInfoHeader(pmtOut.pbFormat).bmiHeader.biCompression;
- lBitCount := PVideoInfoHeader(pmtOut.pbFormat).bmiHeader.biBitCount;
- lStride := (PVideoInfoHeader(pmtOut.pbFormat).bmiHeader.biWidth * lBitCount + 7) div 8;
- lStride := (lStride + 3) and not 3;
- rcS := PVideoInfoHeader(pmtOut.pbFormat).rcSource;
- rcT := PVideoInfoHeader(pmtOut.pbFormat).rcTarget;
- DbgLog(Self,'Changing input type on the fly to');
- DbgLog(Self,'FourCC: ' + inttohex(fccOut.D1,8) + ' Compression: ' + inttostr(lCompression) +
- ' BitCount: ' + inttostr(lBitCount));
- DbgLog(Self,'biHeight: ' + inttostr(PVideoInfoHeader(pmtOut.pbFormat).bmiHeader.biHeight) +
- ' rcDst: (' + inttostr(rcT1.left) + ', ' + inttostr(rcT1.top) + ', ' +
- inttostr(rcT1.right) + ', ' + inttostr(rcT1.bottom) + ')');
- DbgLog(Self,'rcSrc: (' + inttostr(rcS1.left) + ', ' + inttostr(rcS1.top) + ', ' +
- inttostr(rcS1.right) + ', ' + inttostr(rcS1.bottom) + ') Stride' + inttostr(lStride));
- {$ENDIF}
- // now switch to using the new format. I am assuming that the
- // derived filter will do the right thing when its media type is
- // switched and streaming is restarted.
- StopStreaming();
- CopyMediaType(FOutput.AMMediaType,pmtOut);
- DeleteMediaType(pmtOut);
- Result := StartStreaming;
- if (SUCCEEDED(Result)) then
- begin
- // a new format, means a new empty buffer, so wait for a keyframe
- // before passing anything on to the renderer.
- // !!! a keyframe may never come, so give up after 30 frames
- {$IFDEF DEBUG}
- DbgLog(Self,'Output format change means we must wait for a keyframe');
- {$ENDIF}
- FWaitForKey := 30;
- // if this fails, playback will stop, so signal an error
- end else
- begin
- // Must release the sample before calling AbortPlayback
- // because we might be holding the win16 lock or
- // ddraw lock
- pOutSample := nil;
- AbortPlayback(Result);
- Exit;
- end;
- end;
- // After a discontinuity, we need to wait for the next key frame
- if (Sample.IsDiscontinuity = S_OK) then
- begin
- {$IFDEF DEBUG}
- DbgLog(Self,'Non-key discontinuity - wait for keyframe');
- {$ENDIF}
- FWaitForKey := 30;
- end;
- // Start timing the transform (and log it if PERF is defined)
- if (SUCCEEDED(Result)) then
- begin
- FtDecodeStart := timeGetTime;
- {$IFDEF PERF}
- // MSR_START(FidTransform); // not added in conversion
- {$ENDIF}
- // have the derived class transform the data
- Result := Transform(Sample, pOutSample);
- // Stop the clock (and log it if PERF is defined)
- {$IFDEF PERF}
- // MSR_STOP(m_idTransform); // not added in conversion
- {$ENDIF}
- FtDecodeStart := timeGetTime - int64(FtDecodeStart);
- FitrAvgDecode := Round(FtDecodeStart * (10000 / 16) + 15 * (FitrAvgDecode / 16));
- // Maybe we're waiting for a keyframe still?
- if (FWaitForKey > 0) then dec(FWaitForKey);
- if (FWaitForKey > 0) and (Sample.IsSyncPoint = S_OK) then BOOL(FWaitForKey) := False;
- // if so, then we don't want to pass this on to the renderer
- if (FWaitForKey > 0) and (Result = NOERROR) then
- begin
- {$IFDEF DEBUG}
- DbgLog(Self,'still waiting for a keyframe');
- Result := S_FALSE;
- {$ENDIF}
- end;
- end;
- if (FAILED(Result)) then
- begin
- {$IFDEF DEBUG}
- DbgLog(Self,'Error from video transform');
- {$ENDIF}
- end else
- begin
- // the Transform() function can return S_FALSE to indicate that the
- // sample should not be delivered; we only deliver the sample if it's
- // really S_OK (same as NOERROR, of course.)
- // Try not to return S_FALSE to a direct draw buffer (it's wasteful)
- // Try to take the decision earlier - before you get it.
- if (Result = NOERROR) then
- begin
- Result := FOutput.Deliver(pOutSample);
- end else
- begin
- // S_FALSE returned from Transform is a PRIVATE agreement
- // We should return NOERROR from Receive() in this case because returning S_FALSE
- // from Receive() means that this is the end of the stream and no more data should
- // be sent.
- if (S_FALSE = Result) then
- begin
- // We must Release() the sample before doing anything
- // like calling the filter graph because having the
- // sample means we may have the DirectDraw lock
- // (== win16 lock on some versions)
- pOutSample := nil;
- FSampleSkipped := True;
- if not FQualityChanged then
- begin
- FQualityChanged := True;
- NotifyEvent(EC_QUALITY_CHANGE,0,0);
- end;
- Result := NOERROR;
- Exit;
- end;
- end;
- end;
- // release the output buffer. If the connected pin still needs it,
- // it will have addrefed it itself.
- pOutSample := nil;
- ASSERT(FcsReceive.CritCheckIn);
- end;
- function TBCVideoTransformFilter.AlterQuality(const q: TQuality): HRESULT;
- begin
- // to reduce the amount of 64 bit arithmetic, m_itrLate is an int.
- // +, -, >, == etc are not too bad, but * and / are painful.
- if (FitrLate > 300000000) then
- begin
- // Avoid overflow and silliness - more than 30 secs late is already silly
- FitrLate := 300000000;
- end else
- begin
- FitrLate := integer(q.Late);
- end;
- // We ignore the other fields
- // We're actually not very good at handling this. In non-direct draw mode
- // most of the time can be spent in the renderer which can skip any frame.
- // In that case we'd rather the renderer handled things.
- // Nevertheless we will keep an eye on it and if we really start getting
- // a very long way behind then we will actually skip - but we'll still tell
- // the renderer (or whoever is downstream) that they should handle quality.
- Result := E_FAIL; // Tell the renderer to do his thing.
- end;
- function TBCVideoTransformFilter.ShouldSkipFrame(pIn: IMediaSample): Boolean;
- var
- Start, StopAt: TReferenceTime;
- itrFrame: integer;
- it: integer;
- begin
- Result := pIn.GetTime(Start, StopAt) = S_OK;
- // Don't skip frames with no timestamps
- if not Result then Exit;
- itrFrame := integer(StopAt - Start); // frame duration
- if(S_OK = pIn.IsSyncPoint) then
- begin
- {$IFDEF PERF}
- MSR_INTEGER(FidFrameType, 1);
- {$ENDIF}
- if (FKeyFramePeriod < FFramesSinceKeyFrame) then
- begin
- // record the max
- FKeyFramePeriod := FFramesSinceKeyFrame;
- end;
- FFramesSinceKeyFrame := 0;
- FSkipping := False;
- end else
- begin
- {$IFDEF PERF}
- MSR_INTEGER(FidFrameType, 2);
- {$ENDIF}
- if (FFramesSinceKeyFrame > FKeyFramePeriod) and (FKeyFramePeriod > 0) then
- begin
- // We haven't seen the key frame yet, but we were clearly being
- // overoptimistic about how frequent they are.
- FKeyFramePeriod := FFramesSinceKeyFrame;
- end;
- end;
- // Whatever we might otherwise decide,
- // if we are taking only a small fraction of the required frame time to decode
- // then any quality problems are actually coming from somewhere else.
- // Could be a net problem at the source for instance. In this case there's
- // no point in us skipping frames here.
- if (FitrAvgDecode * 4 > itrFrame) then
- begin
- // Don't skip unless we are at least a whole frame late.
- // (We would skip B frames if more than 1/2 frame late, but they're safe).
- if (FitrLate > itrFrame) then
- begin
- // Don't skip unless the anticipated key frame would be no more than
- // 1 frame early. If the renderer has not been waiting (we *guess*
- // it hasn't because we're late) then it will allow frames to be
- // played early by up to a frame.
- // Let T = Stream time from now to anticipated next key frame
- // = (frame duration) * (KeyFramePeriod - FramesSinceKeyFrame)
- // So we skip if T - Late < one frame i.e.
- // (duration) * (freq - FramesSince) - Late < duration
- // or (duration) * (freq - FramesSince - 1) < Late
- // We don't dare skip until we have seen some key frames and have
- // some idea how often they occur and they are reasonably frequent.
- if (FKeyFramePeriod > 0) then
- begin
- // It would be crazy - but we could have a stream with key frames
- // a very long way apart - and if they are further than about
- // 3.5 minutes apart then we could get arithmetic overflow in
- // reference time units. Therefore we switch to mSec at this point
- it := (itrFrame div 10000) * (FKeyFramePeriod - FFramesSinceKeyFrame - 1);
- {$IFDEF PERF}
- MSR_INTEGER(FidTimeTillKey, it);
- {$ENDIF}
- // For debug - might want to see the details - dump them as scratch pad
- {$IFDEF VTRANSPERF}
- MSR_INTEGER(0, itrFrame);
- MSR_INTEGER(0, FFramesSinceKeyFrame);
- MSR_INTEGER(0, FKeyFramePeriod);
- {$ENDIF}
- if (FitrLate div 10000 > it) then
- begin
- FSkipping := True;
- // Now we are committed. Once we start skipping, we
- // cannot stop until we hit a key frame.
- end else
- begin
- {$IFDEF VTRANSPERF}
- MSR_INTEGER(0, 777770); // not near enough to next key
- {$ENDIF}
- end;
- end else
- begin
- {$IFDEF VTRANSPERF}
- MSR_INTEGER(0, 777771); // Next key not predictable
- {$ENDIF}
- end;
- end else
- begin
- {$IFDEF VTRANSPERF}
- MSR_INTEGER(0, 777772); // Less than one frame late
- MSR_INTEGER(0, FitrLate);
- MSR_INTEGER(0, itrFrame);
- {$ENDIF}
- end;
- end else
- begin
- {$IFDEF VTRANSPERF}
- MSR_INTEGER(0, 777773); // Decode time short - not not worth skipping
- MSR_INTEGER(0, FitrAvgDecode);
- MSR_INTEGER(0, itrFrame);
- {$ENDIF}
- end;
- inc(FFramesSinceKeyFrame);
- if FSkipping then
- begin
- // We will count down the lateness as we skip each frame.
- // We re-assess each frame. The key frame might not arrive when expected.
- // We reset m_itrLate if we get a new Quality message, but actually that's
- // not likely because we're not sending frames on to the Renderer. In
- // fact if we DID get another one it would mean that there's a long
- // pipe between us and the renderer and we might need an altogether
- // better strategy to avoid hunting!
- FitrLate := FitrLate - itrFrame;
- end;
- {$IFDEF PERF}
- MSR_INTEGER(FidLate, integer(FitrLate div 10000)); // Note how late we think we are
- {$ENDIF}
- if FSkipping then
- begin
- if not FQualityChanged then
- begin
- FQualityChanged := True;
- NotifyEvent(EC_QUALITY_CHANGE,0,0);
- end;
- end;
- Result := FSkipping;
- end;
- // milenko end
- { TCTransInPlaceInputPin }
- function TBCTransInPlaceInputPin.CheckMediaType(
- pmt: PAMMediaType): HRESULT;
- begin
- result := FTIPFilter.CheckInputType(pmt);
- if (result <> S_OK) then exit;
- if FTIPFilter.FOutput.IsConnected then
- result := FTIPFilter.FOutput.GetConnected.QueryAccept(pmt^)
- else
- result := S_OK;
- end;
- function TBCTransInPlaceInputPin.EnumMediaTypes(
- out ppEnum: IEnumMediaTypes): HRESULT;
- begin
- // Can only pass through if connected
- if (not FTIPFilter.FOutput.IsConnected) then
- begin
- result := VFW_E_NOT_CONNECTED;
- exit;
- end;
- result := FTIPFilter.FOutput.GetConnected.EnumMediaTypes(ppEnum);
- end;
- function TBCTransInPlaceInputPin.GetAllocator(
- out Allocator: IMemAllocator): HRESULT;
- begin
- FLock.Lock;
- try
- if FTIPFilter.FOutput.IsConnected then
- begin
- // Store the allocator we got
- result := FTIPFilter.OutputPin.ConnectedIMemInputPin.GetAllocator(Allocator);
- if SUCCEEDED(result) then
- FTIPFilter.OutputPin.SetAllocator(Allocator);
- end
- else
- begin
- // Help upstream filter (eg TIP filter which is having to do a copy)
- // by providing a temp allocator here - we'll never use
- // this allocator because when our output is connected we'll
- // reconnect this pin
- result := inherited GetAllocator(Allocator);
- end;
- finally
- FLock.UnLock;
- end;
- end;
- function TBCTransInPlaceInputPin.GetAllocatorRequirements(
- props: PAllocatorProperties): HRESULT;
- begin
- if FTIPFilter.FOutput.IsConnected then
- result := FTIPFilter.OutputPin.ConnectedIMemInputPin.GetAllocatorRequirements(Props^)
- else
- result := E_NOTIMPL;
- end;
- function TBCTransInPlaceInputPin.NotifyAllocator(Allocator: IMemAllocator;
- ReadOnly: BOOL): HRESULT;
- var
- OutputAllocator: IMemAllocator;
- Props, Actual: TAllocatorProperties;
- begin
- result := S_OK;
- FLock.Lock;
- try
- FReadOnly := ReadOnly;
- // If we modify data then don't accept the allocator if it's
- // the same as the output pin's allocator
- // If our output is not connected just accept the allocator
- // We're never going to use this allocator because when our
- // output pin is connected we'll reconnect this pin
- if not FTIPFilter.OutputPin.IsConnected then
- begin
- result := inherited NotifyAllocator(Allocator, ReadOnly);
- exit;
- end;
- // If the allocator is read-only and we're modifying data
- // and the allocator is the same as the output pin's
- // then reject
- if (FReadOnly and FTIPFilter.FModifiesData) then
- begin
- OutputAllocator := FTIPFilter.OutputPin.PeekAllocator;
- // Make sure we have an output allocator
- if (OutputAllocator = nil) then
- begin
- result := FTIPFilter.OutputPin.ConnectedIMemInputPin.GetAllocator(OutputAllocator);
- if FAILED(result) then result := CreateMemoryAllocator(OutputAllocator);
- if SUCCEEDED(result) then
- begin
- FTIPFilter.OutputPin.SetAllocator(OutputAllocator);
- OutputAllocator := nil;
- end;
- end;
- if (Allocator = OutputAllocator) then
- begin
- result := E_FAIL;
- exit;
- end
- else
- if SUCCEEDED(result) then
- begin
- // Must copy so set the allocator properties on the output
- result := Allocator.GetProperties(Props);
- if SUCCEEDED(result) then
- result := OutputAllocator.SetProperties(Props, Actual);
- if SUCCEEDED(result) then
- begin
- if ((Props.cBuffers > Actual.cBuffers)
- or (Props.cbBuffer > Actual.cbBuffer)
- or (Props.cbAlign > Actual.cbAlign)) then
- result := E_FAIL;
- end;
- // Set the allocator on the output pin
- if SUCCEEDED(result) then
- result := FTIPFilter.OutputPin.ConnectedIMemInputPin.NotifyAllocator(OutputAllocator, FALSE);
- end;
- end
- else
- begin
- result := FTIPFilter.OutputPin.ConnectedIMemInputPin.NotifyAllocator(Allocator, ReadOnly);
- if SUCCEEDED(result) then FTIPFilter.OutputPin.SetAllocator(Allocator);
- end;
- if SUCCEEDED(result) then
- begin
- // It's possible that the old and the new are the same thing.
- // AddRef before release ensures that we don't unload it.
- Allocator._AddRef;
- if (FAllocator <> nil) then FAllocator := nil;
- Pointer(FAllocator) := Pointer(Allocator); // We have an allocator for the input pin
- end;
- finally
- FLock.UnLock;
- end;
- end;
- function TBCTransInPlaceInputPin.PeekAllocator: IMemAllocator;
- begin
- result := FAllocator;
- end;
- constructor TBCTransInPlaceInputPin.Create(ObjectName: string;
- Filter: TBCTransInPlaceFilter; out hr: HRESULT; Name: WideString);
- begin
- inherited Create(ObjectName, Filter, hr, Name);
- FReadOnly := FALSE;
- FTIPFilter := Filter;
- {$IFDEF DEBUG}
- DbgLog(self, 'TBCTransInPlaceInputPin.Create');
- {$ENDIF}
- end;
- { TBCTransInPlaceOutputPin }
- function TBCTransInPlaceOutputPin.CheckMediaType(
- pmt: PAMMediaType): HRESULT;
- begin
- // Don't accept any output pin type changes if we're copying
- // between allocators - it's too late to change the input
- // allocator size.
- if (FTIPFilter.UsingDifferentAllocators and (not FFilter.IsStopped)) then
- begin
- if TBCMediaType(pmt).Equal(@Fmt) then result := S_OK else result := VFW_E_TYPE_NOT_ACCEPTED;
- exit;
- end;
- // Assumes the type does not change. That's why we're calling
- // CheckINPUTType here on the OUTPUT pin.
- result := FTIPFilter.CheckInputType(pmt);
- if (result <> S_OK) then exit;
- if (FTIPFilter.FInput.IsConnected) then
- result := FTIPFilter.FInput.GetConnected.QueryAccept(pmt^)
- else
- result := S_OK;
- end;
- function TBCTransInPlaceOutputPin.ConnectedIMemInputPin: IMemInputPin;
- begin
- pointer(result) := pointer(FInputPin);
- end;
- constructor TBCTransInPlaceOutputPin.Create(ObjectName: string;
- Filter: TBCTransInPlaceFilter; out hr: HRESULT; Name: WideString);
- begin
- inherited Create(ObjectName, Filter, hr, Name);
- FTIPFilter := Filter;
- {$IFDEF DEBUG}
- DbgLog(self, 'TBCTransInPlaceOutputPin.Create');
- {$ENDIF}
- end;
- function TBCTransInPlaceOutputPin.EnumMediaTypes(
- out ppEnum: IEnumMediaTypes): HRESULT;
- begin
- // Can only pass through if connected.
- if not FTIPFilter.FInput.IsConnected then
- result := VFW_E_NOT_CONNECTED
- else
- result := FTIPFilter.FInput.GetConnected.EnumMediaTypes(ppEnum);
- end;
- function TBCTransInPlaceOutputPin.PeekAllocator: IMemAllocator;
- begin
- result := FAllocator;
- end;
- procedure TBCTransInPlaceOutputPin.SetAllocator(Allocator: IMemAllocator);
- begin
- Allocator._AddRef;
- if(FAllocator <> nil) then FAllocator._Release;
- Pointer(FAllocator) := Pointer(Allocator);
- end;
- { TBCTransInPlaceFilter }
- function TBCTransInPlaceFilter.CheckTransform(mtIn,
- mtOut: PAMMediaType): HRESULT;
- begin
- result := S_OK;
- end;
- // dir is the direction of our pin.
- // pReceivePin is the pin we are connecting to.
- function TBCTransInPlaceFilter.CompleteConnect(dir: TPinDirection;
- ReceivePin: IPin): HRESULT;
- var
- pmt: PAMMediaType;
- begin
- ASSERT(FInput <> nil);
- ASSERT(FOutput <> nil);
- // if we are not part of a graph, then don't indirect the pointer
- // this probably prevents use of the filter without a filtergraph
- if(FGraph = nil) then
- begin
- result := VFW_E_NOT_IN_GRAPH;
- exit;
- end;
- // Always reconnect the input to account for buffering changes
- //
- // Because we don't get to suggest a type on ReceiveConnection
- // we need another way of making sure the right type gets used.
- //
- // One way would be to have our EnumMediaTypes return our output
- // connection type first but more deterministic and simple is to
- // call ReconnectEx passing the type we want to reconnect with
- // via the base class ReconeectPin method.
- if(dir = PINDIR_OUTPUT) then
- begin
- if FInput.IsConnected then
- begin
- result := ReconnectPin(FInput, FOutput.AMMediaType);
- exit;
- end;
- result := NOERROR;
- exit;
- end;
- ASSERT(dir = PINDIR_INPUT);
- // Reconnect output if necessary
- if FOutput.IsConnected then
- begin
- pmt := FInput.CurrentMediaType.MediaType;
- if (not TBCMediaType(pmt).Equal(FOutput.CurrentMediaType.MediaType)) then
- begin
- result := ReconnectPin(FOutput, FInput.CurrentMediaType.MediaType);
- exit;
- end;
- end;
- result := NOERROR;
- end;
- function TBCTransInPlaceFilter.Copy(Source: IMediaSample): IMediaSample;
- var
- Start, Stop: TReferenceTime;
- Time: boolean;
- pStartTime, pEndTime: PReferenceTime;
- TimeStart, TimeEnd: Int64;
- Flags: DWORD;
- Sample2: IMediaSample2;
- props: PAMSample2Properties;
- MediaType: PAMMediaType;
- DataLength: LongInt;
- SourceBuffer, DestBuffer: PByte;
- SourceSize, DestSize: LongInt;
- hr: hresult;
- begin
- Time := (Source.GetTime(Start, Stop) = S_OK);
- // this may block for an indeterminate amount of time
- if Time then
- begin
- pStartTime := @Start;
- pEndTime := @Stop;
- end
- else
- begin
- pStartTime := nil;
- pEndTime := nil;
- end;
- if FSampleSkipped then Flags := AM_GBF_PREVFRAMESKIPPED else Flags := 0;
- hr := OutputPin.PeekAllocator.GetBuffer(result, pStartTime, pEndTime, Flags);
- if FAILED(hr) then exit;
- ASSERT(result <> nil);
- if(SUCCEEDED(result.QueryInterface(IID_IMediaSample2, Sample2))) then
- begin
- props := FInput.SampleProps;
- hr := Sample2.SetProperties(SizeOf(TAMSample2Properties) - (4*2), props^);
- Sample2 := nil;
- if FAILED(hr) then
- begin
- result := nil;
- exit;
- end;
- end
- else
- begin
- if Time then result.SetTime(@Start, @Stop);
- if (Source.IsSyncPoint = S_OK) then result.SetSyncPoint(True);
- if ((Source.IsDiscontinuity = S_OK) or FSampleSkipped) then result.SetDiscontinuity(True);
- if (Source.IsPreroll = S_OK) then result.SetPreroll(True);
- // Copy the media type
- if (Source.GetMediaType(MediaType) = S_OK) then
- begin
- result.SetMediaType(MediaType^);
- DeleteMediaType(MediaType);
- end;
- end;
- FSampleSkipped := FALSE;
- // Copy the sample media times
- if (Source.GetMediaTime(TimeStart, TimeEnd) = NOERROR) then
- result.SetMediaTime(@TimeStart,@TimeEnd);
- // Copy the actual data length and the actual data.
- DataLength := Source.GetActualDataLength;
- result.SetActualDataLength(DataLength);
- // Copy the sample data
- SourceSize := Source.GetSize;
- DestSize := result.GetSize;
- // milenko start get rid of compiler warnings
- if (DestSize < SourceSize) then
- begin
- end;
- // milenko end
- ASSERT(DestSize >= SourceSize, format('DestSize (%d) < SourceSize (%d)',[DestSize, SourceSize]));
- ASSERT(DestSize >= DataLength);
- Source.GetPointer(SourceBuffer);
- result.GetPointer(DestBuffer);
- ASSERT((DestSize = 0) or (SourceBuffer <> nil) and (DestBuffer <> nil));
- CopyMemory(DestBuffer, SourceBuffer, DataLength);
- end;
- constructor TBCTransInPlaceFilter.Create(ObjectName: string;
- unk: IUnKnown; clsid: TGUID; out hr: HRESULT; ModifiesData: boolean);
- begin
- inherited create(ObjectName, Unk, clsid);
- FModifiesData := ModifiesData;
- end;
- constructor TBCTransInPlaceFilter.CreateFromFactory(Factory: TBCClassFactory;
- const Controller: IUnknown);
- begin
- inherited create(FacTory.FName, Controller, FacTory.FClassID);
- FModifiesData := True;
- end;
- // Tell the output pin's allocator what size buffers we require.
- // *pAlloc will be the allocator our output pin is using.
- function TBCTransInPlaceFilter.DecideBufferSize(Alloc: IMemAllocator;
- propInputRequest: PAllocatorProperties): HRESULT;
- var Request, Actual: TAllocatorProperties;
- begin
- // If we are connected upstream, get his views
- if FInput.IsConnected then
- begin
- // Get the input pin allocator, and get its size and count.
- // we don't care about his alignment and prefix.
- result := InputPin.FAllocator.GetProperties(Request);
- //Request.cbBuffer := 230400;
- if FAILED(result) then exit; // Input connected but with a secretive allocator - enough!
- end
- else
- begin
- // We're reduced to blind guessing. Let's guess one byte and if
- // this isn't enough then when the other pin does get connected
- // we can revise it.
- ZeroMemory(@Request, sizeof(Request));
- Request.cBuffers := 1;
- Request.cbBuffer := 1;
- end;
- {$IFDEF DEBUG}
- DbgLog(self, 'Setting Allocator Requirements');
- DbgLog(self, format('Count %d, Size %d',[Request.cBuffers, Request.cbBuffer]));
- {$ENDIF}
- // Pass the allocator requirements to our output side
- // but do a little sanity checking first or we'll just hit
- // asserts in the allocator.
- propInputRequest.cBuffers := Request.cBuffers;
- propInputRequest.cbBuffer := Request.cbBuffer;
- if (propInputRequest.cBuffers <= 0) then propInputRequest.cBuffers := 1;
- if (propInputRequest.cbBuffer <= 0) then propInputRequest.cbBuffer := 1;
- result := Alloc.SetProperties(propInputRequest^, Actual);
- if FAILED(result) then exit;
- {$IFDEF DEBUG}
- DbgLog(self, 'Obtained Allocator Requirements');
- DbgLog(self, format('Count %d, Size %d, Alignment %d', [Actual.cBuffers, Actual.cbBuffer, Actual.cbAlign]));
- {$ENDIF}
- // Make sure we got the right alignment and at least the minimum required
- if ((Request.cBuffers > Actual.cBuffers)
- or (Request.cbBuffer > Actual.cbBuffer)
- or (Request.cbAlign > Actual.cbAlign)) then
- result := E_FAIL
- else
- result := NOERROR;
- end;
- function TBCTransInPlaceFilter.GetMediaType(Position: integer;
- out MediaType: PAMMediaType): HRESULT;
- begin
- {$IFDEF DEBUG}
- DbgLog(self, 'TBCTransInPlaceFilter.GetMediaType should never be called');
- {$ENDIF}
- result := E_UNEXPECTED;
- end;
- // return a non-addrefed CBasePin * for the user to addref if he holds onto it
- // for longer than his pointer to us. We create the pins dynamically when they
- // are asked for rather than in the constructor. This is because we want to
- // give the derived class an oppportunity to return different pin objects
- // As soon as any pin is needed we create both (this is different from the
- // usual transform filter) because enumerators, allocators etc are passed
- // through from one pin to another and it becomes very painful if the other
- // pin isn't there. If we fail to create either pin we ensure we fail both.
- function TBCTransInPlaceFilter.GetPin(n: integer): TBCBasePin;
- var hr: HRESULT;
- begin
- hr := S_OK;
- // Create an input pin if not already done
- if(FInput = nil) then
- begin
- FInput := TBCTransInPlaceInputPin.Create('TransInPlace input pin',
- self, // Owner filter
- hr, // Result code
- 'Input'); // Pin name
- // Constructor for CTransInPlaceInputPin can't fail
- ASSERT(SUCCEEDED(hr));
- end;
- // Create an output pin if not already done
- if((FInput <> nil) and (FOutput = nil)) then
- begin
- FOutput := TBCTransInPlaceOutputPin.Create('TransInPlace output pin',
- self, // Owner filter
- hr, // Result code
- 'Output'); // Pin name
- // a failed return code should delete the object
- ASSERT(SUCCEEDED(hr));
- if(FOutput = nil) then
- begin
- FInput.Free;
- FInput := nil;
- end;
- end;
- // Return the appropriate pin
- ASSERT(n in [0,1]);
- case n of
- 0: result := FInput;
- 1: result := FOutput;
- else
- result := nil;
- end;
- end;
- function TBCTransInPlaceFilter.InputPin: TBCTransInPlaceInputPin;
- begin
- result := TBCTransInPlaceInputPin(FInput);
- end;
- function TBCTransInPlaceFilter.OutputPin: TBCTransInPlaceOutputPin;
- begin
- result := TBCTransInPlaceOutputPin(FOutput);
- end;
- function TBCTransInPlaceFilter.Receive(Sample: IMediaSample): HRESULT;
- var Props: PAMSample2Properties;
- begin
- // Check for other streams and pass them on */
- Props := FInput.SampleProps;
- if (Props.dwStreamId <> AM_STREAM_MEDIA) then
- begin
- result := FOutput.Deliver(Sample);
- exit;
- end;
- if UsingDifferentAllocators then
- begin
- // We have to copy the data.
- Sample := Copy(Sample);
- if (Sample = nil) then
- begin
- result := E_UNEXPECTED;
- exit;
- end;
- end;
- // have the derived class transform the data
- result := Transform(Sample);
- if FAILED(result) then
- begin
- {$IFDEF DEBUG}
- DbgLog(self, 'Error from TransInPlace');
- {$ENDIF}
- if UsingDifferentAllocators then Sample := nil;
- exit;
- end;
- // the Transform() function can return S_FALSE to indicate that the
- // sample should not be delivered; we only deliver the sample if it's
- // really S_OK (same as NOERROR, of course.)
- if (result = NOERROR) then
- result := FOutput.Deliver(Sample)
- else
- begin
- // But it would be an error to return this private workaround
- // to the caller ...
- if (result = S_FALSE) then
- begin
- // S_FALSE returned from Transform is a PRIVATE agreement
- // We should return NOERROR from Receive() in this cause because
- // returning S_FALSE from Receive() means that this is the end
- // of the stream and no more data should be sent.
- FSampleSkipped := True;
- if (not FQualityChanged) then
- begin
- NotifyEvent(EC_QUALITY_CHANGE,0,0);
- FQualityChanged := True;
- end;
- result := NOERROR;
- end;
- end;
- // release the output buffer. If the connected pin still needs it,
- // it will have addrefed it itself.
- if UsingDifferentAllocators then Sample := nil;
- end;
- function TBCTransInPlaceFilter.TypesMatch: boolean;
- var
- pmt: PAMMediaType;
- begin
- pmt := InputPin.CurrentMediaType.MediaType;
- result := TBCMediaType(pmt).Equal(OutputPin.CurrentMediaType.MediaType);
- end;
- function TBCTransInPlaceFilter.UsingDifferentAllocators: boolean;
- begin
- result := Pointer(InputPin.FAllocator) <> Pointer(OutputPin.FAllocator);
- end;
- { TBCBasePropertyPage }
- function TBCBasePropertyPage.Activate(hwndParent: HWnd; const rc: TRect;
- bModal: BOOL): HResult;
- begin
- // Return failure if SetObject has not been called.
- if (FObjectSet = FALSE) or (hwndParent = 0) then
- begin
- result := E_UNEXPECTED;
- exit;
- end;
- // FForm := TCustomFormClass(FFormClass).Create(nil);
- if (FForm = nil) then
- begin
- result := E_OUTOFMEMORY;
- exit;
- end;
- FForm.ParentWindow := hwndParent;
- if assigned(FForm.OnActivate) then FForm.OnActivate(FForm);
- Move(rc);
- result := Show(SW_SHOWNORMAL);
- end;
- function TBCBasePropertyPage.Apply: HResult;
- begin
- // In ActiveMovie 1.0 we used to check whether we had been activated or
- // not. This is too constrictive. Apply should be allowed as long as
- // SetObject was called to set an object. So we will no longer check to
- // see if we have been activated (ie., m_hWnd != NULL), but instead
- // make sure that m_bObjectSet is True (ie., SetObject has been called).
- if (FObjectSet = FALSE) or (FPageSite = nil) then
- begin
- result := E_UNEXPECTED;
- exit;
- end;
- if (FDirty = FALSE) then
- begin
- result := NOERROR;
- exit;
- end;
- // Commit derived class changes
- result := FForm.OnApplyChanges;
- if SUCCEEDED(result) then FDirty := FALSE;
- end;
- function TBCBasePropertyPage.Deactivate: HResult;
- var Style: DWORD;
- begin
- if (FForm = nil) then
- begin
- result := E_UNEXPECTED;
- exit;
- end;
- // Remove WS_EX_CONTROLPARENT before DestroyWindow call
- Style := GetWindowLong(FForm.Handle, GWL_EXSTYLE);
- Style := Style and (not WS_EX_CONTROLPARENT);
- // Set m_hwnd to be NULL temporarily so the message handler
- // for WM_STYLECHANGING doesn't add the WS_EX_CONTROLPARENT
- // style back in
- SetWindowLong(FForm.Handle, GWL_EXSTYLE, Style);
- if assigned(FForm.OnDeactivate) then FForm.OnDeactivate(FForm);
- // Destroy the dialog window
- //FForm.Free;
- //FForm := nil;
- result := NOERROR;
- end;
- function TBCBasePropertyPage.GetPageInfo(out pageInfo: TPropPageInfo): HResult;
- begin
- pageInfo.cb := sizeof(TPropPageInfo);
- AMGetWideString(FForm.Caption, pageInfo.pszTitle);
- PageInfo.pszDocString := nil;
- PageInfo.pszHelpFile := nil;
- PageInfo.dwHelpContext:= 0;
- PageInfo.size.cx := FForm.width;
- PageInfo.size.cy := FForm.Height;
- Result := NoError;
- end;
- function TBCBasePropertyPage.Help(pszHelpDir: POleStr): HResult;
- begin
- result := E_NOTIMPL;
- end;
- function TBCBasePropertyPage.IsPageDirty: HResult;
- begin
- if FDirty then result := S_OK else result := S_FALSE;
- end;
- function TBCBasePropertyPage.Move(const rect: TRect): HResult;
- begin
- if (FForm = nil) then
- begin
- result := E_UNEXPECTED;
- exit;
- end;
- MoveWindow(FForm.Handle, // Property page handle
- Rect.left, // x coordinate
- Rect.top, // y coordinate
- Rect.Right - Rect.Left, // Overall window width
- Rect.Bottom - Rect.Top, // And likewise height
- True); // Should we repaint it
- result := NOERROR;
- end;
- function TBCBasePropertyPage.SetObjects(cObjects: Integer;
- pUnkList: PUnknownList): HResult;
- begin
- if (cObjects = 1) then
- begin
- if (pUnkList = nil) then
- begin
- result := E_POINTER;
- exit;
- end;
- // Set a flag to say that we have set the Object
- FObjectSet := True ;
- result := FForm.OnConnect(pUnkList^[0]);
- exit;
- end
- else
- if (cObjects = 0) then
- begin
- // Set a flag to say that we have not set the Object for the page
- FObjectSet := FALSE;
- result := FForm.OnDisconnect;
- exit;
- end;
- {$IFDEF DEBUG}
- DbgLog(self, 'No support for more than one object');
- {$ENDIF}
- result := E_UNEXPECTED;
- end;
- function TBCBasePropertyPage.SetPageSite(
- const pageSite: IPropertyPageSite): HResult;
- begin
- if (pageSite <> nil) then
- begin
- if (FPageSite <> nil) then
- begin
- result := E_UNEXPECTED;
- exit;
- end;
- FPageSite := pageSite;
- end
- else
- begin
- if (FPageSite = nil) then
- begin
- result := E_UNEXPECTED;
- exit;
- end;
- FPageSite := nil;
- end;
- result := NOERROR;
- end;
- function TBCBasePropertyPage.Show(nCmdShow: Integer): HResult;
- begin
- if (FForm = nil) then
- begin
- result := E_UNEXPECTED;
- exit;
- end;
- if ((nCmdShow <> SW_SHOW) and (nCmdShow <> SW_SHOWNORMAL) and (nCmdShow <> SW_HIDE)) then
- begin
- result := E_INVALIDARG;
- exit;
- end;
- if nCmdShow in [SW_SHOW,SW_SHOWNORMAL] then FForm.Show else FForm.Hide;
- InvalidateRect(FForm.Handle, nil, True);
- result := NOERROR;
- end;
- function TBCBasePropertyPage.TranslateAccelerator(msg: PMsg): HResult;
- begin
- result := E_NOTIMPL;
- end;
- constructor TBCBasePropertyPage.Create(Name: String; Unk: IUnKnown; Form: TFormPropertyPage);
- begin
- inherited Create(Name, Unk);
- FForm := Form;
- FForm.BorderStyle := bsNone;
- FPageSite := nil;
- FObjectSet := false;
- FDirty := false;
- end;
- destructor TBCBasePropertyPage.Destroy;
- begin
- if FForm <> nil then
- begin
- FForm.Free;
- FForm := nil;
- end;
- inherited;
- end;
- constructor TFormPropertyPage.Create(AOwner: TComponent);
- begin
- inherited Create(AOwner);
- WindowProc := MyWndProc;
- end;
- procedure TFormPropertyPage.MyWndProc(var aMsg: TMessage);
- var
- lpss : PStyleStruct;
- begin
- // we would like the TAB key to move around the tab stops in our property
- // page, but for some reason OleCreatePropertyFrame clears the CONTROLPARENT
- // style behind our back, so we need to switch it back on now behind its
- // back. Otherwise the tab key will be useless in every page.
- // DCoder: removing CONTROLPARENT is also the reason for non responding
- // PropertyPages when using ShowMessage and TComboBox.
- if (aMsg.Msg = WM_STYLECHANGING) and (aMsg.WParam = GWL_EXSTYLE) then
- begin
- lpss := PStyleStruct(aMsg.LParam);
- lpss.styleNew := lpss.styleNew or WS_EX_CONTROLPARENT;
- aMsg.Result := 0;
- Exit;
- end;
- WndProc(aMsg);
- end;
- function TFormPropertyPage.OnApplyChanges: HRESULT;
- begin
- result := NOERROR;
- end;
- function TFormPropertyPage.OnConnect(Unknown: IUnKnown): HRESULT;
- begin
- result := NOERROR;
- end;
- function TFormPropertyPage.OnDisconnect: HRESULT;
- begin
- result := NOERROR;
- end;
- procedure TBCBasePropertyPage.SetPageDirty;
- begin
- FDirty := True;
- if Assigned(FPageSite) then FPageSite.OnStatusChange(PROPPAGESTATUS_DIRTY);
- end;
- { TBCBaseDispatch }
- function TBCBaseDispatch.GetIDsOfNames(const IID: TGUID; Names: Pointer;
- NameCount, LocaleID: Integer; DispIDs: Pointer): HResult;
- var ti: ITypeInfo;
- begin
- // although the IDispatch riid is dead, we use this to pass from
- // the interface implementation class to us the iid we are talking about.
- result := GetTypeInfo(iid, 0, LocaleID, ti);
- if SUCCEEDED(result) then
- result := ti.GetIDsOfNames(Names, NameCount, DispIDs);
- end;
- function TBCBaseDispatch.GetTypeInfo(const iid: TGUID; info: Cardinal; lcid: LCID;
- out tinfo): HRESULT; stdcall;
- var
- tlib : ITypeLib;
- begin
- // we only support one type element
- if (info <> 0) then
- begin
- result := TYPE_E_ELEMENTNOTFOUND;
- exit;
- end;
- // always look for neutral
- if (FTI = nil) then
- begin
- result := LoadRegTypeLib(LIBID_QuartzTypeLib, 1, 0, lcid, tlib);
- if FAILED(result) then
- begin
- result := LoadTypeLib('control.tlb', tlib);
- if FAILED(result) then exit;
- end;
- result := tlib.GetTypeInfoOfGuid(iid, Fti);
- tlib := nil;
- if FAILED(result) then exit;
- end;
- ITypeInfo(tinfo) := Fti;
- result := S_OK;
- end;
- function TBCBaseDispatch.GetTypeInfoCount(out Count: Integer): HResult;
- begin
- count := 1;
- result := S_OK;
- end;
- { TBCMediaControl }
- constructor TBCMediaControl.Create(name: string; unk: IUnknown);
- begin
- FBaseDisp := TBCBaseDispatch.Create;
- end;
- destructor TBCMediaControl.Destroy;
- begin
- FBaseDisp.Free;
- inherited;
- end;
- function TBCMediaControl.GetIDsOfNames(const IID: TGUID; Names: Pointer;
- NameCount, LocaleID: Integer; DispIDs: Pointer): HResult;
- begin
- result := FBasedisp.GetIDsOfNames(IID_IMediaControl, Names, NameCount, LocaleID, DispIDs);
- end;
- function TBCMediaControl.GetTypeInfo(Index, LocaleID: Integer;
- out TypeInfo): HResult;
- begin
- result := Fbasedisp.GetTypeInfo(IID_IMediaControl, index, LocaleID, TypeInfo);
- end;
- function TBCMediaControl.GetTypeInfoCount(out Count: Integer): HResult;
- begin
- result := FBaseDisp.GetTypeInfoCount(Count);
- end;
- function TBCMediaControl.Invoke(DispID: Integer; const IID: TGUID;
- LocaleID: Integer; Flags: Word; var Params; VarResult, ExcepInfo,
- ArgErr: Pointer): HResult;
- var ti: ITypeInfo;
- begin
- // this parameter is a dead leftover from an earlier interface
- if not IsEqualGUID(GUID_NULL, IID) then
- begin
- result := DISP_E_UNKNOWNINTERFACE;
- exit;
- end;
- result := GetTypeInfo(0, LocaleID, ti);
- if FAILED(result) then exit;
- result := ti.Invoke(Pointer(Integer(Self)), DISPID, Flags, TDispParams(Params),
- VarResult, ExcepInfo, ArgErr);
- end;
- { TBCMediaEvent }
- constructor TBCMediaEvent.Create(Name: string; Unk: IUnknown);
- begin
- inherited Create(name, Unk);
- FBasedisp := TBCBaseDispatch.Create;
- end;
- destructor TBCMediaEvent.destroy;
- begin
- FBasedisp.Free;
- inherited;
- end;
- function TBCMediaEvent.GetIDsOfNames(const IID: TGUID; Names: Pointer;
- NameCount, LocaleID: Integer; DispIDs: Pointer): HResult;
- begin
- result := FBasedisp.GetIDsOfNames(IID_IMediaEvent, Names, NameCount, LocaleID, DispIDs);
- end;
- function TBCMediaEvent.GetTypeInfo(Index, LocaleID: Integer;
- out TypeInfo): HResult;
- begin
- result := Fbasedisp.GetTypeInfo(IID_IMediaEvent, index, LocaleID, TypeInfo);
- end;
- function TBCMediaEvent.GetTypeInfoCount(out Count: Integer): HResult;
- begin
- result := FBaseDisp.GetTypeInfoCount(Count);
- end;
- function TBCMediaEvent.Invoke(DispID: Integer; const IID: TGUID;
- LocaleID: Integer; Flags: Word; var Params; VarResult, ExcepInfo,
- ArgErr: Pointer): HResult;
- var ti: ITypeInfo;
- begin
- // this parameter is a dead leftover from an earlier interface
- if not IsEqualGUID(GUID_NULL, IID) then
- begin
- result := DISP_E_UNKNOWNINTERFACE;
- exit;
- end;
- result := GetTypeInfo(0, LocaleID, ti);
- if FAILED(result) then exit;
- result := ti.Invoke(Pointer(Integer(Self)), DISPID, Flags, TDispParams(Params), VarResult, ExcepInfo, ArgErr);
- end;
- { TBCMediaPosition }
- constructor TBCMediaPosition.Create(Name: String; Unk: IUnknown);
- begin
- inherited Create(Name, Unk);
- FBaseDisp := TBCBaseDispatch.Create;
- end;
- constructor TBCMediaPosition.Create(Name: String; Unk: IUnknown;
- out hr: HRESULT);
- begin
- inherited Create(Name, Unk);
- FBaseDisp := TBCBaseDispatch.Create;
- end;
- destructor TBCMediaPosition.Destroy;
- begin
- FBaseDisp.Free;
- inherited;
- end;
- function TBCMediaPosition.GetIDsOfNames(const IID: TGUID; Names: Pointer;
- NameCount, LocaleID: Integer; DispIDs: Pointer): HResult;
- begin
- result := FBasedisp.GetIDsOfNames(IID_IMediaPosition, Names, NameCount, LocaleID, DispIDs);
- end;
- function TBCMediaPosition.GetTypeInfo(Index, LocaleID: Integer;
- out TypeInfo): HResult;
- begin
- result := Fbasedisp.GetTypeInfo(IID_IMediaPosition, index, LocaleID, TypeInfo);
- end;
- function TBCMediaPosition.GetTypeInfoCount(out Count: Integer): HResult;
- begin
- result := Fbasedisp.GetTypeInfoCount(Count);
- end;
- function TBCMediaPosition.Invoke(DispID: Integer; const IID: TGUID;
- LocaleID: Integer; Flags: Word; var Params; VarResult, ExcepInfo,
- ArgErr: Pointer): HResult;
- var ti: ITypeInfo;
- begin
- // this parameter is a dead leftover from an earlier interface
- if not IsEqualGUID(GUID_NULL, IID) then
- begin
- result := DISP_E_UNKNOWNINTERFACE;
- exit;
- end;
- result := GetTypeInfo(0, LocaleID, ti);
- if FAILED(result) then exit;
- result := ti.Invoke(Pointer(Integer(Self)), DISPID, Flags, TDispParams(Params), VarResult, ExcepInfo, ArgErr);
- end;
- { TBCPosPassThru }
- function TBCPosPassThru.CanSeekBackward(
- out pCanSeekBackward: Integer): HResult;
- var MP: IMediaPosition;
- begin
- result := GetPeer(MP);
- if FAILED(result) then exit;
- result := MP.CanSeekBackward(pCanSeekBackward);
- end;
- function TBCPosPassThru.CanSeekForward(
- out pCanSeekForward: Integer): HResult;
- var MP: IMediaPosition;
- begin
- result := GetPeer(MP);
- if FAILED(result) then exit;
- result := MP.CanSeekForward(pCanSeekForward);
- end;
- function TBCPosPassThru.CheckCapabilities(
- var pCapabilities: DWORD): HRESULT;
- var
- MS: IMediaSeeking;
- begin
- result := GetPeerSeeking(MS);
- if FAILED(result) then exit;
- result := MS.CheckCapabilities(pCapabilities);
- end;
- function TBCPosPassThru.ConvertTimeFormat(out pTarget: int64;
- pTargetFormat: PGUID; Source: int64; pSourceFormat: PGUID): HRESULT;
- var MS: IMediaSeeking;
- begin
- result := GetPeerSeeking(MS);
- if FAILED(result) then exit;
- result := MS.ConvertTimeFormat(pTarget, pTargetFormat, Source, pSourceFormat);
- end;
- constructor TBCPosPassThru.Create(name: String; Unk: IUnknown;
- out hr: HRESULT; Pin: IPin);
- begin
- assert(Pin <> nil);
- inherited Create(Name,Unk);
- FPin := Pin;
- end;
- function TBCPosPassThru.ForceRefresh: HRESULT;
- begin
- result := S_OK;
- end;
- function TBCPosPassThru.get_CurrentPosition(
- out pllTime: TRefTime): HResult;
- var MP: IMediaPosition;
- begin
- result := GetPeer(MP);
- if FAILED(result) then exit;
- result := MP.get_CurrentPosition(pllTime);
- end;
- function TBCPosPassThru.get_Duration(out plength: TRefTime): HResult;
- var MP: IMediaPosition;
- begin
- result := GetPeer(MP);
- if FAILED(result) then exit;
- result := MP.get_Duration(plength);
- end;
- function TBCPosPassThru.get_PrerollTime(out pllTime: TRefTime): HResult;
- var MP: IMediaPosition;
- begin
- result := GetPeer(MP);
- if FAILED(result) then exit;
- result := MP.get_PrerollTime(pllTime);
- end;
- function TBCPosPassThru.get_Rate(out pdRate: double): HResult;
- var MP: IMediaPosition;
- begin
- result := GetPeer(MP);
- if FAILED(result) then exit;
- result := MP.get_Rate(pdRate);
- end;
- function TBCPosPassThru.get_StopTime(out pllTime: TRefTime): HResult;
- var MP: IMediaPosition;
- begin
- result := GetPeer(MP);
- if FAILED(result) then exit;
- result := MP.get_StopTime(pllTime);
- end;
- function TBCPosPassThru.GetAvailable(out pEarliest,
- pLatest: int64): HRESULT;
- var MS: IMediaSeeking;
- begin
- result := GetPeerSeeking(MS);
- if FAILED(result) then exit;
- result := MS.GetAvailable(pEarliest, pLatest);
- end;
- function TBCPosPassThru.GetCapabilities(out pCapabilities: DWORD): HRESULT;
- var MS: IMediaSeeking;
- begin
- result := GetPeerSeeking(MS);
- if FAILED(result) then exit;
- result := MS.GetCapabilities(pCapabilities);
- end;
- function TBCPosPassThru.GetCurrentPosition(out pCurrent: int64): HRESULT;
- var
- MS: IMediaSeeking;
- Stop: int64;
- begin
- result := GetMediaTime(pCurrent, Stop);
- if SUCCEEDED(result) then
- result := NOERROR
- else
- begin
- result := GetPeerSeeking(MS);
- if FAILED(result) then exit;
- result := MS.GetCurrentPosition(pCurrent)
- end;
- end;
- function TBCPosPassThru.GetDuration(out pDuration: int64): HRESULT;
- var MS: IMediaSeeking;
- begin
- result := GetPeerSeeking(MS);
- if FAILED(result) then exit;
- result := MS.GetDuration(pDuration);
- end;
- function TBCPosPassThru.GetMediaTime(out StartTime,
- EndTime: Int64): HRESULT;
- begin
- result := E_FAIL;
- end;
- // Return the IMediaPosition interface from our peer
- function TBCPosPassThru.GetPeer(out MP: IMediaPosition): HRESULT;
- var
- Connected: IPin;
- begin
- result := FPin.ConnectedTo(Connected);
- if FAILED(result) then
- begin
- result := E_NOTIMPL;
- exit;
- end;
- result := Connected.QueryInterface(IID_IMediaPosition, MP);
- Connected := nil;
- if FAILED(result) then
- begin
- result := E_NOTIMPL;
- exit;
- end;
- result := S_OK;
- end;
- function TBCPosPassThru.GetPeerSeeking(out MS: IMediaSeeking): HRESULT;
- var
- Connected: IPin;
- begin
- MS := nil;
- result := FPin.ConnectedTo(Connected);
- if FAILED(result) then
- begin
- result := E_NOTIMPL;
- exit;
- end;
- result := Connected.QueryInterface(IID_IMediaSeeking, MS);
- Connected := nil;
- if FAILED(result) then
- begin
- result := E_NOTIMPL;
- exit;
- end;
- result := S_OK;
- end;
- function TBCPosPassThru.GetPositions(out pCurrent, pStop: int64): HRESULT;
- var MS: IMediaSeeking;
- begin
- result := GetPeerSeeking(MS);
- if FAILED(result) then exit;
- result := MS.GetPositions(pCurrent, pStop);
- end;
- function TBCPosPassThru.GetPreroll(out pllPreroll: int64): HRESULT;
- var MS: IMediaSeeking;
- begin
- result := GetPeerSeeking(MS);
- if FAILED(result) then exit;
- result := MS.GetPreroll(pllPreroll);
- end;
- function TBCPosPassThru.GetRate(out pdRate: double): HRESULT;
- var MS: IMediaSeeking;
- begin
- result := GetPeerSeeking(MS);
- if FAILED(result) then exit;
- result := MS.GetRate(pdRate);
- end;
- function TBCPosPassThru.GetStopPosition(out pStop: int64): HRESULT;
- var MS: IMediaSeeking;
- begin
- result := GetPeerSeeking(MS);
- if FAILED(result) then exit;
- result := MS.GetStopPosition(pStop);
- end;
- function TBCPosPassThru.GetTimeFormat(out pFormat: TGUID): HRESULT;
- var MS: IMediaSeeking;
- begin
- result := GetPeerSeeking(MS);
- if FAILED(result) then exit;
- result := MS.GetTimeFormat(pFormat);
- end;
- function TBCPosPassThru.IsFormatSupported(const pFormat: TGUID): HRESULT;
- var MS: IMediaSeeking;
- begin
- result := GetPeerSeeking(MS);
- if FAILED(result) then exit;
- result := MS.IsFormatSupported(pFormat);
- end;
- function TBCPosPassThru.IsUsingTimeFormat(const pFormat: TGUID): HRESULT;
- var MS: IMediaSeeking;
- begin
- result := GetPeerSeeking(MS);
- if FAILED(result) then exit;
- result := MS.IsUsingTimeFormat(pFormat);
- end;
- function TBCPosPassThru.put_CurrentPosition(llTime: TRefTime): HResult;
- var MP: IMediaPosition;
- begin
- result := GetPeer(MP);
- if FAILED(result) then exit;
- result := MP.put_CurrentPosition(llTime);
- end;
- function TBCPosPassThru.put_PrerollTime(llTime: TRefTime): HResult;
- var MP: IMediaPosition;
- begin
- result := GetPeer(MP);
- if FAILED(result) then exit;
- result := MP.put_PrerollTime(llTime);
- end;
- function TBCPosPassThru.put_Rate(dRate: double): HResult;
- var MP: IMediaPosition;
- begin
- if (dRate = 0.0) then
- begin
- result := E_INVALIDARG;
- exit;
- end;
- result := GetPeer(MP);
- if FAILED(result) then exit;
- result := MP.put_Rate(dRate);
- end;
- function TBCPosPassThru.put_StopTime(llTime: TRefTime): HResult;
- var MP: IMediaPosition;
- begin
- result := GetPeer(MP);
- if FAILED(result) then exit;
- result := MP.put_StopTime(llTime);
- end;
- function TBCPosPassThru.QueryPreferredFormat(out pFormat: TGUID): HRESULT;
- var MS: IMediaSeeking;
- begin
- result := GetPeerSeeking(MS);
- if FAILED(result) then exit;
- result := MS.QueryPreferredFormat(pFormat);
- end;
- function TBCPosPassThru.SetPositions(var pCurrent: int64;
- dwCurrentFlags: DWORD; var pStop: int64; dwStopFlags: DWORD): HRESULT;
- var MS: IMediaSeeking;
- begin
- result := GetPeerSeeking(MS);
- if FAILED(result) then exit;
- result := MS.SetPositions(pCurrent, dwCurrentFlags, pStop, dwStopFlags);
- end;
- function TBCPosPassThru.SetRate(dRate: double): HRESULT;
- var MS: IMediaSeeking;
- begin
- if (dRate = 0.0) then
- begin
- result := E_INVALIDARG;
- exit;
- end;
- result := GetPeerSeeking(MS);
- if FAILED(result) then exit;
- result := MS.SetRate(dRate);
- end;
- function TBCPosPassThru.SetTimeFormat(const pFormat: TGUID): HRESULT;
- var MS: IMediaSeeking;
- begin
- result := GetPeerSeeking(MS);
- if FAILED(result) then exit;
- result := MS.SetTimeFormat(pFormat);
- end;
- { TBCRendererPosPassThru }
- // Media times (eg current frame, field, sample etc) are passed through the
- // filtergraph in media samples. When a renderer gets a sample with media
- // times in it, it will call one of the RegisterMediaTime methods we expose
- // (one takes an IMediaSample, the other takes the media times direct). We
- // store the media times internally and return them in GetCurrentPosition.
- constructor TBCRendererPosPassThru.Create(name: String; Unk: IUnknown;
- out hr: HRESULT; Pin: IPin);
- begin
- inherited Create(Name,Unk,hr,Pin);
- FStartMedia:= 0;
- FEndMedia := 0;
- FReset := True;
- FPositionLock := TBCCritSec.Create;
- end;
- destructor TBCRendererPosPassThru.destroy;
- begin
- FPositionLock.Free;
- inherited;
- end;
- // Intended to be called by the owing filter during EOS processing so
- // that the media times can be adjusted to the stop time. This ensures
- // that the GetCurrentPosition will actully get to the stop position.
- function TBCRendererPosPassThru.EOS: HRESULT;
- var Stop: int64;
- begin
- if FReset then result := E_FAIL
- else
- begin
- result := GetStopPosition(Stop);
- if SUCCEEDED(result) then
- begin
- FPositionLock.Lock;
- try
- FStartMedia := Stop;
- FEndMedia := Stop;
- finally
- FPositionLock.UnLock;
- end;
- end;
- end;
- end;
- function TBCRendererPosPassThru.GetMediaTime(out StartTime,
- EndTime: int64): HRESULT;
- begin
- FPositionLock.Lock;
- try
- if FReset then
- begin
- result := E_FAIL;
- exit;
- end;
- // We don't have to return the end time
- result := ConvertTimeFormat(StartTime, nil, FStartMedia, @TIME_FORMAT_MEDIA_TIME);
- if SUCCEEDED(result) then
- result := ConvertTimeFormat(EndTime, nil, FEndMedia, @TIME_FORMAT_MEDIA_TIME);
- finally
- FPositionLock.UnLock;
- end;
- end;
- // Sets the media times the object should report
- function TBCRendererPosPassThru.RegisterMediaTime(
- MediaSample: IMediaSample): HRESULT;
- var StartMedia, EndMedia: TReferenceTime;
- begin
- ASSERT(assigned(MediaSample));
- FPositionLock.Lock;
- try
- // Get the media times from the sample
- result := MediaSample.GetTime(StartMedia, EndMedia);
- if FAILED(result) then
- begin
- ASSERT(result = VFW_E_SAMPLE_TIME_NOT_SET);
- exit;
- end;
- FStartMedia := StartMedia;
- FEndMedia := EndMedia;
- FReset := FALSE;
- result := NOERROR;
- finally
- FPositionLock.Unlock;
- end;
- end;
- // Sets the media times the object should report
- function TBCRendererPosPassThru.RegisterMediaTime(StartTime,
- EndTime: int64): HRESULT;
- begin
- FPositionLock.Lock;
- try
- FStartMedia := StartTime;
- FEndMedia := EndTime;
- FReset := FALSE;
- result := NOERROR;
- finally
- FPositionLock.UnLock;
- end;
- end;
- // Resets the media times we hold
- function TBCRendererPosPassThru.ResetMediaTime: HRESULT;
- begin
- FPositionLock.Lock;
- try
- FStartMedia := 0;
- FEndMedia := 0;
- FReset := True;
- result := NOERROR;
- finally
- FPositionLock.UnLock;
- end;
- end;
- { TBCAMEvent }
- function TBCAMEvent.Check: boolean;
- begin
- result := Wait(0);
- end;
- constructor TBCAMEvent.Create(ManualReset: boolean);
- begin
- FEvent := CreateEvent(nil, ManualReset, FALSE, nil);
- end;
- destructor TBCAMEvent.destroy;
- begin
- if FEvent <> 0 then
- Assert(CloseHandle(FEvent));
- inherited;
- end;
- procedure TBCAMEvent.Reset;
- begin
- ResetEvent(FEvent);
- end;
- procedure TBCAMEvent.SetEv;
- begin
- SetEvent(FEvent);
- end;
- function TBCAMEvent.Wait(Timeout: Cardinal): boolean;
- begin
- result := (WaitForSingleObject(FEvent, Timeout) = WAIT_OBJECT_0);
- end;
- { TBCRenderedInputPin }
- function TBCRenderedInputPin.Active: HRESULT;
- begin
- FAtEndOfStream := FALSE;
- FCompleteNotified := FALSE;
- result := inherited Active;
- end;
- constructor TBCRenderedInputPin.Create(ObjectName: string;
- Filter: TBCBaseFilter; Lock: TBCCritSec; out hr: HRESULT;
- Name: WideString);
- begin
- inherited Create(ObjectName, Filter, Lock, hr, Name);
- FAtEndOfStream := FALSE;
- FCompleteNotified := FALSE;
- end;
- procedure TBCRenderedInputPin.DoCompleteHandling;
- begin
- ASSERT(FAtEndOfStream);
- if (not FCompleteNotified) then
- begin
- FCompleteNotified := True;
- FFilter.NotifyEvent(EC_COMPLETE, S_OK, Integer(FFilter));
- end;
- end;
- function TBCRenderedInputPin.EndFlush: HRESULT;
- begin
- FLock.Lock;
- try
- // Clean up renderer state
- FAtEndOfStream := FALSE;
- FCompleteNotified := FALSE;
- result := inherited EndFlush;
- finally
- FLock.UnLock;
- end;
- end;
- function TBCRenderedInputPin.EndOfStream: HRESULT;
- var
- fs: TFilterState;
- begin
- result := CheckStreaming;
- // Do EC_COMPLETE handling for rendered pins
- if ((result = S_OK) and (not FAtEndOfStream)) then
- begin
- FAtEndOfStream := True;
- ASSERT(SUCCEEDED(FFilter.GetState(0, fs)));
- if (fs = State_Running) then
- DoCompleteHandling;
- end;
- end;
- function TBCRenderedInputPin.Run(Start: TReferenceTime): HRESULT;
- begin
- FCompleteNotified := FALSE;
- if FAtEndOfStream then DoCompleteHandling;
- result := S_OK;
- end;
- { TBCAMMsgEvent }
- function TBCAMMsgEvent.WaitMsg(Timeout: DWord): boolean;
- var
- // wait for the event to be signalled, or for the
- // timeout (in MS) to expire. allow SENT messages
- // to be processed while we wait
- Wait, StartTime: DWord;
- // set the waiting period.
- WaitTime: Dword;
- Msg: TMsg;
- Elapsed: DWord;
- begin
- WaitTime := Timeout;
- // the timeout will eventually run down as we iterate
- // processing messages. grab the start time so that
- // we can calculate elapsed times.
- if (WaitTime <> INFINITE) then
- StartTime := timeGetTime else
- StartTime := 0; // don't generate compiler hint
- repeat
- Wait := MsgWaitForMultipleObjects(1, FEvent, FALSE, WaitTime, QS_SENDMESSAGE);
- if (Wait = WAIT_OBJECT_0 + 1) then
- begin
- PeekMessage(Msg, 0, 0, 0, PM_NOREMOVE);
- // If we have an explicit length of time to wait calculate
- // the next wake up point - which might be now.
- // If dwTimeout is INFINITE, it stays INFINITE
- if (WaitTime <> INFINITE) then
- begin
- Elapsed := timeGetTime - StartTime;
- if (Elapsed >= Timeout) then
- WaitTime := 0 else // wake up with WAIT_TIMEOUT
- WaitTime := Timeout - Elapsed;
- end;
- end
- until (Wait <> WAIT_OBJECT_0 + 1);
- // return True if we woke on the event handle,
- // FALSE if we timed out.
- result := (Wait = WAIT_OBJECT_0);
- end;
- { TBCAMThread }
- function TBCAMThread.CallWorker(Param: DWORD): DWORD;
- begin
- // lock access to the worker thread for scope of this object
- FAccessLock.Lock;
- try
- if not ThreadExists then
- begin
- Result := DWORD(E_FAIL);
- Exit;
- end;
- // set the parameter
- FParam := Param;
- // signal the worker thread
- FEventSend.SetEv;
- // wait for the completion to be signalled
- FEventComplete.Wait;
- // done - this is the thread's return value
- Result := FReturnVal;
- finally
- FAccessLock.unlock;
- end;
- end;
- function TBCAMThread.CheckRequest(Param: PDWORD): boolean;
- begin
- if not FEventSend.Check then
- begin
- Result := FALSE;
- Exit;
- end else
- begin
- if (Param <> nil) then
- Param^ := FParam;
- Result := True;
- end;
- end;
- procedure TBCAMThread.Close;
- var
- Thread: THandle;
- begin
- Thread := InterlockedExchange(Integer(FThread), 0);
- if BOOL(Thread) then
- begin
- WaitForSingleObject(Thread, INFINITE);
- CloseHandle(Thread);
- end;
- end;
- class function TBCAMThread.CoInitializeHelper: HRESULT;
- var
- hr: HRESULT;
- hOle: LongWord;
- CoInitializeEx: function(pvReserved: Pointer; coInit: Longint): HResult; stdcall;
- begin
- // call CoInitializeEx and tell OLE not to create a window (this
- // thread probably won't dispatch messages and will hang on
- // broadcast msgs o/w).
- //
- // If CoInitEx is not available, threads that don't call CoCreate
- // aren't affected. Threads that do will have to handle the
- // failure. Perhaps we should fall back to CoInitialize and risk
- // hanging?
- //
- // older versions of ole32.dll don't have CoInitializeEx
- hr := E_FAIL;
- hOle := GetModuleHandle(PChar('ole32.dll'));
- if (hOle <> 0) then
- begin
- CoInitializeEx := GetProcAddress(hOle, 'CoInitializeEx');
- if (@CoInitializeEx <> nil) then
- hr := CoInitializeEx(nil, COINIT_DISABLE_OLE1DDE);
- end else
- begin
- {$IFDEF DEBUG}
- // caller must load ole32.dll
- DbgLog('couldn''t locate ole32.dll');
- {$ENDIF}
- end;
- result := hr;
- end;
- constructor TBCAMThread.Create;
- begin
- // must be manual-reset for CheckRequest()
- FAccessLock := TBCCritSec.Create;
- FWorkerLock := TBCCritSec.Create;
- FEventSend := TBCAMEvent.Create(True);
- FEventComplete := TBCAMEvent.Create;
- FThread := 0;
- FThreadProc := nil;
- end;
- function TBCAMThread.Create_: boolean;
- var
- threadid: DWORD;
- begin
- FAccessLock.Lock;
- try
- if ThreadExists then
- begin
- Result := False;
- Exit;
- end;
- FThread := CreateThread(nil, 0, @TBCAMThread.InitialThreadProc,
- Self, 0, threadid);
- if not BOOL(FThread) then
- Result := FALSE else
- Result := True;
- finally
- FAccessLock.Unlock;
- end;
- end;
- destructor TBCAMThread.Destroy;
- begin
- Close;
- FAccessLock.Free;
- FWorkerLock.Free;
- FEventSend.Free;
- FEventComplete.Free;
- inherited;
- end;
- function TBCAMThread.GetRequest: DWORD;
- begin
- FEventSend.Wait;
- Result := FParam;
- end;
- function TBCAMThread.GetRequestHandle: THANDLE;
- begin
- Result := FEventSend.FEvent
- end;
- function TBCAMThread.GetRequestParam: DWORD;
- begin
- Result := FParam;
- end;
- function TBCAMThread.InitialThreadProc(p: Pointer): DWORD;
- var
- hrCoInit: HRESULT;
- begin
- hrCoInit := TBCAMThread.CoInitializeHelper;
- {$IFDEF DEBUG}
- if(FAILED(hrCoInit)) then
- DbgLog('CoInitializeEx failed.');
- {$ENDIF}
- Result := ThreadProc;
- if(SUCCEEDED(hrCoInit)) then
- CoUninitialize;
- end;
- procedure TBCAMThread.Reply(v: DWORD);
- begin
- FReturnVal := v;
- // The request is now complete so CheckRequest should fail from
- // now on
- //
- // This event should be reset BEFORE we signal the client or
- // the client may Set it before we reset it and we'll then
- // reset it (!)
- FEventSend.Reset;
- // Tell the client we're finished
- FEventComplete.SetEv;
- end;
- function TBCAMThread.ThreadExists: boolean;
- begin
- Result := FThread <> 0;
- end;
- function TBCAMThread.ThreadProc: DWord;
- begin
- if @FThreadProc <> nil then
- Result := FThreadProc else
- Result := 0
- end;
- { TBCNode }
- {$ifdef DEBUG}
- constructor TBCNode.Create;
- begin
- inherited Create('List node');
- end;
- {$ENDIF}
- { TBCNodeCache }
- procedure TBCNodeCache.AddToCache(Node: TBCNode);
- begin
- if (FUsed < FCacheSize) then
- begin
- Node.Next := FHead;
- FHead := Node;
- inc(FUsed);
- end else
- Node.Free;
- end;
- constructor TBCNodeCache.Create(CacheSize: Integer);
- begin
- FCacheSize := CacheSize;
- FHead := nil;
- FUsed := 0;
- end;
- destructor TBCNodeCache.Destroy;
- var Node, Current: TBCNode;
- begin
- Node := FHead;
- while (Node <> nil) do
- begin
- Current := Node;
- Node := Node.Next;
- Current.Free;
- end;
- inherited;
- end;
- function TBCNodeCache.RemoveFromCache: TBCNode;
- var Node: TBCNode;
- begin
- Node := FHead;
- if (Node <> nil) then
- begin
- FHead := Node.Next;
- Dec(FUsed);
- ASSERT(FUsed >= 0);
- end else
- ASSERT(FUsed = 0);
- Result := Node;
- end;
- { TBCBaseList }
- function TBCBaseList.AddAfter(p: Position; List: TBCBaseList): BOOL;
- var pos: Position;
- begin
- pos := list.GetHeadPositionI;
- while(pos <> nil) do
- begin
- // p follows along the elements being added
- p := AddAfterI(p, List.GetI(pos));
- if (p = nil) then
- begin
- Result := FALSE;
- Exit;
- end;
- pos := list.Next(pos);
- end;
- Result := True;
- end;
- (* Add the object after position p
- p is still valid after the operation.
- AddAfter(NULL,x) adds x to the start - same as AddHead
- Return the position of the new object, NULL if it failed
- *)
- function TBCBaseList.AddAfterI(pos: Position; Obj: Pointer): Position;
- var After, Node, Before: TBCNode;
- begin
- if (pos = nil) then
- Result := AddHeadI(Obj) else
- begin
- (* As someone else might be furkling with the list -
- Lock the critical section before continuing
- *)
- After := pos;
- ASSERT(After <> nil);
- if (After = FLast) then
- Result := AddTailI(Obj) else
- begin
- // set pnode to point to a new node, preferably from the cache
- Node := FCache.RemoveFromCache;
- if (Node = nil) then
- Node := TBCNode.Create;
- // Check we have a valid object
- if (Node = nil) then
- Result := nil else
- begin
- (* Initialise all the CNode object
- just in case it came from the cache
- *)
- Node.Data := Obj;
- (* It is to be added to the middle of the list - there is a before
- and after node. Chain it after pAfter, before pBefore.
- *)
- Before := After.Next;
- ASSERT(Before <> nil);
- // chain it in (set four pointers)
- Node.Prev := After;
- Node.Next := Before;
- Before.Prev := Node;
- After.Next := Node;
- inc(FCount);
- Result := Node;
- end;
- end;
- end;
- end;
- function TBCBaseList.AddBefore(p: Position; List: TBCBaseList): BOOL;
- var pos: Position;
- begin
- pos := List.GetTailPositionI;
- while (pos <> nil) do
- begin
- // p follows along the elements being added
- p := AddBeforeI(p, List.GetI(pos));
- if (p = nil) then
- begin
- Result := FALSE;
- Exit;
- end;
- pos := list.Prev(pos);
- end;
- Result := True;
- end;
- (* Mirror images:
- Add the element or list after position p.
- p is still valid after the operation.
- AddBefore(NULL,x) adds x to the end - same as AddTail
- *)
- function TBCBaseList.AddBeforeI(pos: Position; Obj: Pointer): Position;
- var
- Before, Node, After: TBCNode;
- begin
- if (pos = nil) then
- Result := AddTailI(Obj) else
- begin
- // set pnode to point to a new node, preferably from the cache
- Before := pos;
- ASSERT(Before <> nil);
- if (Before = FFirst) then
- Result := AddHeadI(Obj) else
- begin
- Node := FCache.RemoveFromCache;
- if (Node = nil) then
- Node := TBCNode.Create;
- // Check we have a valid object */
- if (Node = nil) then
- Result := nil else
- begin
- (* Initialise all the CNode object
- just in case it came from the cache
- *)
- Node.Data := Obj;
- (* It is to be added to the middle of the list - there is a before
- and after node. Chain it after pAfter, before pBefore.
- *)
- After := Before.Prev;
- ASSERT(After <> nil);
- // chain it in (set four pointers)
- Node.Prev := After;
- Node.Next := Before;
- Before.Prev := Node;
- After.Next := Node;
- inc(FCount);
- Result := Node;
- end;
- end;
- end;
- end;
- (* Add all the elements in *pList to the head of this list.
- Return True if it all worked, FALSE if it didn't.
- If it fails some elements may have been added.
- *)
- function TBCBaseList.AddHead(List: TBCBaseList): BOOL;
- var
- pos: Position;
- begin
- (* lock the object before starting then enumerate
- each entry in the source list and add them one by one to
- our list (while still holding the object lock)
- Lock the other list too.
- To avoid reversing the list, traverse it backwards.
- *)
- pos := list.GetTailPositionI;
- while (pos <> nil) do
- begin
- if (nil = AddHeadI(List.GetI(pos))) then
- begin
- Result := FALSE;
- Exit;
- end;
- pos := list.Prev(pos)
- end;
- Result := True;
- end;
- (* Add this object to the head end of our list
- Return the new head position.
- *)
- function TBCBaseList.AddHeadI(Obj: Pointer): Position;
- var Node: TBCNode;
- begin
- (* If there is a node objects in the cache then use
- that otherwise we will have to create a new one *)
- Node := FCache.RemoveFromCache;
- if (Node = nil) then
- Node := TBCNode.Create;
- // Check we have a valid object
- if (Node = nil) then
- begin
- Result := nil;
- Exit;
- end;
- (* Initialise all the CNode object
- just in case it came from the cache
- *)
- Node.Data := Obj;
- // chain it in (set four pointers)
- Node.Prev := nil;
- Node.Next := FFirst;
- if (FFirst = nil) then
- FLast := Node;
- FFirst.Prev := Node;
- FFirst := Node;
- inc(FCount);
- Result := Node;
- end;
- (* Add all the elements in *pList to the tail of this list.
- Return True if it all worked, FALSE if it didn't.
- If it fails some elements may have been added.
- *)
- function TBCBaseList.AddTail(List: TBCBaseList): boolean;
- var pos: Position;
- begin
- (* lock the object before starting then enumerate
- each entry in the source list and add them one by one to
- our list (while still holding the object lock)
- Lock the other list too.
- *)
- Result := false;
- pos := List.GetHeadPositionI;
- while (pos <> nil) do
- if (nil = AddTailI(List.GetNextI(pos))) then
- Exit;
- Result := True;
- end;
- (* Add this object to the tail end of our list
- Return the new tail position.
- *)
- function TBCBaseList.AddTailI(Obj: Pointer): Position;
- var
- Node: TBCNode;
- begin
- // Lock the critical section before continuing
- // ASSERT(pObject); // NULL pointers in the list are allowed.
- (* If there is a node objects in the cache then use
- that otherwise we will have to create a new one *)
- Node := FCache.RemoveFromCache;
- if (Node = nil) then
- Node := TBCNode.Create;
- // Check we have a valid object
- if Node = nil then // HG: out of memory ???
- begin
- Result := nil;
- Exit;
- end;
- (* Initialise all the CNode object
- just in case it came from the cache
- *)
- Node.Data := Obj;
- Node.Next := nil;
- Node.Prev := FLast;
- if (FLast = nil) then
- FFirst := Node;
- FLast.Next := Node;
- (* Set the new last node pointer and also increment the number
- of list entries, the critical section is unlocked when we
- exit the function
- *)
- FLast := Node;
- inc(FCount);
- Result := Node;
- end;
- (* Constructor calls a separate initialisation function that
- creates a node cache, optionally creates a lock object
- and optionally creates a signaling object.
- By default we create a locking object, a DEFAULTCACHE sized
- cache but no event object so the list cannot be used in calls
- to WaitForSingleObject
- *)
- constructor TBCBaseList.Create(Name: string; Items: Integer = DEFAULTCACHE);
- begin
- {$ifdef DEBUG}
- inherited Create(Name);
- {$endif}
- FFirst := nil;
- FLast := nil;
- FCount := 0;
- FCache := TBCNodeCache.Create(Items);
- end;
- (* The destructor enumerates all the node objects in the list and
- in the cache deleting each in turn. We do not do any processing
- on the objects that the list holds (i.e. points to) so if they
- represent interfaces for example the creator of the list should
- ensure that each of them is released before deleting us
- *)
- destructor TBCBaseList.Destroy;
- begin
- RemoveAll;
- FCache.Free;
- inherited;
- end;
- (* Return the first position in the list which holds the given pointer.
- Return NULL if it's not found.
- *)
- function TBCBaseList.FindI(Obj: Pointer): Position;
- begin
- Result := GetHeadPositionI;
- while (Result <> nil) do
- begin
- if (GetI(Result) = Obj) then Exit;
- Result := Next(Result);
- end;
- end;
- (* Get the number of objects in the list,
- Get the lock before accessing the count.
- Locking may not be entirely necessary but it has the side effect
- of making sure that all operations are complete before we get it.
- So for example if a list is being added to this list then that
- will have completed in full before we continue rather than seeing
- an intermediate albeit valid state
- *)
- function TBCBaseList.GetCountI: Integer;
- begin
- Result := FCount;
- end;
- (* Return a position enumerator for the entire list.
- A position enumerator is a pointer to a node object cast to a
- transparent type so all we do is return the head/tail node
- pointer in the list.
- WARNING because the position is a pointer to a node there is
- an implicit assumption for users a the list class that after
- deleting an object from the list that any other position
- enumerators that you have may be invalid (since the node
- may be gone).
- *)
- function TBCBaseList.GetHeadPositionI: Position;
- begin
- result := Position(FFirst);
- end;
- (* Return the object at p.
- Asking for the object at NULL ASSERTs then returns NULL
- The object is NOT locked. The list is not being changed
- in any way. If another thread is busy deleting the object
- then locking would only result in a change from one bad
- behaviour to another.
- *)
- function TBCBaseList.GetI(p: Position): Pointer;
- begin
- if (p = nil) then
- Result := nil else
- Result := TBCNode(p).Data;
- end;
- (* Return the object at rp, update rp to the next object from
- the list or NULL if you have moved over the last object.
- You may still call this function once we return NULL but
- we will continue to return a NULL position value
- *)
- function TBCBaseList.GetNextI(var rp: Position): Pointer;
- var
- pn: TBCNode;
- begin
- // have we reached the end of the list
- if (rp = nil) then
- Result := nil else
- begin
- // Lock the object before continuing
- // Copy the original position then step on
- pn := rp;
- ASSERT(pn <> nil);
- rp := Position(pn.Next);
- // Get the object at the original position from the list
- Result := pn.Data;
- end;
- end;
- function TBCBaseList.GetTailPositionI: Position;
- begin
- Result := Position(FLast);
- end;
- (* Mirror image of MoveToTail:
- Split self before position p in self.
- Retain in self the head portion of the original self
- Add the tail portion to the start (i.e. head) of *pList
- Return True if it all worked, FALSE if it didn't.
- e.g.
- foo->MoveToHead(foo->GetTailPosition(), bar);
- moves one element from the tail of foo to the head of bar
- foo->MoveToHead(NULL, bar);
- is a no-op
- foo->MoveToHead(foo->GetHeadPosition, bar);
- concatenates foo onto the start of bar and empties foo.
- *)
- function TBCBaseList.MoveToHead(pos: Position; List: TBCBaseList): boolean;
- var
- p: TBCNode;
- m: Integer;
- begin
- // See the comments on the algorithm in MoveToTail
- if (pos = nil) then
- Result := True else // no-op. Eliminates special cases later.
- begin
- // Make cMove the number of nodes to move
- p := pos;
- m := 0; // number of nodes to move
- while(p <> nil) do
- begin
- p := p.Next;
- inc(m);
- end;
- // Join the two chains together
- if (List.FFirst <> nil) then
- List.FFirst.Prev := FLast;
- if (FLast <> nil) then
- FLast.Next := List.FFirst;
- // set first and last pointers
- p := pos;
- if (List.FLast = nil) then
- List.FLast := FLast;
- FLast := p.Prev;
- if (FLast = nil) then
- FFirst := nil;
- List.FFirst := p;
- // Break the chain after p to create the new pieces
- if (FLast <> nil) then
- FLast.Next := nil;
- p.Prev := nil;
- // Adjust the counts
- dec(FCount, m);
- inc(List.FCount, m);
- Result := True;
- end;
- end;
- (* Split self after position p in self
- Retain as self the tail portion of the original self
- Add the head portion to the tail end of *pList
- Return True if it all worked, FALSE if it didn't.
- e.g.
- foo->MoveToTail(foo->GetHeadPosition(), bar);
- moves one element from the head of foo to the tail of bar
- foo->MoveToTail(NULL, bar);
- is a no-op
- foo->MoveToTail(foo->GetTailPosition, bar);
- concatenates foo onto the end of bar and empties foo.
- A better, except excessively long name might be
- MoveElementsFromHeadThroughPositionToOtherTail
- *)
- function TBCBaseList.MoveToTail(pos: Position; List: TBCBaseList): boolean;
- var
- p: TBCNode;
- m: Integer;
- begin
- (* Algorithm:
- Note that the elements (including their order) in the concatenation
- of *pList to the head of self is invariant.
- 1. Count elements to be moved
- 2. Join *pList onto the head of this to make one long chain
- 3. Set first/Last pointers in self and *pList
- 4. Break the chain at the new place
- 5. Adjust counts
- 6. Set/Reset any events
- *)
- if (pos = nil) then
- Result := True else // no-op. Eliminates special cases later.
- begin
- // Make m the number of nodes to move
- p := pos;
- m := 0; // number of nodes to move
- while(p <> nil) do
- begin
- p := p.Prev;
- inc(m);
- end;
- // Join the two chains together
- if (List.FLast <> nil) then
- List.FLast.Next := FFirst;
- if (FFirst <> nil) then
- FFirst.Prev := List.FLast;
- // set first and last pointers
- p := pos;
- if (List.FFirst = nil) then
- List.FFirst := FFirst;
- FFirst := p.Next;
- if (FFirst = nil) then
- FLast := nil;
- List.FLast := p;
- // Break the chain after p to create the new pieces
- if (FFirst <> nil) then
- FFirst.Prev := nil;
- p.Next := nil;
- // Adjust the counts
- dec(FCount, m);
- inc(List.FCount, m);
- Result := True;
- end;
- end;
- function TBCBaseList.Next(pos: Position): Position;
- begin
- if (pos = nil) then
- Result := Position(FFirst) else
- Result := Position(TBCNode(pos).Next);
- end;
- function TBCBaseList.Prev(pos: Position): Position;
- begin
- if (pos = nil) then
- Result := Position(FLast) else
- Result := Position(TBCNode(pos).Prev);
- end;
- (* Remove all the nodes from the list but don't do anything
- with the objects that each node looks after (this is the
- responsibility of the creator).
- Aa a last act we reset the signalling event
- (if available) to indicate to clients that the list
- does not have any entries in it.
- *)
- procedure TBCBaseList.RemoveAll;
- var pn, op: TBCNode;
- begin
- (* Free up all the CNode objects NOTE we don't bother putting the
- deleted nodes into the cache as this method is only really called
- in serious times of change such as when we are being deleted at
- which point the cache will be deleted anyway *)
- pn := FFirst;
- while (pn <> nil) do
- begin
- op := pn;
- pn := pn.Next;
- op.Free;
- end;
- (* Reset the object count and the list pointers *)
- FCount := 0;
- FFirst := nil;
- FLast := nil;
- end;
- (* Remove the first node in the list (deletes the pointer to its object
- from the list, does not free the object itself).
- Return the pointer to its object or NULL if empty
- *)
- function TBCBaseList.RemoveHeadI: Pointer;
- begin
- (* All we do is get the head position and ask for that to be deleted.
- We could special case this since some of the code path checking
- in Remove() is redundant as we know there is no previous
- node for example but it seems to gain little over the
- added complexity
- *)
- Result := RemoveI(FFirst);
- end;
- (* Remove the pointer to the object in this position from the list.
- Deal with all the chain pointers
- Return a pointer to the object removed from the list.
- The node object that is freed as a result
- of this operation is added to the node cache where
- it can be used again.
- Remove(NULL) is a harmless no-op - but probably is a wart.
- *)
- function TBCBaseList.RemoveI(pos: Position): Pointer;
- var
- Current, Node: TBCNode;
- begin
- (* Lock the critical section before continuing *)
- if (pos = nil) then
- Result := nil else
- begin
- Current := pos;
- ASSERT(Current <> nil);
- // Update the previous node
- Node := Current.Prev;
- if (Node = nil) then
- FFirst := Current.Next else
- Node.Next := Current.Next;
- // Update the following node
- Node := Current.Next;
- if (Node = nil) then
- FLast := Current.Prev else
- Node.Prev := Current.Prev;
- // Get the object this node was looking after */
- Result := Current.Data;
- // ASSERT(pObject != NULL); // NULL pointers in the list are allowed.
- (* Try and add the node object to the cache -
- a NULL return code from the cache means we ran out of room.
- The cache size is fixed by a constructor argument when the
- list is created and defaults to DEFAULTCACHE.
- This means that the cache will have room for this many
- node objects. So if you have a list of media samples
- and you know there will never be more than five active at
- any given time of them for example then override the default
- constructor
- *)
- FCache.AddToCache(Current);
- // If the list is empty then reset the list event
- Dec(FCount);
- ASSERT(FCount >= 0);
- end;
- end;
- (* Remove the last node in the list (deletes the pointer to its object
- from the list, does not free the object itself).
- Return the pointer to its object or NULL if empty
- *)
- function TBCBaseList.RemoveTailI: Pointer;
- begin
- (* All we do is get the tail position and ask for that to be deleted.
- We could special case this since some of the code path checking
- in Remove() is redundant as we know there is no previous
- node for example but it seems to gain little over the
- added complexity
- *)
- Result := RemoveI(FLast);
- end;
- (* Reverse the order of the [pointers to] objects in slef *)
- procedure TBCBaseList.Reverse;
- var p, q: TBCNode;
- begin
- (* algorithm:
- The obvious booby trap is that you flip pointers around and lose
- addressability to the node that you are going to process next.
- The easy way to avoid this is do do one chain at a time.
- Run along the forward chain,
- For each node, set the reverse pointer to the one ahead of us.
- The reverse chain is now a copy of the old forward chain, including
- the NULL termination.
- Run along the reverse chain (i.e. old forward chain again)
- For each node set the forward pointer of the node ahead to point back
- to the one we're standing on.
- The first node needs special treatment,
- it's new forward pointer is NULL.
- Finally set the First/Last pointers
- *)
- // Yes we COULD use a traverse, but it would look funny!
- p := FFirst;
- while (p <> nil) do
- begin
- q := p.Next;
- p.Next := p.Prev;
- p.Prev := q;
- p := q;
- end;
- p := FFirst;
- FFirst := FLast;
- FLast := p;
- end;
- { TBCSource }
- function TBCSource.AddPin(Stream: TBCSourceStream): HRESULT;
- begin
- FStateLock.Lock;
- try
- inc(FPins);
- ReallocMem(FStreams, FPins * SizeOf(TBCSourceStream));
- TStreamArray(FStreams)[FPins-1] := Stream;
- Result := S_OK;
- finally
- FStateLock.UnLock;
- end;
- end;
- // milenko start (delphi 5 doesn't IInterface - changed IInterface to IUnknown)
- constructor TBCSource.Create(const Name: string; unk: IUnknown;
- // milenko end
- const clsid: TGUID; out hr: HRESULT);
- begin
- FStateLock := TBCCritSec.Create;
- // nev: changed 02/17/04
- inherited Create(Name, unk, FStateLock, clsid, hr);
- FPins := 0;
- FStreams := nil;
- end;
- // milenko start (delphi 5 doesn't IInterface - changed IInterface to IUnknown)
- constructor TBCSource.Create(const Name: string; unk: IUnknown;
- // milenko end
- const clsid: TGUID);
- begin
- FStateLock := TBCCritSec.Create;
- inherited Create(Name, unk, FStateLock, clsid);
- FPins := 0;
- FStreams := nil;
- end;
- destructor TBCSource.Destroy;
- begin
- // Free our pins and pin array
- while (FPins <> 0) do
- // deleting the pins causes them to be removed from the array...
- TStreamArray(FStreams)[FPins - 1].Free;
- if Assigned(FStreams) then FreeMem(FStreams);
- ASSERT(FPins = 0);
- inherited;
- end;
- // Set Pin to the IPin that has the id Id.
- // or to nil if the Id cannot be matched.
- function TBCSource.FindPin(Id: PWideChar; out Pin: IPin): HRESULT;
- var
- i : integer;
- Code : integer;
- begin
- // The -1 undoes the +1 in QueryId and ensures that totally invalid
- // strings (for which WstrToInt delivers 0) give a deliver a NULL pin.
- // DCoder (1. Nov 2003)
- // StrToInt throws EConvertError Exceptions if
- // a Filter calls FindPin with a String instead of a Number in ID.
- // To be sure, capture the Error Handling by using Val and call
- // the inherited function if Val fails.
-
- Val(Id,i,Code);
- if Code = 0 then
- begin
- i := i - 1;
- Pin := GetPin(i);
- if (Pin <> nil) then
- Result := NOERROR else
- Result := VFW_E_NOT_FOUND;
- end else Result := inherited FindPin(Id,Pin);
- end;
- // return the number of the pin with this IPin or -1 if none
- function TBCSource.FindPinNumber(Pin: IPin): Integer;
- begin
- for Result := 0 to FPins - 1 do
- if (IPin(TStreamArray(FStreams)[Result]) = Pin) then
- Exit;
- Result := -1;
- end;
- // Return a non-addref'd pointer to pin n
- // needed by CBaseFilter
- function TBCSource.GetPin(n: Integer): TBCBasePin;
- begin
- FStateLock.Lock;
- try
- // n must be in the range 0..m_iPins-1
- // if m_iPins>n && n>=0 it follows that m_iPins>0
- // which is what used to be checked (i.e. checking that we have a pin)
- if ((n >= 0) and (n < FPins)) then
- begin
- ASSERT(TStreamArray(FStreams)[n] <> nil);
- Result := TStreamArray(FStreams)[n];
- end else
- Result := nil;
- finally
- FStateLock.UnLock;
- end;
- end;
- // Returns the number of pins this filter has
- function TBCSource.GetPinCount: Integer;
- begin
- FStateLock.Lock;
- try
- Result := FPins;
- finally
- FStateLock.UnLock;
- end;
- end;
- function TBCSource.RemovePin(Stream: TBCSourceStream): HRESULT;
- var i, j: Integer;
- begin
- for i := 0 to FPins - 1 do
- begin
- if (TStreamArray(FStreams)[i] = Stream) then
- begin
- if (FPins = 1) then
- begin
- FreeMem(FStreams);
- FStreams := nil;
- end else
- begin
- // no need to reallocate
- j := i + 1;
- while (j < FPins) do
- begin
- TStreamArray(FStreams)[j-1] := TStreamArray(FStreams)[j];
- inc(j);
- end;
- end;
- dec(FPins);
- Result := S_OK;
- Exit;
- end;
- end;
- Result := S_FALSE;
- end;
- { TBCSourceStream }
- // The pin is active - start up the worker thread
- function TBCSourceStream.Active: HRESULT;
- begin
- FFilter.FStateLock.Lock;
- try
- if (FFilter.IsActive) then
- begin
- Result := S_FALSE; // succeeded, but did not allocate resources (they already exist...)
- Exit;
- end;
- // do nothing if not connected - its ok not to connect to
- // all pins of a source filter
- if not IsConnected then
- begin
- Result := NOERROR;
- Exit;
- end;
- Result := inherited Active;
- if FAILED(Result) then
- Exit;
- ASSERT(not FThread.ThreadExists);
- // start the thread
- if not FThread.Create_ then
- begin
- Result := E_FAIL;
- Exit;
- end;
- // Tell thread to initialize. If OnThreadCreate Fails, so does this.
- Result := Init;
- if FAILED(Result) then
- Exit;
- Result := Pause;
- finally
- FFilter.FStateLock.UnLock;
- end;
- end;
- // Do we support this type? Provides the default support for 1 type.
- function TBCSourceStream.CheckMediaType(MediaType: PAMMediaType): HRESULT;
- var mt: TAMMediaType;
- pmt: PAMMediaType;
- begin
- FFilter.FStateLock.Lock;
- try
- pmt := @mt;
- GetMediaType(pmt);
- if TBCMediaType(pmt).Equal(MediaType) then
- Result := NOERROR else
- Result := E_FAIL;
- finally
- FFilter.FStateLock.UnLock;
- end;
- end;
- function TBCSourceStream.CheckRequest(var com: TThreadCommand): boolean;
- begin
- Result := FThread.CheckRequest(@Com);
- end;
- // increments the number of pins present on the filter
- constructor TBCSourceStream.Create(const ObjectName: string;
- out hr: HRESULT; Filter: TBCSource; const Name: WideString);
- begin
- FThread := TBCAMThread.Create;
- FThread.FThreadProc := ThreadProc;
- inherited Create(ObjectName, Filter, Filter.FStateLock, hr, Name);
- FFilter := Filter;
- hr := FFilter.AddPin(Self);
- end;
- // Decrements the number of pins on this filter
- destructor TBCSourceStream.Destroy;
- begin
- FFilter.RemovePin(Self);
- inherited;
- FThread.Free;
- end;
- // Grabs a buffer and calls the users processing function.
- // Overridable, so that different delivery styles can be catered for.
- function TBCSourceStream.DoBufferProcessingLoop: HRESULT;
- var
- com: TThreadCommand;
- Sample: IMediaSample;
- begin
- OnThreadStartPlay;
- repeat
- begin
- while not CheckRequest(com) do
- begin
- Result := GetDeliveryBuffer(Sample, nil, nil, 0);
- if FAILED(result) then
- begin
- Sleep(1);
- continue; // go round again. Perhaps the error will go away
- // or the allocator is decommited & we will be asked to
- // exit soon.
- end;
- // Virtual function user will override.
- Result := FillBuffer(Sample);
- if (Result = S_OK) then
- begin
- Result := Deliver(Sample);
- Sample := nil;
- // downstream filter returns S_FALSE if it wants us to
- // stop or an error if it's reporting an error.
- if (Result <> S_OK) then
- begin
- {$IFDEF DEBUG}
- DbgLog(format('Deliver() returned %08x; stopping', [Result]));
- {$ENDIF}
- Result := S_OK;
- Exit;
- end;
- end else
- if (Result = S_FALSE) then
- begin
- // derived class wants us to stop pushing data
- Sample := nil;
- DeliverEndOfStream;
- Result := S_OK;
- Exit;
- end else
- begin
- // derived class encountered an error
- Sample := nil;
- {$IFDEF DEBUG}
- DbgLog(format('Error %08lX from FillBuffer!!!', [Result]));
- {$ENDIF}
- DeliverEndOfStream;
- FFilter.NotifyEvent(EC_ERRORABORT, Result, 0);
- Exit;
- end;
- // all paths release the sample
- end;
- // For all commands sent to us there must be a Reply call!
- if ((com = CMD_RUN) or (com = CMD_PAUSE)) then
- FThread.Reply(NOERROR) else
- if (com <> CMD_STOP) then
- begin
- Fthread.Reply(DWORD(E_UNEXPECTED));
- {$IFDEF DEBUG}
- DbgLog('Unexpected command!!!');
- {$ENDIF}
- end
- end until (com = CMD_STOP);
- Result := S_FALSE;
- end;
- function TBCSourceStream.Exit_: HRESULT;
- begin
- Result := FThread.CallWorker(Ord(CMD_EXIT));
- end;
- function TBCSourceStream.GetMediaType(MediaType: PAMMediaType): HRESULT;
- begin
- Result := E_UNEXPECTED;
- end;
- function TBCSourceStream.GetMediaType(Position: integer;
- out MediaType: PAMMediaType): HRESULT;
- begin
- // By default we support only one type
- // Position indexes are 0-n
- FFilter.FStateLock.Lock;
- try
- if (Position = 0) then
- Result := GetMediaType(MediaType)
- else
- if (Position > 0) then
- Result := VFW_S_NO_MORE_ITEMS else
- Result := E_INVALIDARG;
- finally
- FFilter.FStateLock.UnLock;
- end;
- end;
- function TBCSourceStream.GetRequest: TThreadCommand;
- begin
- Result := TThreadCommand(FThread.GetRequest);
- end;
- // Pin is inactive - shut down the worker thread
- // Waits for the worker to exit before returning.
- function TBCSourceStream.Inactive: HRESULT;
- begin
- FFilter.FStateLock.Lock;
- try
- // do nothing if not connected - its ok not to connect to
- // all pins of a source filter
- if not IsConnected then
- begin
- Result := NOERROR;
- Exit;
- end;
- // !!! need to do this before trying to stop the thread, because
- // we may be stuck waiting for our own allocator!!!
- Result := inherited Inactive; // call this first to Decommit the allocator
- if FAILED(Result) then
- Exit;
- if FThread.ThreadExists then
- begin
- Result := Stop;
- if FAILED(Result) then
- Exit;
- Result := Exit_;
- if FAILED(Result) then
- Exit;
- FThread.Close; // Wait for the thread to exit, then tidy up.
- end;
- Result := NOERROR;
- finally
- FFilter.FStateLock.UnLock;
- end;
- end;
- function TBCSourceStream.Init: HRESULT;
- begin
- Result := FThread.CallWorker(Ord(CMD_INIT));
- end;
- function TBCSourceStream.OnThreadCreate: HRESULT;
- begin
- Result := NOERROR;
- end;
- function TBCSourceStream.OnThreadDestroy: HRESULT;
- begin
- Result := NOERROR;
- end;
- function TBCSourceStream.OnThreadStartPlay: HRESULT;
- begin
- Result := NOERROR;
- end;
- function TBCSourceStream.Pause: HRESULT;
- begin
- Result := FThread.CallWorker(Ord(CMD_PAUSE));
- end;
- // Set Id to point to a CoTaskMemAlloc'd
- function TBCSourceStream.QueryId(out id: PWideChar): HRESULT;
- var
- i: Integer;
- begin
- // We give the pins id's which are 1,2,...
- // FindPinNumber returns -1 for an invalid pin
- i := 1 + FFilter.FindPinNumber(Self);
- if (i < 1) then
- Result := VFW_E_NOT_FOUND else
- Result := AMGetWideString(IntToStr(i), id);
- end;
- function TBCSourceStream.Run: HRESULT;
- begin
- Result := FThread.CallWorker(Ord(CMD_RUN));
- end;
- function TBCSourceStream.Stop: HRESULT;
- begin
- Result := FThread.CallWorker(Ord(CMD_STOP));
- end;
- // When this returns the thread exits
- // Return codes > 0 indicate an error occured
- function TBCSourceStream.ThreadProc: DWORD;
- var
- com, cmd: TThreadCommand;
- begin
- repeat
- com := GetRequest;
- if (com <> CMD_INIT) then
- begin
- {$IFDEF DEBUG}
- DbgLog(self, 'Thread expected init command');
- {$ENDIF}
- FThread.Reply(DWORD(E_UNEXPECTED));
- end;
- until (com = CMD_INIT);
- {$IFDEF DEBUG}
- DbgLog(self, 'Worker thread initializing');
- {$ENDIF}
- Result := OnThreadCreate; // perform set up tasks
- if FAILED(Result) then
- begin
- {$IFDEF DEBUG}
- DbgLog(Self, 'OnThreadCreate failed. Aborting thread.');
- {$ENDIF}
- OnThreadDestroy();
- FThread.Reply(Result); // send failed return code from OnThreadCreate
- Result := 1;
- Exit;
- end;
- // Initialisation suceeded
- FThread.Reply(NOERROR);
- repeat
- cmd := GetRequest;
- // nev: changed 02/17/04
- // "repeat..until false" ensures, that if cmd = CMD_RUN
- // the next executing block will be CMD_PAUSE handler block.
- // This corresponds to the original C "switch" functionality
- repeat
- case cmd of
- CMD_EXIT, CMD_STOP:
- begin
- FThread.Reply(NOERROR);
- Break;
- end;
- CMD_RUN:
- begin
- {$IFDEF DEBUG}
- DbgLog(Self, 'CMD_RUN received before a CMD_PAUSE???');
- {$ENDIF}
- // !!! fall through???
- cmd := CMD_PAUSE;
- end;
- CMD_PAUSE:
- begin
- FThread.Reply(NOERROR);
- DoBufferProcessingLoop;
- Break;
- end;
- else
- {$IFDEF DEBUG}
- DbgLog(self, format('Unknown command %d received!', [Integer(cmd)]));
- {$ENDIF}
- FThread.Reply(DWORD(E_NOTIMPL));
- Break;
- end;
- until False;
- until (cmd = CMD_EXIT);
- Result := OnThreadDestroy; // tidy up.
- if FAILED(Result) then
- begin
- {$IFDEF DEBUG}
- DbgLog(self, 'OnThreadDestroy failed. Exiting thread.');
- {$ENDIF}
- Result := 1;
- Exit;
- end;
- {$IFDEF DEBUG}
- DbgLog(Self, 'worker thread exiting');
- {$ENDIF}
- Result := 0;
- end;
- function TimeKillSynchronousFlagAvailable: Boolean;
- var
- osverinfo: TOSVERSIONINFO;
- begin
- osverinfo.dwOSVersionInfoSize := sizeof(osverinfo);
- if GetVersionEx(osverinfo) then
- // Windows XP's major version is 5 and its' minor version is 1.
- // timeSetEvent() started supporting the TIME_KILL_SYNCHRONOUS flag
- // in Windows XP.
- Result := (osverinfo.dwMajorVersion > 5) or
- ((osverinfo.dwMajorVersion = 5) and (osverinfo.dwMinorVersion >= 1))
- else
- Result := False;
- end;
- function CompatibleTimeSetEvent(Delay, Resolution: UINT;
- TimeProc: TFNTimeCallBack; User: DWORD; Event: UINT): MMResult;
- // milenko start (replaced with global variables)
- //const
- //{$IFOPT J-}
- //{$DEFINE ResetJ}
- //{$J+}
- //{$ENDIF}
- // IsCheckedVersion: Bool = False;
- // IsTimeKillSynchronousFlagAvailable: Bool = False;
- //{$IFDEF ResetJ}
- //{$J-}
- //{$UNDEF ResetJ}
- //{$ENDIF}
- const
- TIME_KILL_SYNCHRONOUS = $100;
- // Milenko end
- var
- Event_: UINT;
- begin
- Event_ := Event;
- // ??? TIME_KILL_SYNCHRONOUS flag is defined in MMSystem for XP:
- // need to check that D7 unit for proper compilation flag
- // Milenko start (no need for "ifdef xp" in delphi)
- // {$IFDEF XP}
- if not IsCheckedVersion then
- begin
- IsTimeKillSynchronousFlagAvailable := TimeKillSynchronousFlagAvailable;
- IsCheckedVersion := true;
- end;
- if IsTimeKillSynchronousFlagAvailable then
- Event_ := Event_ or TIME_KILL_SYNCHRONOUS;
- // {$ENDIF}
- // Milenko end
- Result := timeSetEvent(Delay, Resolution, TimeProc, User, Event_);
- end;
- // ??? See Measure.h for Msr_??? definition
- // milenko start (only needed with PERF)
- {$IFDEF PERF}
- type
- TIncidentRec = packed record
- Name: String[255];
- end;
- TIncidentLog = packed record
- Id: Integer;
- Time: TReferenceTime;
- Data: Integer;
- Note: String[10];
- end;
- var
- Incidents: array of TIncidentRec;
- IncidentsLog: array of TIncidentLog;
- {$ENDIF}
- // milenko end
- function MSR_REGISTER(s: String): Integer;
- // milenko start (only needed with PERF)
- {$IFDEF PERF}
- var
- k: Integer;
- {$ENDIF}
- // milenko end
- begin
- // milenko start (only needed with PERF)
- {$IFDEF PERF}
- k := Length(Incidents) + 1;
- SetLength(Incidents, k);
- Incidents[k-1].Name := Copy(s, 0, 255);
- Result := k-1;
- {$ELSE}
- Result := 0;
- {$ENDIF}
- // milenko end
- end;
- procedure MSR_START(Id_: Integer);
- {$IFDEF PERF}
- var
- k: Integer;
- {$ENDIF}
- begin
- {$IFDEF PERF}
- Assert((Id_>=0) and (Id_<Length(Incidents)));
- k := Length(IncidentsLog) + 1;
- SetLength(IncidentsLog, k);
- with IncidentsLog[k-1] do
- begin
- Id := Id_;
- Time := timeGetTime;
- Data := 0;
- Note := Copy('START', 0, 10);
- end;
- {$ENDIF}
- end;
- procedure MSR_STOP(Id_: Integer);
- {$IFDEF PERF}
- var
- k: Integer;
- {$ENDIF}
- begin
- {$IFDEF PERF}
- Assert((Id_>=0) and (Id_<Length(Incidents)));
- k := Length(IncidentsLog) + 1;
- SetLength(IncidentsLog, k);
- with IncidentsLog[k-1] do
- begin
- Id := Id_;
- Time := timeGetTime;
- Data := 0;
- Note := Copy('STOP', 0, 10);
- end;
- {$ENDIF}
- end;
- procedure MSR_INTEGER(Id_, i: Integer);
- {$IFDEF PERF}
- var
- k: Integer;
- {$ENDIF}
- begin
- {$IFDEF PERF}
- Assert((Id_>=0) and (Id_<Length(Incidents)));
- k := Length(IncidentsLog) + 1;
- SetLength(IncidentsLog, k);
- with IncidentsLog[k-1] do
- begin
- Id := Id_;
- Time := timeGetTime;
- Data := i;
- Note := Copy('START', 0, 10);
- end;
- {$ENDIF}
- end;
- // #define DO_MOVING_AVG(avg,obs) (avg = (1024*obs + (AVGPERIOD-1)*avg)/AVGPERIOD)
- procedure DO_MOVING_AVG(var avg, obs: Integer);
- begin
- avg := (1024 * obs + (AVGPERIOD - 1) * avg) div AVGPERIOD;
- end;
- // Helper function for clamping time differences
- function TimeDiff(rt: TReferenceTime): Integer;
- begin
- if (rt < -(50 * UNITS)) then
- Result := -(50 * UNITS)
- else
- if (rt > 50 * UNITS) then
- Result := 50 * UNITS
- else
- Result := Integer(rt);
- end;
- // Implements the CBaseRenderer class
- constructor TBCBaseRenderer.Create(RendererClass: TGUID; Name: PChar;
- Unk: IUnknown; hr: HResult);
- begin
- FInterfaceLock := TBCCritSec.Create;
- FRendererLock := TBCCritSec.Create;
- FObjectCreationLock := TBCCritSec.Create;
- inherited Create(Name, Unk, FInterfaceLock, RendererClass);
- FCompleteEvent := TBCAMEvent.Create(True);
- FRenderEvent := TBCAMEvent.Create(True);
- FAbort := False;
- FPosition := nil;
- FThreadSignal := TBCAMEvent.Create(True);
- FIsStreaming := False;
- FIsEOS := False;
- FIsEOSDelivered := False;
- FMediaSample := nil;
- FAdvisedCookie := 0;
- FQSink := nil;
- FInputPin := nil;
- FRepaintStatus := True;
- FSignalTime := 0;
- FInReceive := False;
- FEndOfStreamTimer := 0;
- Ready;
- {$IFDEF PERF}
- FBaseStamp := MSR_REGISTER('BaseRenderer: sample time stamp');
- FBaseRenderTime := MSR_REGISTER('BaseRenderer: draw time(msec)');
- FBaseAccuracy := MSR_REGISTER('BaseRenderer: Accuracy(msec)');
- {$ENDIF}
- end;
- // Delete the dynamically allocated IMediaPosition and IMediaSeeking helper
- // object. The object is created when somebody queries us. These are standard
- // control interfaces for seeking and setting start/stop positions and rates.
- // We will probably also have made an input pin based on CRendererInputPin
- // that has to be deleted, it's created when an enumerator calls our GetPin
- destructor TBCBaseRenderer.Destroy;
- begin
- Assert(not FIsStreaming);
- Assert(FEndOfStreamTimer = 0);
- StopStreaming;
- ClearPendingSample;
- // Delete any IMediaPosition implementation
- if Assigned(FPosition) then
- FreeAndNil(FPosition);
- // Delete any input pin created
- if Assigned(FInputPin) then
- FreeAndNil(FInputPin);
- // Release any Quality sink
- Assert(FQSink = nil);
- // Release critical sections objects
- // ??? will be deleted by the parent class destroy FreeAndNil(FInterfaceLock);
- FreeAndNil(FRendererLock);
- FreeAndNil(FObjectCreationLock);
- FreeAndNil(FCompleteEvent);
- FreeAndNil(FRenderEvent);
- FreeAndNil(FThreadSignal);
- inherited Destroy;
- end;
- // This returns the IMediaPosition and IMediaSeeking interfaces
- function TBCBaseRenderer.GetMediaPositionInterface(IID: TGUID;
- out Obj): HResult;
- var
- hr: HResult;
- begin
- FObjectCreationLock.Lock;
- try
- if Assigned(FPosition) then
- begin
- // Milenko start
- // Result := FPosition.QueryInterface(IID, Obj);
- Result := FPosition.NonDelegatingQueryInterface(IID, Obj);
- // Milenko end
- Exit;
- end;
- hr := NOERROR;
- // Create implementation of this dynamically since sometimes we may
- // never try and do a seek. The helper object implements a position
- // control interface (IMediaPosition) which in fact simply takes the
- // calls normally from the filter graph and passes them upstream
- //hr := CreatePosPassThru(GetOwner, False, GetPin(0), FPosition);
- FPosition := TBCRendererPosPassThru.Create('Renderer TBCPosPassThru',
- Inherited GetOwner, hr, GetPin(0));
- if (FPosition = nil) then
- begin
- Result := E_OUTOFMEMORY;
- Exit;
- end;
- if (Failed(hr)) then
- begin
- FreeAndNil(FPosition);
- Result := E_NOINTERFACE;
- Exit;
- end;
- // milenko start (needed or the class will destroy itself. Disadvantage=Destructor is not called)
- // Solution is to keep FPosition alive without adding a Reference Count to it. But how???
- FPosition._AddRef;
- // milenko end
- Result := GetMediaPositionInterface(IID, Obj);
- finally
- FObjectCreationLock.UnLock;
- end;
- end;
- // milenko start (workaround for destructor issue with FPosition)
- function TBCBaseRenderer.JoinFilterGraph(pGraph: IFilterGraph;
- pName: PWideChar): HRESULT;
- begin
- if (pGraph = nil) and (FPosition <> nil) then
- begin
- FPosition._Release;
- Pointer(FPosition) := nil;
- end;
- Result := inherited JoinFilterGraph(pGraph,pName);
- end;
- // milenko end
- // Overriden to say what interfaces we support and where
- function TBCBaseRenderer.NonDelegatingQueryInterface(const IID: TGUID;
- out Obj): HResult;
- begin
- // Milenko start (removed unnessacery code)
- // Do we have this interface
- if IsEqualGUID(IID, IID_IMediaPosition) or IsEqualGUID(IID, IID_IMediaSeeking)
- then Result := GetMediaPositionInterface(IID,Obj)
- else Result := inherited NonDelegatingQueryInterface(IID, Obj);
- // Milenko end
- end;
- // This is called whenever we change states, we have a manual reset event that
- // is signalled whenever we don't won't the source filter thread to wait in us
- // (such as in a stopped state) and likewise is not signalled whenever it can
- // wait (during paused and running) this function sets or resets the thread
- // event. The event is used to stop source filter threads waiting in Receive
- function TBCBaseRenderer.SourceThreadCanWait(CanWait: Boolean): HResult;
- begin
- if CanWait then
- FThreadSignal.Reset
- else
- FThreadSignal.SetEv;
- Result := NOERROR;
- end;
- {$IFDEF DEBUG}
- // Dump the current renderer state to the debug terminal. The hardest part of
- // the renderer is the window where we unlock everything to wait for a clock
- // to signal it is time to draw or for the application to cancel everything
- // by stopping the filter. If we get things wrong we can leave the thread in
- // WaitForRenderTime with no way for it to ever get out and we will deadlock
- procedure TBCBaseRenderer.DisplayRendererState;
- var
- bSignalled, bFlushing: Boolean;
- CurrentTime, StartTime, EndTime, Offset, Wait: TReferenceTime;
- function RT_in_Millisecs(rt: TReferenceTime): Int64;
- begin
- Result := rt div 10000;
- end;
- begin
- DbgLog(Self, 'Timed out in WaitForRenderTime');
- // No way should this be signalled at this point
- bSignalled := FThreadSignal.Check;
- DbgLog(Self, Format('Signal sanity check %d', [Byte(bSignalled)]));
- // Now output the current renderer state variables
- DbgLog(Self, Format('Filter state %d', [Ord(FState)]));
- DbgLog(Self, Format('Abort flag %d', [Byte(FAbort)]));
- DbgLog(Self, Format('Streaming flag %d', [Byte(FIsStreaming)]));
- DbgLog(Self, Format('Clock advise link %d', [FAdvisedCookie]));
- // DbgLog(Self, Format('Current media sample %x', [FMediaSample]));
- DbgLog(Self, Format('EOS signalled %d', [Byte(FIsEOS)]));
- DbgLog(Self, Format('EOS delivered %d', [Byte(FIsEOSDelivered)]));
- DbgLog(Self, Format('Repaint status %d', [Byte(FRepaintStatus)]));
- // Output the delayed end of stream timer information
- DbgLog(Self, Format('End of stream timer %x', [FEndOfStreamTimer]));
- // ??? convert reftime to str
- // DbgLog((LOG_TIMING, 1, TEXT("Deliver time %s"),CDisp((LONGLONG)FSignalTime)));
- DbgLog(Self, Format('Deliver time %d', [FSignalTime]));
- // Should never timeout during a flushing state
- bFlushing := FInputPin.IsFlushing;
- DbgLog(Self, Format('Flushing sanity check %d', [Byte(bFlushing)]));
- // Display the time we were told to start at
- // ??? DbgLog((LOG_TIMING, 1, TEXT("Last run time %s"),CDisp((LONGLONG)m_tStart.m_time)));
- DbgLog(Self, Format('Last run time %d', [FStart]));
- // Have we got a reference clock
- if (FClock = nil) then
- Exit;
- // Get the current time from the wall clock
- FClock.GetTime(int64(CurrentTime));
- Offset := CurrentTime - FStart;
- // Display the current time from the clock
- DbgLog(Self, Format('Clock time %d', [CurrentTime]));
- DbgLog(Self, Format('Time difference %d ms', [RT_in_Millisecs(Offset)]));
- // Do we have a sample ready to render
- if (FMediaSample = nil) then
- Exit;
- FMediaSample.GetTime(StartTime, EndTime);
- DbgLog(Self, Format('Next sample stream times (Start %d End %d ms)',
- [RT_in_Millisecs(StartTime), RT_in_Millisecs(EndTime)]));
- // Calculate how long it is until it is due for rendering
- Wait := (FStart + StartTime) - CurrentTime;
- DbgLog(Self, Format('Wait required %d ms', [RT_in_Millisecs(Wait)]));
- end;
- {$ENDIF}
- // Wait until the clock sets the timer event or we're otherwise signalled. We
- // set an arbitrary timeout for this wait and if it fires then we display the
- // current renderer state on the debugger. It will often fire if the filter's
- // left paused in an application however it may also fire during stress tests
- // if the synchronisation with application seeks and state changes is faulty
- const
- RENDER_TIMEOUT = 10000;
- function TBCBaseRenderer.WaitForRenderTime: HResult;
- var
- WaitObjects: array[0..1] of THandle;
- begin
- WaitObjects[0] := FThreadSignal.Handle;
- WaitObjects[1] := FRenderEvent.Handle;
- DWord(Result) := WAIT_TIMEOUT;
- // Wait for either the time to arrive or for us to be stopped
- OnWaitStart;
- while (Result = WAIT_TIMEOUT) do
- begin
- Result := WaitForMultipleObjects(2, @WaitObjects, False, RENDER_TIMEOUT);
- {$IFDEF DEBUG}
- if (Result = WAIT_TIMEOUT) then
- DisplayRendererState;
- {$ENDIF}
- end;
- OnWaitEnd;
- // We may have been awoken without the timer firing
- if (Result = WAIT_OBJECT_0) then
- begin
- Result := VFW_E_STATE_CHANGED;
- Exit;
- end;
- SignalTimerFired;
- Result := NOERROR;
- end;
- // Poll waiting for Receive to complete. This really matters when
- // Receive may set the palette and cause window messages
- // The problem is that if we don't really wait for a renderer to
- // stop processing we can deadlock waiting for a transform which
- // is calling the renderer's Receive() method because the transform's
- // Stop method doesn't know to process window messages to unblock
- // the renderer's Receive processing
- procedure TBCBaseRenderer.WaitForReceiveToComplete;
- var
- msg: TMsg;
- begin
- repeat
- if Not FInReceive then
- Break;
- // Receive all interthread sendmessages
- PeekMessage(msg, 0, WM_NULL, WM_NULL, PM_NOREMOVE);
- Sleep(1);
- until False;
- // If the wakebit for QS_POSTMESSAGE is set, the PeekMessage call
- // above just cleared the changebit which will cause some messaging
- // calls to block (waitMessage, MsgWaitFor...) now.
- // Post a dummy message to set the QS_POSTMESSAGE bit again
-
- if (HIWORD(GetQueueStatus(QS_POSTMESSAGE)) and QS_POSTMESSAGE) <> 0 then
- // Send dummy message
- PostThreadMessage(GetCurrentThreadId, WM_NULL, 0, 0);
- end;
- // A filter can have four discrete states, namely Stopped, Running, Paused,
- // Intermediate. We are in an intermediate state if we are currently trying
- // to pause but haven't yet got the first sample (or if we have been flushed
- // in paused state and therefore still have to wait for a sample to arrive)
- // This class contains an event called FCompleteEvent which is signalled when
- // the current state is completed and is not signalled when we are waiting to
- // complete the last state transition. As mentioned above the only time we
- // use this at the moment is when we wait for a media sample in paused state
- // If while we are waiting we receive an end of stream notification from the
- // source filter then we know no data is imminent so we can reset the event
- // This means that when we transition to paused the source filter must call
- // end of stream on us or send us an image otherwise we'll hang indefinately
- // Simple internal way of getting the real state
- // !!! make property here
- function TBCBaseRenderer.GetRealState: TFilterState;
- begin
- Result := FState;
- end;
- // Waits for the HANDLE hObject. While waiting messages sent
- // to windows on our thread by SendMessage will be processed.
- // Using this function to do waits and mutual exclusion
- // avoids some deadlocks in objects with windows.
- // Return codes are the same as for WaitForSingleObject
- function WaitDispatchingMessages(Object_: THandle; Wait: DWord;
- Wnd: HWnd = 0; Msg: Cardinal = 0; Event: THandle = 0): DWord;
- // milenko start (replaced with global variables)
- //const
- //{$IFOPT J-}
- //{$DEFINE ResetJ}
- //{$J+}
- //{$ENDIF}
- // MsgId: Cardinal = 0;
- //{$IFDEF ResetJ}
- //{$J-}
- //{$UNDEF ResetJ}
- //{$ENDIF}
- // milenko end
- var
- Peeked: Boolean;
- Res, Start, ThreadPriority: DWord;
- Objects: array[0..1] of THandle;
- Count, TimeOut, WakeMask, Now_, Diff: DWord;
- Msg_: TMsg;
- begin
- Peeked := False;
- MsgId := 0;
- Start := 0;
- ThreadPriority := THREAD_PRIORITY_NORMAL;
- Objects[0] := Object_;
- Objects[1] := Event;
- if (Wait <> INFINITE) and (Wait <> 0) then
- Start := GetTickCount;
- repeat
- if (Event <> 0) then
- Count := 2
- else
- Count := 1;
- // Minimize the chance of actually dispatching any messages
- // by seeing if we can lock immediately.
- Res := WaitForMultipleObjects(Count, @Objects, False, 0);
- if (Res < WAIT_OBJECT_0 + Count) then
- Break;
- TimeOut := Wait;
- if (TimeOut > 10) then
- TimeOut := 10;
- if (Wnd = 0) then
- WakeMask := QS_SENDMESSAGE
- else
- WakeMask := QS_SENDMESSAGE + QS_POSTMESSAGE;
- Res := MsgWaitForMultipleObjects(Count, Objects, False,
- TimeOut, WakeMask);
- if (Res = WAIT_OBJECT_0 + Count) or
- ((Res = WAIT_TIMEOUT) and (TimeOut <> Wait)) then
- begin
- if (Wnd <> 0) then
- while PeekMessage(Msg_, Wnd, Msg, Msg, PM_REMOVE) do
- DispatchMessage(Msg_);
- // Do this anyway - the previous peek doesn't flush out the
- // messages
- PeekMessage(Msg_, 0, 0, 0, PM_NOREMOVE);
- if (Wait <> INFINITE) and (Wait <> 0) then
- begin
- Now_ := GetTickCount();
- // Working with differences handles wrap-around
- Diff := Now_ - Start;
- if (Diff > Wait) then
- Wait := 0
- else
- Dec(Wait, Diff);
- Start := Now_;
- end;
- if not (Peeked) then
- begin
- // Raise our priority to prevent our message queue
- // building up
- ThreadPriority := GetThreadPriority(GetCurrentThread);
- if (ThreadPriority < THREAD_PRIORITY_HIGHEST) then
- begin
- // ??? raising priority requires one more routine....
- SetThreadPriority(GetCurrentThread, THREAD_PRIORITY_HIGHEST);
- end;
- Peeked := True;
- end;
- end
- else
- Break;
- until False;
- if (Peeked) then
- begin
- // ??? setting priority requires one more routine....
- SetThreadPriority(GetCurrentThread, ThreadPriority);
- // milenko start (important!)
- // if (HIWORD(GetQueueStatus(QS_POSTMESSAGE)) and QS_POSTMESSAGE) = 0 then
- if (HIWORD(GetQueueStatus(QS_POSTMESSAGE)) and QS_POSTMESSAGE) > 0 then
- // milenko end
- begin
- if (MsgId = 0) then
- MsgId := RegisterWindowMessage('AMUnblock')
- else
- // Remove old ones
- while (PeekMessage(Msg_, (Wnd) - 1, MsgId, MsgId, PM_REMOVE)) do
- // milenko start (this is a loop without any further function.
- // it does not call PostThreadMEssage while looping!)
- begin
- end;
- // milenko end
- PostThreadMessage(GetCurrentThreadId, MsgId, 0, 0);
- end;
- end;
- Result := Res;
- end;
- // The renderer doesn't complete the full transition to paused states until
- // it has got one media sample to render. If you ask it for its state while
- // it's waiting it will return the state along with VFW_S_STATE_INTERMEDIATE
- function TBCBaseRenderer.GetState(MSecs: DWord; out State: TFilterState):
- HResult;
- begin
- if (WaitDispatchingMessages(FCompleteEvent.Handle, MSecs) = WAIT_TIMEOUT) then
- Result := VFW_S_STATE_INTERMEDIATE
- else
- Result := NOERROR;
- State := FState;
- end;
- // If we're pausing and we have no samples we don't complete the transition
- // to State_Paused and we return S_FALSE. However if the FAborting flag has
- // been set then all samples are rejected so there is no point waiting for
- // one. If we do have a sample then return NOERROR. We will only ever return
- // VFW_S_STATE_INTERMEDIATE from GetState after being paused with no sample
- // (calling GetState after either being stopped or Run will NOT return this)
- function TBCBaseRenderer.CompleteStateChange(OldState: TFilterState): HResult;
- begin
- // Allow us to be paused when disconnected
- if not (FInputPin.IsConnected) or
- // Have we run off the end of stream
- IsEndOfStream or
- // Make sure we get fresh data after being stopped
- (HaveCurrentSample and (OldState <> State_Stopped)) then
- begin
- Ready;
- Result := S_OK;
- Exit;
- end;
- NotReady;
- Result := S_False;
- end;
- procedure TBCBaseRenderer.SetAbortSignal(Abort_: Boolean);
- begin
- FAbort := Abort_;
- end;
- procedure TBCBaseRenderer.OnReceiveFirstSample(MediaSample: IMediaSample);
- begin
- end;
- procedure TBCBaseRenderer.Ready;
- begin
- FCompleteEvent.SetEv
- end;
- procedure TBCBaseRenderer.NotReady;
- begin
- FCompleteEvent.Reset
- end;
- function TBCBaseRenderer.CheckReady: Boolean;
- begin
- Result := FCompleteEvent.Check
- end;
- // When we stop the filter the things we do are:-
- // Decommit the allocator being used in the connection
- // Release the source filter if it's waiting in Receive
- // Cancel any advise link we set up with the clock
- // Any end of stream signalled is now obsolete so reset
- // Allow us to be stopped when we are not connected
- function TBCBaseRenderer.Stop: HResult;
- begin
- FInterfaceLock.Lock;
- try
- // Make sure there really is a state change
- if (FState = State_Stopped) then
- begin
- Result := NOERROR;
- Exit;
- end;
- // Is our input pin connected
- if not (FInputPin.IsConnected) then
- begin
- {$IFDEF DEBUG}
- DbgLog(Self, 'Input pin is not connected');
- {$ENDIF}
- FState := State_Stopped;
- Result := NOERROR;
- Exit;
- end;
- inherited Stop;
- // If we are going into a stopped state then we must decommit whatever
- // allocator we are using it so that any source filter waiting in the
- // GetBuffer can be released and unlock themselves for a state change
- if Assigned(FInputPin.FAllocator) then
- FInputPin.FAllocator.Decommit;
- // Cancel any scheduled rendering
- SetRepaintStatus(True);
- StopStreaming;
- SourceThreadCanWait(False);
- ResetEndOfStream;
- CancelNotification;
- // There should be no outstanding clock advise
- Assert(CancelNotification = S_FALSE);
- Assert(WAIT_TIMEOUT = WaitForSingleObject(FRenderEvent.Handle, 0));
- Assert(FEndOfStreamTimer = 0);
- Ready;
- WaitForReceiveToComplete;
- FAbort := False;
- Result := NOERROR;
- finally
- FInterfaceLock.UnLock;
- end;
- end;
- // When we pause the filter the things we do are:-
- // Commit the allocator being used in the connection
- // Allow a source filter thread to wait in Receive
- // Cancel any clock advise link (we may be running)
- // Possibly complete the state change if we have data
- // Allow us to be paused when we are not connected
- function TBCBaseRenderer.Pause: HResult;
- var
- OldState: TFilterState;
- hr: HResult;
- begin
- FInterfaceLock.Lock;
- try
- OldState := FState;
- Assert(not FInputPin.IsFlushing);
- // Make sure there really is a state change
- if (FState = State_Paused) then
- begin
- Result := CompleteStateChange(State_Paused);
- Exit;
- end;
- // Has our input pin been connected
- if Not FInputPin.IsConnected then
- begin
- {$IFDEF DEBUG}
- DbgLog(Self, 'Input pin is not connected');
- {$ENDIF}
- FState := State_Paused;
- Result := CompleteStateChange(State_Paused);
- Exit;
- end;
- // Pause the base filter class
- hr := inherited Pause;
- if Failed(hr) then
- begin
- {$IFDEF DEBUG}
- DbgLog(Self, 'Pause failed');
- {$ENDIF}
- Result := hr;
- Exit;
- end;
- // Enable EC_REPAINT events again
- SetRepaintStatus(True);
- StopStreaming;
- SourceThreadCanWait(True);
- CancelNotification;
- ResetEndOfStreamTimer;
- // If we are going into a paused state then we must commit whatever
- // allocator we are using it so that any source filter can call the
- // GetBuffer and expect to get a buffer without returning an error
- if Assigned(FInputPin.FAllocator) then
- FInputPin.FAllocator.Commit;
- // There should be no outstanding advise
- Assert(CancelNotification = S_FALSE);
- Assert(WAIT_TIMEOUT = WaitForSingleObject(FRenderEvent.Handle, 0));
- Assert(FEndOfStreamTimer = 0);
- Assert(not FInputPin.IsFlushing);
- // When we come out of a stopped state we must clear any image we were
- // holding onto for frame refreshing. Since renderers see state changes
- // first we can reset ourselves ready to accept the source thread data
- // Paused or running after being stopped causes the current position to
- // be reset so we're not interested in passing end of stream signals
- if (OldState = State_Stopped) then
- begin
- FAbort := False;
- ClearPendingSample;
- end;
- Result := CompleteStateChange(OldState);
- finally
- FInterfaceLock.Unlock;
- end;
- end;
- // When we run the filter the things we do are:-
- // Commit the allocator being used in the connection
- // Allow a source filter thread to wait in Receive
- // Signal the render event just to get us going
- // Start the base class by calling StartStreaming
- // Allow us to be run when we are not connected
- // Signal EC_COMPLETE if we are not connected
- function TBCBaseRenderer.Run(StartTime: TReferenceTime): HResult;
- var
- OldState: TFilterState;
- hr: HResult;
- // milenko start
- Filter: IBaseFilter;
- // milenko end
- begin
- FInterfaceLock.Lock;
- try
- OldState := FState;
- // Make sure there really is a state change
- if (FState = State_Running) then
- begin
- Result := NOERROR;
- Exit;
- end;
- // Send EC_COMPLETE if we're not connected
- if not FInputPin.IsConnected then
- begin
- // milenko start (Delphi 5 compatibility)
- QueryInterface(IID_IBaseFilter,Filter);
- NotifyEvent(EC_COMPLETE, S_OK, Integer(Filter));
- Filter := nil;
- // milenko end
- FState := State_Running;
- Result := NOERROR;
- Exit;
- end;
- Ready;
- // Pause the base filter class
- hr := inherited Run(StartTime);
- if Failed(hr) then
- begin
- {$IFDEF DEBUG}
- DbgLog(Self, 'Run failed');
- {$ENDIF}
- Result := hr;
- Exit;
- end;
- // Allow the source thread to wait
- Assert(not FInputPin.IsFlushing);
- SourceThreadCanWait(True);
- SetRepaintStatus(False);
- // There should be no outstanding advise
- Assert(CancelNotification = S_FALSE);
- Assert(WAIT_TIMEOUT = WaitForSingleObject(FRenderEvent.Handle, 0));
- Assert(FEndOfStreamTimer = 0);
- Assert(not FInputPin.IsFlushing);
- // If we are going into a running state then we must commit whatever
- // allocator we are using it so that any source filter can call the
- // GetBuffer and expect to get a buffer without returning an error
- if Assigned(FInputPin.FAllocator) then
- FInputPin.FAllocator.Commit;
- // When we come out of a stopped state we must clear any image we were
- // holding onto for frame refreshing. Since renderers see state changes
- // first we can reset ourselves ready to accept the source thread data
- // Paused or running after being stopped causes the current position to
- // be reset so we're not interested in passing end of stream signals
- if (OldState = State_Stopped) then
- begin
- FAbort := False;
- ClearPendingSample;
- end;
- Result := StartStreaming;
- finally
- FInterfaceLock.Unlock;
- end;
- end;
- // Return the number of input pins we support
- function TBCBaseRenderer.GetPinCount: Integer;
- begin
- Result := 1;
- end;
- // We only support one input pin and it is numbered zero
- function TBCBaseRenderer.GetPin(n: integer): TBCBasePin;
- var
- hr: HResult;
- begin
- FObjectCreationLock.Lock;
- try
- // Should only ever be called with zero
- Assert(n = 0);
- if (n <> 0) then
- begin
- Result := nil;
- Exit;
- end;
- // Create the input pin if not already done so
- if (FInputPin = nil) then
- begin
- // hr must be initialized to NOERROR because
- // CRendererInputPin's constructor only changes
- // hr's value if an error occurs.
- hr := NOERROR;
- FInputPin := TBCRendererInputPin.Create(Self, hr, 'In');
- if (FInputPin = nil) then
- begin
- Result := nil;
- Exit;
- end;
- if Failed(hr) then
- begin
- FreeAndNil(FInputPin);
- Result := nil;
- Exit;
- end;
- end;
- Result := FInputPin;
- finally
- FObjectCreationLock.UnLock;
- end;
- end;
- function DumbItDownFor95(const S1, S2: WideString; CmpFlags: Integer): Integer;
- var
- a1, a2: AnsiString;
- begin
- a1 := s1;
- a2 := s2;
- Result := CompareStringA(LOCALE_USER_DEFAULT, CmpFlags, PChar(a1), Length(a1),
- PChar(a2), Length(a2)) - 2;
- end;
- function WideCompareText(const S1, S2: WideString): Integer;
- begin
- SetLastError(0);
- Result := CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE, PWideChar(S1),
- Length(S1), PWideChar(S2), Length(S2)) - 2;
- case GetLastError of
- 0: ;
- ERROR_CALL_NOT_IMPLEMENTED: Result := DumbItDownFor95(S1, S2, NORM_IGNORECASE);
- end;
- end;
- // If "In" then return the IPin for our input pin, otherwise NULL and error
- function TBCBaseRenderer.FindPin(id: PWideChar; out Pin: IPin): HResult;
- begin
- // Milenko start
- if (@Pin = nil) then
- begin
- Result := E_POINTER;
- Exit;
- end;
- // Milenko end
- // milenko start (delphi 5 doesn't know WideCompareText)
- if WideCompareText(id, 'In') = 0 then
- // milenko end
- begin
- Pin := GetPin(0);
- Assert(Pin <> nil);
- // ??? Pin.AddRef;
- Result := NOERROR;
- end
- else
- begin
- Pin := nil;
- Result := VFW_E_NOT_FOUND;
- end;
- end;
- // Called when the input pin receives an EndOfStream notification. If we have
- // not got a sample, then notify EC_COMPLETE now. If we have samples, then set
- // m_bEOS and check for this on completing samples. If we're waiting to pause
- // then complete the transition to paused state by setting the state event
- function TBCBaseRenderer.EndOfStream: HResult;
- begin
- // Ignore these calls if we are stopped
- if (FState = State_Stopped) then
- begin
- Result := NOERROR;
- Exit;
- end;
- // If we have a sample then wait for it to be rendered
- FIsEOS := True;
- if Assigned(FMediaSample) then
- begin
- Result := NOERROR;
- Exit;
- end;
- // If we are waiting for pause then we are now ready since we cannot now
- // carry on waiting for a sample to arrive since we are being told there
- // won't be any. This sets an event that the GetState function picks up
- Ready;
- // Only signal completion now if we are running otherwise queue it until
- // we do run in StartStreaming. This is used when we seek because a seek
- // causes a pause where early notification of completion is misleading
- if FIsStreaming then
- SendEndOfStream;
- Result := NOERROR;
- end;
- // When we are told to flush we should release the source thread
- function TBCBaseRenderer.BeginFlush: HResult;
- begin
- // If paused then report state intermediate until we get some data
- if (FState = State_Paused) then
- NotReady;
- SourceThreadCanWait(False);
- CancelNotification;
- ClearPendingSample;
- // Wait for Receive to complete
- WaitForReceiveToComplete;
- Result := NOERROR;
- end;
- // After flushing the source thread can wait in Receive again
- function TBCBaseRenderer.EndFlush: HResult;
- begin
- // Reset the current sample media time
- if Assigned(FPosition) then
- FPosition.ResetMediaTime;
- // There should be no outstanding advise
- Assert(CancelNotification = S_FALSE);
- SourceThreadCanWait(True);
- Result := NOERROR;
- end;
- // We can now send EC_REPAINTs if so required
- function TBCBaseRenderer.CompleteConnect(ReceivePin: IPin): HResult;
- begin
- // The caller should always hold the interface lock because
- // the function uses CBaseFilter::m_State.
- {$IFDEF DEBUG}
- Assert(FInterfaceLock.CritCheckIn);
- {$ENDIF}
- FAbort := False;
- if (State_Running = GetRealState) then
- begin
- Result := StartStreaming;
- if Failed(Result) then
- Exit;
- SetRepaintStatus(False);
- end
- else
- SetRepaintStatus(True);
- Result := NOERROR;
- end;
- // Called when we go paused or running
- function TBCBaseRenderer.Active: HResult;
- begin
- Result := NOERROR;
- end;
- // Called when we go into a stopped state
- function TBCBaseRenderer.Inactive: HResult;
- begin
- if Assigned(FPosition) then
- FPosition.ResetMediaTime;
- // People who derive from this may want to override this behaviour
- // to keep hold of the sample in some circumstances
- ClearPendingSample;
- Result := NOERROR;
- end;
- // Tell derived classes about the media type agreed
- function TBCBaseRenderer.SetMediaType(MediaType: PAMMediaType): HResult;
- begin
- Result := NOERROR;
- end;
- // When we break the input pin connection we should reset the EOS flags. When
- // we are asked for either IMediaPosition or IMediaSeeking we will create a
- // CPosPassThru object to handles media time pass through. When we're handed
- // samples we store (by calling CPosPassThru::RegisterMediaTime) their media
- // times so we can then return a real current position of data being rendered
- function TBCBaseRenderer.BreakConnect: HResult;
- begin
- // Do we have a quality management sink
- if Assigned(FQSink) then
- FQSink := nil;
- // Check we have a valid connection
- if not FInputPin.IsConnected then
- begin
- Result := S_FALSE;
- Exit;
- end;
- // Check we are stopped before disconnecting
- if (FState <> State_Stopped) and (not FInputPin.CanReconnectWhenActive) then
- begin
- Result := VFW_E_NOT_STOPPED;
- Exit;
- end;
- SetRepaintStatus(False);
- ResetEndOfStream;
- ClearPendingSample;
- FAbort := False;
- if (State_Running = FState) then
- StopStreaming;
- Result := NOERROR;
- end;
- // Retrieves the sample times for this samples (note the sample times are
- // passed in by reference not value). We return S_FALSE to say schedule this
- // sample according to the times on the sample. We also return S_OK in
- // which case the object should simply render the sample data immediately
- function TBCBaseRenderer.GetSampleTimes(MediaSample: IMediaSample;
- out StartTime: TReferenceTime; out EndTime: TReferenceTime): HResult;
- begin
- Assert(FAdvisedCookie = 0);
- Assert(Assigned(MediaSample));
- // If the stop time for this sample is before or the same as start time,
- // then just ignore it (release it) and schedule the next one in line
- // Source filters should always fill in the start and end times properly!
- if Succeeded(MediaSample.GetTime(StartTime, EndTime)) then
- begin
- if (EndTime < StartTime) then
- begin
- Result := VFW_E_START_TIME_AFTER_END;
- Exit;
- end;
- end
- else
- begin
- // no time set in the sample... draw it now?
- Result := S_OK;
- Exit;
- end;
- // Can't synchronise without a clock so we return S_OK which tells the
- // caller that the sample should be rendered immediately without going
- // through the overhead of setting a timer advise link with the clock
- if (FClock = nil) then
- Result := S_OK
- else
- Result := ShouldDrawSampleNow(MediaSample, StartTime, EndTime);
- end;
- // By default all samples are drawn according to their time stamps so we
- // return S_FALSE. Returning S_OK means draw immediately, this is used
- // by the derived video renderer class in its quality management.
- function TBCBaseRenderer.ShouldDrawSampleNow(MediaSample: IMediaSample;
- StartTime: TReferenceTime; out EndTime: TReferenceTime): HResult;
- begin
- Result := S_FALSE;
- end;
- // We must always reset the current advise time to zero after a timer fires
- // because there are several possible ways which lead us not to do any more
- // scheduling such as the pending image being cleared after state changes
- procedure TBCBaseRenderer.SignalTimerFired;
- begin
- FAdvisedCookie := 0;
- end;
- // Cancel any notification currently scheduled. This is called by the owning
- // window object when it is told to stop streaming. If there is no timer link
- // outstanding then calling this is benign otherwise we go ahead and cancel
- // We must always reset the render event as the quality management code can
- // signal immediate rendering by setting the event without setting an advise
- // link. If we're subsequently stopped and run the first attempt to setup an
- // advise link with the reference clock will find the event still signalled
- function TBCBaseRenderer.CancelNotification: HResult;
- var
- dwAdvisedCookie: DWord;
- begin
- Assert((FAdvisedCookie = 0) or Assigned(FClock));
- dwAdvisedCookie := FAdvisedCookie;
- // Have we a live advise link
- if (FAdvisedCookie <> 0) then
- begin
- FClock.Unadvise(FAdvisedCookie);
- SignalTimerFired;
- Assert(FAdvisedCookie = 0);
- end;
- // Clear the event and return our status
- FRenderEvent.Reset;
- if (dwAdvisedCookie <> 0) then
- Result := S_OK
- else
- Result := S_FALSE;
- end;
- // Responsible for setting up one shot advise links with the clock
- // Return FALSE if the sample is to be dropped (not drawn at all)
- // Return TRUE if the sample is to be drawn and in this case also
- // arrange for m_RenderEvent to be set at the appropriate time
- function TBCBaseRenderer.ScheduleSample(MediaSample: IMediaSample): Boolean;
- var
- StartSample, EndSample: TReferenceTime;
- hr: HResult;
- begin
- // Is someone pulling our leg
- if (MediaSample = nil) then
- begin
- Result := False;
- Exit;
- end;
- // Get the next sample due up for rendering. If there aren't any ready
- // then GetNextSampleTimes returns an error. If there is one to be done
- // then it succeeds and yields the sample times. If it is due now then
- // it returns S_OK other if it's to be done when due it returns S_FALSE
- hr := GetSampleTimes(MediaSample, StartSample, EndSample);
- if Failed(hr) then
- begin
- Result := False;
- Exit;
- end;
- // If we don't have a reference clock then we cannot set up the advise
- // time so we simply set the event indicating an image to render. This
- // will cause us to run flat out without any timing or synchronisation
- if (hr = S_OK) then
- begin
- // ???Assert(SetEvent(FRenderEvent.Handle));
- FRenderEvent.SetEv;
- Result := True;
- Exit;
- end;
- Assert(FAdvisedCookie = 0);
- Assert(Assigned(FClock));
- Assert(Wait_Timeout = WaitForSingleObject(FRenderEvent.Handle, 0));
- // We do have a valid reference clock interface so we can ask it to
- // set an event when the image comes due for rendering. We pass in
- // the reference time we were told to start at and also the current
- // stream time which is the offset from the start reference time
- hr := FClock.AdviseTime(
- FStart, // Start run time
- StartSample, // Stream time
- FRenderEvent.Handle, // Render notification
- FAdvisedCookie); // Advise cookie
- if Succeeded(hr) then
- begin
- Result := True;
- Exit;
- end;
- // We could not schedule the next sample for rendering despite the fact
- // we have a valid sample here. This is a fair indication that either
- // the system clock is wrong or the time stamp for the sample is duff
- Assert(FAdvisedCookie = 0);
- Result := False;
- end;
- // This is called when a sample comes due for rendering. We pass the sample
- // on to the derived class. After rendering we will initialise the timer for
- // the next sample, NOTE signal that the last one fired first, if we don't
- // do this it thinks there is still one outstanding that hasn't completed
- function TBCBaseRenderer.Render(MediaSample: IMediaSample): HResult;
- begin
- // If the media sample is NULL then we will have been notified by the
- // clock that another sample is ready but in the mean time someone has
- // stopped us streaming which causes the next sample to be released
- if (MediaSample = nil) then
- begin
- Result := S_FALSE;
- Exit;
- end;
- // If we have stopped streaming then don't render any more samples, the
- // thread that got in and locked us and then reset this flag does not
- // clear the pending sample as we can use it to refresh any output device
- if Not FIsStreaming then
- begin
- Result := S_FALSE;
- Exit;
- end;
- // Time how long the rendering takes
- OnRenderStart(MediaSample);
- DoRenderSample(MediaSample);
- OnRenderEnd(MediaSample);
- Result := NOERROR;
- end;
- // Checks if there is a sample waiting at the renderer
- function TBCBaseRenderer.HaveCurrentSample: Boolean;
- begin
- FRendererLock.Lock;
- try
- Result := (FMediaSample <> nil);
- finally
- FRendererLock.UnLock;
- end;
- end;
- // Returns the current sample waiting at the video renderer. We AddRef the
- // sample before returning so that should it come due for rendering the
- // person who called this method will hold the remaining reference count
- // that will stop the sample being added back onto the allocator free list
- function TBCBaseRenderer.GetCurrentSample: IMediaSample;
- begin
- FRendererLock.Lock;
- try
- (* ???
- if (m_pMediaSample) {
- m_pMediaSample->AddRef();
- *)
- Result := FMediaSample;
- finally
- FRendererLock.Unlock;
- end;
- end;
- // Called when the source delivers us a sample. We go through a few checks to
- // make sure the sample can be rendered. If we are running (streaming) then we
- // have the sample scheduled with the reference clock, if we are not streaming
- // then we have received an sample in paused mode so we can complete any state
- // transition. On leaving this function everything will be unlocked so an app
- // thread may get in and change our state to stopped (for example) in which
- // case it will also signal the thread event so that our wait call is stopped
- function TBCBaseRenderer.PrepareReceive(MediaSample: IMediaSample): HResult;
- var
- hr: HResult;
- begin
- FInterfaceLock.Lock;
- try
- FInReceive := True;
- // Check our flushing and filter state
- // This function must hold the interface lock because it calls
- // CBaseInputPin::Receive() and CBaseInputPin::Receive() uses
- // CBasePin::m_bRunTimeError.
- // ??? HRESULT hr = m_pInputPin->CBaseInputPin::Receive(MediaSample);
- hr := FInputPin.InheritedReceive(MediaSample);
- if (hr <> NOERROR) then
- begin
- FInReceive := False;
- Result := E_FAIL;
- Exit;
- end;
- // Has the type changed on a media sample. We do all rendering
- // synchronously on the source thread, which has a side effect
- // that only one buffer is ever outstanding. Therefore when we
- // have Receive called we can go ahead and change the format
- // Since the format change can cause a SendMessage we just don't
- // lock
- if Assigned(FInputPin.SampleProps.pMediaType) then
- begin
- hr := FInputPin.SetMediaType(FInputPin.FSampleProps.pMediaType);
- if Failed(hr) then
- begin
- Result := hr;
- FInReceive := False;
- Exit;
- end;
- end;
- FRendererLock.Lock;
- try
- Assert(IsActive);
- Assert(not FInputPin.IsFlushing);
- Assert(FInputPin.IsConnected);
- Assert(FMediaSample = nil);
- // Return an error if we already have a sample waiting for rendering
- // source pins must serialise the Receive calls - we also check that
- // no data is being sent after the source signalled an end of stream
- if (Assigned(FMediaSample) or FIsEOS or FAbort) then
- begin
- Ready;
- FInReceive := False;
- Result := E_UNEXPECTED;
- Exit;
- end;
- // Store the media times from this sample
- if Assigned(FPosition) then
- FPosition.RegisterMediaTime(MediaSample);
- // Schedule the next sample if we are streaming
- if (FIsStreaming and (not ScheduleSample(MediaSample))) then
- begin
- Assert(WAIT_TIMEOUT = WaitForSingleObject(FRenderEvent.Handle, 0));
- Assert(CancelNotification = S_FALSE);
- FInReceive := False;
- Result := VFW_E_SAMPLE_REJECTED;
- Exit;
- end;
- // Store the sample end time for EC_COMPLETE handling
- FSignalTime := FInputPin.FSampleProps.tStop;
- // BEWARE we sometimes keep the sample even after returning the thread to
- // the source filter such as when we go into a stopped state (we keep it
- // to refresh the device with) so we must AddRef it to keep it safely. If
- // we start flushing the source thread is released and any sample waiting
- // will be released otherwise GetBuffer may never return (see BeginFlush)
- FMediaSample := MediaSample;
- //??? m_pMediaSample->AddRef();
- if not FIsStreaming then
- SetRepaintStatus(True);
- Result := NOERROR;
- finally
- FRendererLock.Unlock;
- end;
- finally
- FInterfaceLock.UnLock;
- end;
- end;
- // Called by the source filter when we have a sample to render. Under normal
- // circumstances we set an advise link with the clock, wait for the time to
- // arrive and then render the data using the PURE virtual DoRenderSample that
- // the derived class will have overriden. After rendering the sample we may
- // also signal EOS if it was the last one sent before EndOfStream was called
- function TBCBaseRenderer.Receive(MediaSample: IMediaSample): HResult;
- begin
- Assert(Assigned(MediaSample));
- // It may return VFW_E_SAMPLE_REJECTED code to say don't bother
- Result := PrepareReceive(MediaSample);
- Assert(FInReceive = Succeeded(Result));
- if Failed(Result) then
- begin
- if (Result = VFW_E_SAMPLE_REJECTED) then
- Result := NOERROR;
- Exit;
- end;
- // We realize the palette in "PrepareRender()" so we have to give away the
- // filter lock here.
- if (FState = State_Paused) then
- begin
- PrepareRender;
- // no need to use InterlockedExchange
- FInReceive := False;
- // We must hold both these locks
- FInterfaceLock.Lock;
- try
- if (FState = State_Stopped) then
- begin
- Result := NOERROR;
- Exit;
- end;
- FInReceive := True;
- FRendererLock.Lock;
- try
- OnReceiveFirstSample(MediaSample);
- finally
- FRendererLock.UnLock;
- end;
- finally
- FInterfaceLock.UnLock;
- end;
- Ready;
- end;
- // Having set an advise link with the clock we sit and wait. We may be
- // awoken by the clock firing or by a state change. The rendering call
- // will lock the critical section and check we can still render the data
- Result := WaitForRenderTime;
- if Failed(Result) then
- begin
- FInReceive := False;
- Result := NOERROR;
- Exit;
- end;
- PrepareRender;
- // Set this here and poll it until we work out the locking correctly
- // It can't be right that the streaming stuff grabs the interface
- // lock - after all we want to be able to wait for this stuff
- // to complete
- FInReceive := False;
- // We must hold both these locks
- FInterfaceLock.Lock;
- try
- // since we gave away the filter wide lock, the sate of the filter could
- // have chnaged to Stopped
- if (FState = State_Stopped) then
- begin
- Result := NOERROR;
- Exit;
- end;
- FRendererLock.Lock;
- try
- // Deal with this sample
- Render(FMediaSample);
- ClearPendingSample;
- // milenko start (why commented before?)
- SendEndOfStream;
- // milenko end
- CancelNotification;
- Result := NOERROR;
- finally
- FRendererLock.UnLock;
- end;
- finally
- FInterfaceLock.UnLock;
- end;
- end;
- // This is called when we stop or are inactivated to clear the pending sample
- // We release the media sample interface so that they can be allocated to the
- // source filter again, unless of course we are changing state to inactive in
- // which case GetBuffer will return an error. We must also reset the current
- // media sample to NULL so that we know we do not currently have an image
- function TBCBaseRenderer.ClearPendingSample: HResult;
- begin
- FRendererLock.Lock;
- try
- if Assigned(FMediaSample) then
- FMediaSample := nil;
- Result := NOERROR;
- finally
- FRendererLock.Unlock;
- end;
- end;
- // Used to signal end of stream according to the sample end time
- // Milenko start (use this callback outside of the class and with stdcall;)
- procedure EndOfStreamTimer(uID, uMsg: UINT;
- dwUser, dw1, dw2: DWord); stdcall;
- var
- Renderer: TBCBaseRenderer;
- begin
- Renderer := TBCBaseRenderer(dwUser);
- {$IFDEF DEBUG}
- //NOTE1("EndOfStreamTimer called (%d)",uID);
- DbgLog(Format('EndOfStreamTimer called (%d)', [uID]));
- {$ENDIF}
- Renderer.TimerCallback;
- {
- ???
- CBaseRenderer *pRenderer = (CBaseRenderer * ) dwUser;
- pRenderer->TimerCallback();
- }
- end;
- // Milenko end
- // Do the timer callback work
- procedure TBCBaseRenderer.TimerCallback;
- begin
- // Lock for synchronization (but don't hold this lock when calling
- // timeKillEvent)
- FRendererLock.Lock;
- try
- // See if we should signal end of stream now
- if (FEndOfStreamTimer <> 0) then
- begin
- FEndOfStreamTimer := 0;
- // milenko start (why commented before?)
- SendEndOfStream;
- // milenko end
- end;
- finally
- FRendererLock.Unlock;
- end;
- end;
- // If we are at the end of the stream signal the filter graph but do not set
- // the state flag back to FALSE. Once we drop off the end of the stream we
- // leave the flag set (until a subsequent ResetEndOfStream). Each sample we
- // get delivered will update m_SignalTime to be the last sample's end time.
- // We must wait this long before signalling end of stream to the filtergraph
- const
- TIMEOUT_DELIVERYWAIT = 50;
- TIMEOUT_RESOLUTION = 10;
- function TBCBaseRenderer.SendEndOfStream: HResult;
- var
- Signal, CurrentTime: TReferenceTime;
- Delay: Longint;
- begin
- {$IFDEF DEBUG}
- Assert(FRendererLock.CritCheckIn);
- {$ENDIF}
- if ((not FIsEOS) or FIsEOSDelivered or (FEndOfStreamTimer <> 0)) then
- begin
- Result := NOERROR;
- Exit;
- end;
- // If there is no clock then signal immediately
- if (FClock = nil) then
- begin
- Result := NotifyEndOfStream;
- Exit;
- end;
- // How long into the future is the delivery time
- Signal := FStart + FSignalTime;
- FClock.GetTime(int64(CurrentTime));
- // Milenko Start (important!)
- // Delay := (Longint(Signal) - CurrentTime) div 10000;
- Delay := LongInt((Signal - CurrentTime) div 10000);
- // Milenko end
- // Dump the timing information to the debugger
- {$IFDEF DEBUG}
- DbgLog(Self, Format('Delay until end of stream delivery %d', [Delay]));
- // ??? NOTE1("Current %s",(LPCTSTR)CDisp((LONGLONG)CurrentTime));
- // ??? NOTE1("Signal %s",(LPCTSTR)CDisp((LONGLONG)Signal));
- DbgLog(Self, Format('Current %d', [CurrentTime]));
- DbgLog(Self, Format('Signal %d', [Signal]));
- {$ENDIF}
- // Wait for the delivery time to arrive
- if (Delay < TIMEOUT_DELIVERYWAIT) then
- begin
- Result := NotifyEndOfStream;
- Exit;
- end;
- // Signal a timer callback on another worker thread
- FEndOfStreamTimer := CompatibleTimeSetEvent(
- Delay, // Period of timer
- TIMEOUT_RESOLUTION, // Timer resolution
- // ???
- // Milenko start (callback is now outside of the class)
- @EndOfStreamTimer,// Callback function
- // Milenko end
- Cardinal(Self), // Used information
- TIME_ONESHOT); // Type of callback
- if (FEndOfStreamTimer = 0) then
- begin
- Result := NotifyEndOfStream;
- Exit;
- end;
- Result := NOERROR;
- end;
- // Signals EC_COMPLETE to the filtergraph manager
- function TBCBaseRenderer.NotifyEndOfStream: HResult;
- var
- Filter: IBaseFilter;
- begin
- FRendererLock.Lock;
- try
- Assert(not FIsEOSDelivered);
- Assert(FEndOfStreamTimer = 0);
- // Has the filter changed state
- if not FIsStreaming then
- begin
- Assert(FEndOfStreamTimer = 0);
- Result := NOERROR;
- Exit;
- end;
- // Reset the end of stream timer
- FEndOfStreamTimer := 0;
- // If we've been using the IMediaPosition interface, set it's start
- // and end media "times" to the stop position by hand. This ensures
- // that we actually get to the end, even if the MPEG guestimate has
- // been bad or if the quality management dropped the last few frames
- if Assigned(FPosition) then
- FPosition.EOS;
- FIsEOSDelivered := True;
- {$IFDEF DEBUG}
- DbgLog('Sending EC_COMPLETE...');
- {$ENDIF}
- // ??? return NotifyEvent(EC_COMPLETE,S_OK,(LONG_PTR)(IBaseFilter *)this);
- // milenko start (Delphi 5 compatibility)
- QueryInterface(IID_IBaseFilter,Filter);
- Result := NotifyEvent(EC_COMPLETE, S_OK, Integer(Filter));
- Filter := nil;
- // milenko end
- finally
- FRendererLock.UnLock;
- end;
- end;
- // Reset the end of stream flag, this is typically called when we transfer to
- // stopped states since that resets the current position back to the start so
- // we will receive more samples or another EndOfStream if there aren't any. We
- // keep two separate flags one to say we have run off the end of the stream
- // (this is the m_bEOS flag) and another to say we have delivered EC_COMPLETE
- // to the filter graph. We need the latter otherwise we can end up sending an
- // EC_COMPLETE every time the source changes state and calls our EndOfStream
- function TBCBaseRenderer.ResetEndOfStream: HResult;
- begin
- ResetEndOfStreamTimer;
- FRendererLock.Lock;
- try
- FIsEOS := False;
- FIsEOSDelivered := False;
- FSignalTime := 0;
- Result := NOERROR;
- finally
- FRendererLock.UnLock;
- end;
- end;
- // Kills any outstanding end of stream timer
- procedure TBCBaseRenderer.ResetEndOfStreamTimer;
- begin
- {$IFDEF DEBUG}
- Assert(FRendererLock.CritCheckOut);
- {$ENDIF}
- if (FEndOfStreamTimer <> 0) then
- begin
- timeKillEvent(FEndOfStreamTimer);
- FEndOfStreamTimer := 0;
- end;
- end;
- // This is called when we start running so that we can schedule any pending
- // image we have with the clock and display any timing information. If we
- // don't have any sample but we have queued an EOS flag then we send it. If
- // we do have a sample then we wait until that has been rendered before we
- // signal the filter graph otherwise we may change state before it's done
- function TBCBaseRenderer.StartStreaming: HResult;
- begin
- FRendererLock.Lock;
- try
- if FIsStreaming then
- begin
- Result := NOERROR;
- Exit;
- end;
- // Reset the streaming times ready for running
- FIsStreaming := True;
- timeBeginPeriod(1);
- OnStartStreaming;
- // There should be no outstanding advise
- Assert(WAIT_TIMEOUT = WaitForSingleObject(FRenderEvent.Handle, 0));
- Assert(CancelNotification = S_FALSE);
- // If we have an EOS and no data then deliver it now
- if (FMediaSample = nil) then
- begin
- Result := SendEndOfStream;
- Exit;
- end;
- // Have the data rendered
- Assert(Assigned(FMediaSample));
- if not ScheduleSample(FMediaSample) then
- FRenderEvent.SetEv;
- Result := NOERROR;
- finally
- FRendererLock.UnLock;
- end;
- end;
- // This is called when we stop streaming so that we can set our internal flag
- // indicating we are not now to schedule any more samples arriving. The state
- // change methods in the filter implementation take care of cancelling any
- // clock advise link we have set up and clearing any pending sample we have
- function TBCBaseRenderer.StopStreaming: HResult;
- begin
- FRendererLock.Lock;
- try
- FIsEOSDelivered := False;
- if FIsStreaming then
- begin
- FIsStreaming := False;
- OnStopStreaming;
- timeEndPeriod(1);
- end;
- Result := NOERROR;
- finally
- FRendererLock.Unlock;
- end;
- end;
- // We have a boolean flag that is reset when we have signalled EC_REPAINT to
- // the filter graph. We set this when we receive an image so that should any
- // conditions arise again we can send another one. By having a flag we ensure
- // we don't flood the filter graph with redundant calls. We do not set the
- // event when we receive an EndOfStream call since there is no point in us
- // sending further EC_REPAINTs. In particular the AutoShowWindow method and
- // the DirectDraw object use this method to control the window repainting
- procedure TBCBaseRenderer.SetRepaintStatus(Repaint: Boolean);
- begin
- FRendererLock.Lock;
- try
- FRepaintStatus := Repaint;
- finally
- FRendererLock.Unlock;
- end;
- end;
- // Pass the window handle to the upstream filter
- procedure TBCBaseRenderer.SendNotifyWindow(Pin: IPin; Handle: HWND);
- var
- Sink: IMediaEventSink;
- hr: HResult;
- begin
- // Does the pin support IMediaEventSink
- hr := Pin.QueryInterface(IID_IMediaEventSink, Sink);
- if Succeeded(hr) then
- begin
- Sink.Notify(EC_NOTIFY_WINDOW, Handle, 0);
- Sink := nil;
- end;
- NotifyEvent(EC_NOTIFY_WINDOW, Handle, 0);
- end;
- // Signal an EC_REPAINT to the filter graph. This can be used to have data
- // sent to us. For example when a video window is first displayed it may
- // not have an image to display, at which point it signals EC_REPAINT. The
- // filtergraph will either pause the graph if stopped or if already paused
- // it will call put_CurrentPosition of the current position. Setting the
- // current position to itself has the stream flushed and the image resent
- // ??? #define RLOG(_x_) DbgLog((LOG_TRACE,1,TEXT(_x_)));
- procedure TBCBaseRenderer.SendRepaint;
- var
- Pin: IPin;
- begin
- FRendererLock.Lock;
- try
- Assert(Assigned(FInputPin));
- // We should not send repaint notifications when...
- // - An end of stream has been notified
- // - Our input pin is being flushed
- // - The input pin is not connected
- // - We have aborted a video playback
- // - There is a repaint already sent
- if (not FAbort) and
- (FInputPin.IsConnected) and
- (not FInputPin.IsFlushing) and
- (not IsEndOfStream) and
- FRepaintStatus then
- begin
- // milenko start (delphi 5 compatibility)
- // Pin := FInputPin as IPin;
- FInputPin.QueryInterface(IID_IPin,Pin);
- NotifyEvent(EC_REPAINT, Integer(Pin), 0);
- Pin := nil;
- // milenko end
- SetRepaintStatus(False);
- {$IFDEF DEBUG}
- DbgLog('Sending repaint');
- {$ENDIF}
- end;
- finally
- FRendererLock.Unlock;
- end;
- end;
- // When a video window detects a display change (WM_DISPLAYCHANGE message) it
- // can send an EC_DISPLAY_CHANGED event code along with the renderer pin. The
- // filtergraph will stop everyone and reconnect our input pin. As we're then
- // reconnected we can accept the media type that matches the new display mode
- // since we may no longer be able to draw the current image type efficiently
- function TBCBaseRenderer.OnDisplayChange: Boolean;
- var
- Pin: IPin;
- begin
- // Ignore if we are not connected yet
- FRendererLock.Lock;
- try
- if not FInputPin.IsConnected then
- begin
- Result := False;
- Exit;
- end;
- {$IFDEF DEBUG}
- DbgLog('Notification of EC_DISPLAY_CHANGE');
- {$ENDIF}
- // Pass our input pin as parameter on the event
- // milenko start (Delphi 5 compatibility)
- // Pin := FInputPin as IPin;
- FInputPin.QueryInterface(IID_IPin,Pin);
- // ??? m_pInputPin->AddRef();
- NotifyEvent(EC_DISPLAY_CHANGED, Integer(Pin), 0);
- SetAbortSignal(True);
- ClearPendingSample;
- // FreeAndNil(FInputPin);
- Pin := nil;
- // milenko end
- Result := True;
- finally
- FRendererLock.Unlock;
- end;
- end;
- // Called just before we start drawing.
- // Store the current time in m_trRenderStart to allow the rendering time to be
- // logged. Log the time stamp of the sample and how late it is (neg is early)
- procedure TBCBaseRenderer.OnRenderStart(MediaSample: IMediaSample);
- {$IFDEF PERF}
- var
- StartTime, EndTime, StreamTime: TReferenceTime;
- {$ENDIF}
- begin
- {$IFDEF PERF}
- MediaSample.GetTime(StartTime, EndTime);
- MSR_INTEGER(FBaseStamp, Integer(StartTime)); // dump low order 32 bits
- FClock.GetTime(pint64(@FRenderStart)^);
- MSR_INTEGER(0, Integer(FRenderStart));
- StreamTime := FRenderStart - FStart; // convert reftime to stream time
- MSR_INTEGER(0, Integer(StreamTime));
- MSR_INTEGER(FBaseAccuracy, RefTimeToMiliSec(StreamTime - StartTime)); // dump in mSec
- {$ENDIF}
- end;
- // Called directly after drawing an image.
- // calculate the time spent drawing and log it.
- procedure TBCBaseRenderer.OnRenderEnd(MediaSample: IMediaSample);
- {$IFDEF PERF}
- var
- NowTime: TReferenceTime;
- t: Integer;
- {$ENDIF}
- begin
- {$IFDEF PERF}
- FClock.GetTime(int64(NowTime));
- MSR_INTEGER(0, Integer(NowTime));
- t := RefTimeToMiliSec(NowTime - FRenderStart); // convert UNITS->msec
- MSR_INTEGER(FBaseRenderTime, t);
- {$ENDIF}
- end;
- function TBCBaseRenderer.OnStartStreaming: HResult;
- begin
- Result := NOERROR;
- end;
- function TBCBaseRenderer.OnStopStreaming: HResult;
- begin
- Result := NOERROR;
- end;
- procedure TBCBaseRenderer.OnWaitStart;
- begin
- end;
- procedure TBCBaseRenderer.OnWaitEnd;
- begin
- end;
- procedure TBCBaseRenderer.PrepareRender;
- begin
- end;
- // Constructor must be passed the base renderer object
- constructor TBCRendererInputPin.Create(Renderer: TBCBaseRenderer;
- out hr: HResult; Name: PWideChar);
- begin
- inherited Create('Renderer pin', Renderer, Renderer.FInterfaceLock,
- hr, Name);
- FRenderer := Renderer;
- Assert(Assigned(FRenderer));
- end;
- // Signals end of data stream on the input pin
- function TBCRendererInputPin.EndOfStream: HResult;
- begin
- FRenderer.FInterfaceLock.Lock;
- FRenderer.FRendererLock.Lock;
- try
- // Make sure we're streaming ok
- Result := CheckStreaming;
- if (Result <> NOERROR) then
- Exit;
- // Pass it onto the renderer
- Result := FRenderer.EndOfStream;
- if Succeeded(Result) then
- Result := inherited EndOfStream;
- finally
- FRenderer.FRendererLock.UnLock;
- FRenderer.FInterfaceLock.UnLock;
- end;
- end;
- // Signals start of flushing on the input pin - we do the final reset end of
- // stream with the renderer lock unlocked but with the interface lock locked
- // We must do this because we call timeKillEvent, our timer callback method
- // has to take the renderer lock to serialise our state. Therefore holding a
- // renderer lock when calling timeKillEvent could cause a deadlock condition
- function TBCRendererInputPin.BeginFlush: HResult;
- begin
- FRenderer.FInterfaceLock.Lock;
- try
- FRenderer.FRendererLock.Lock;
- try
- inherited BeginFlush;
- FRenderer.BeginFlush;
- finally
- FRenderer.FRendererLock.UnLock;
- end;
- Result := FRenderer.ResetEndOfStream;
- finally
- FRenderer.FInterfaceLock.UnLock;
- end;
- end;
- // Signals end of flushing on the input pin
- function TBCRendererInputPin.EndFlush: HResult;
- begin
- FRenderer.FInterfaceLock.Lock;
- FRenderer.FRendererLock.Lock;
- try
- Result := FRenderer.EndFlush;
- if Succeeded(Result) then
- Result := inherited EndFlush;
- finally
- FRenderer.FRendererLock.UnLock;
- FRenderer.FInterfaceLock.UnLock;
- end;
- end;
- // Pass the sample straight through to the renderer object
- function TBCRendererInputPin.Receive(MediaSample: IMediaSample): HResult;
- var
- hr: HResult;
- begin
- hr := FRenderer.Receive(MediaSample);
- if Failed(hr) then
- begin
- // A deadlock could occur if the caller holds the renderer lock and
- // attempts to acquire the interface lock.
- {$IFDEF DEBUG}
- Assert(FRenderer.FRendererLock.CritCheckOut);
- {$ENDIF}
- // The interface lock must be held when the filter is calling
- // IsStopped or IsFlushing. The interface lock must also
- // be held because the function uses m_bRunTimeError.
- FRenderer.FInterfaceLock.Lock;
- try
- // We do not report errors which occur while the filter is stopping,
- // flushing or if the FAborting flag is set . Errors are expected to
- // occur during these operations and the streaming thread correctly
- // handles the errors.
- if (not IsStopped) and (not IsFlushing) and
- (not FRenderer.FAbort) and
- (not FRunTimeError) then
- begin
- // EC_ERRORABORT's first parameter is the error which caused
- // the event and its' last parameter is 0. See the Direct
- // Show SDK documentation for more information.
- FRenderer.NotifyEvent(EC_ERRORABORT, hr, 0);
- FRenderer.FRendererLock.Lock;
- try
- if (FRenderer.IsStreaming and
- (not FRenderer.IsEndOfStreamDelivered)) then
- FRenderer.NotifyEndOfStream;
- finally
- FRenderer.FRendererLock.UnLock;
- end;
- FRunTimeError := True;
- end;
- finally
- FRenderer.FInterfaceLock.UnLock;
- end;
- end;
- Result := hr;
- end;
- function TBCRendererInputPin.InheritedReceive(MediaSample: IMediaSample): HResult;
- begin
- Result := Inherited Receive(MediaSample);
- end;
- // Called when the input pin is disconnected
- function TBCRendererInputPin.BreakConnect: HResult;
- begin
- Result := FRenderer.BreakConnect;
- if Succeeded(Result) then
- Result := inherited BreakConnect;
- end;
- // Called when the input pin is connected
- function TBCRendererInputPin.CompleteConnect(ReceivePin: IPin): HResult;
- begin
- Result := FRenderer.CompleteConnect(ReceivePin);
- if Succeeded(Result) then
- Result := inherited CompleteConnect(ReceivePin);
- end;
- // Give the pin id of our one and only pin
- function TBCRendererInputPin.QueryId(out Id: PWideChar): HRESULT;
- begin
- // milenko start (AMGetWideString bugged before, so this call only will do fine now)
- Result := AMGetWideString('In', Id);
- // milenko end
- end;
- // Will the filter accept this media type
- function TBCRendererInputPin.CheckMediaType(MediaType: PAMMediaType): HResult;
- begin
- Result := FRenderer.CheckMediaType(MediaType);
- end;
- // Called when we go paused or running
- function TBCRendererInputPin.Active: HResult;
- begin
- Result := FRenderer.Active;
- end;
- // Called when we go into a stopped state
- function TBCRendererInputPin.Inactive: HResult;
- begin
- // The caller must hold the interface lock because
- // this function uses FRunTimeError.
- {$IFDEF DEBUG}
- Assert(FRenderer.FInterfaceLock.CritCheckIn);
- {$ENDIF}
- FRunTimeError := False;
- Result := FRenderer.Inactive;
- end;
- // Tell derived classes about the media type agreed
- function TBCRendererInputPin.SetMediaType(MediaType: PAMMediaType): HResult;
- begin
- Result := inherited SetMediaType(MediaType);
- if Succeeded(Result) then
- Result := FRenderer.SetMediaType(MediaType);
- end;
- // We do not keep an event object to use when setting up a timer link with
- // the clock but are given a pointer to one by the owning object through the
- // SetNotificationObject method - this must be initialised before starting
- // We can override the default quality management process to have it always
- // draw late frames, this is currently done by having the following registry
- // key (actually an INI key) called DrawLateFrames set to 1 (default is 0)
- (* ???
- const TCHAR AMQUALITY[] = TEXT("ActiveMovie");
- const TCHAR DRAWLATEFRAMES[] = TEXT("DrawLateFrames");
- *)
- resourcestring
- AMQUALITY = 'ActiveMovie';
- DRAWLATEFRAMES = 'DrawLateFrames';
- constructor TBCBaseVideoRenderer.Create(RenderClass: TGUID; Name: PChar;
- Unk: IUnknown; hr: HResult);
- begin
- // milenko start (not sure if this is really needed, but looks better)
- // inherited;
- inherited Create(RenderClass,Name,Unk,hr);
- // milenko end
- FFramesDropped := 0;
- FFramesDrawn := 0;
- FSupplierHandlingQuality:= False;
- ResetStreamingTimes;
- {$IFDEF PERF}
- FTimeStamp := MSR_REGISTER('Frame time stamp');
- FEarliness := MSR_REGISTER('Earliness fudge');
- FTarget := MSR_REGISTER('Target(mSec)');
- FSchLateTime := MSR_REGISTER('mSec late when scheduled');
- FDecision := MSR_REGISTER('Scheduler decision code');
- FQualityRate := MSR_REGISTER('Quality rate sent');
- FQualityTime := MSR_REGISTER('Quality time sent');
- FWaitReal := MSR_REGISTER('Render wait');
- FWait := MSR_REGISTER('wait time recorded (msec)');
- FFrameAccuracy := MSR_REGISTER('Frame accuracy(msecs)');
- FDrawLateFrames := Boolean(GetProfileInt(PChar(AMQUALITY),
- PChar(DRAWLATEFRAMES), Integer(False)));
- FSendQuality := MSR_REGISTER('Processing Quality message');
- FRenderAvg := MSR_REGISTER('Render draw time Avg');
- FFrameAvg := MSR_REGISTER('FrameAvg');
- FWaitAvg := MSR_REGISTER('WaitAvg');
- FDuration := MSR_REGISTER('Duration');
- FThrottle := MSR_REGISTER('Audio - video throttle wait');
- FDebug := MSR_REGISTER('Debug stuff');
- {$ENDIF}
- end;
- // Destructor is just a placeholder
- destructor TBCBaseVideoRenderer.Destroy;
- begin
- Assert(FAdvisedCookie = 0);
- // ??? seems should leave it, but...
- // milenko start (not really needed...)
- // inherited;
- inherited Destroy;
- // milenko end
- end;
- // The timing functions in this class are called by the window object and by
- // the renderer's allocator.
- // The windows object calls timing functions as it receives media sample
- // images for drawing using GDI.
- // The allocator calls timing functions when it starts passing DCI/DirectDraw
- // surfaces which are not rendered in the same way; The decompressor writes
- // directly to the surface with no separate rendering, so those code paths
- // call direct into us. Since we only ever hand out DCI/DirectDraw surfaces
- // when we have allocated one and only one image we know there cannot be any
- // conflict between the two.
- //
- // We use timeGetTime to return the timing counts we use (since it's relative
- // performance we are interested in rather than absolute compared to a clock)
- // The window object sets the accuracy of the system clock (normally 1ms) by
- // calling timeBeginPeriod/timeEndPeriod when it changes streaming states
- // Reset all times controlling streaming.
- // Set them so that
- // 1. Frames will not initially be dropped
- // 2. The first frame will definitely be drawn (achieved by saying that there
- // has not ben a frame drawn for a long time).
- function TBCBaseVideoRenderer.ResetStreamingTimes: HResult;
- begin
- FLastDraw := -1000; // set up as first frame since ages (1 sec) ago
- FStreamingStart := timeGetTime;
- FRenderAvg := 0;
- FFrameAvg := -1; // -1000 fps :=:= "unset"
- FDuration := 0; // 0 - strange value
- FRenderLast := 0;
- FWaitAvg := 0;
- FRenderStart := 0;
- FFramesDrawn := 0;
- FFramesDropped := 0;
- FTotAcc := 0;
- FSumSqAcc := 0;
- FSumSqFrameTime := 0;
- FFrame := 0; // hygiene - not really needed
- FLate := 0; // hygiene - not really needed
- FSumFrameTime := 0;
- FNormal := 0;
- FEarliness := 0;
- FTarget := -300000; // 30mSec early
- FThrottle := 0;
- FRememberStampForPerf := 0;
- {$IFDEF PERF}
- FRememberFrameForPerf := 0;
- {$ENDIF}
- Result := NOERROR;
- end;
- // Reset all times controlling streaming. Note that we're now streaming. We
- // don't need to set the rendering event to have the source filter released
- // as it is done during the Run processing. When we are run we immediately
- // release the source filter thread and draw any image waiting (that image
- // may already have been drawn once as a poster frame while we were paused)
- function TBCBaseVideoRenderer.OnStartStreaming: HResult;
- begin
- ResetStreamingTimes;
- Result := NOERROR;
- end;
- // Called at end of streaming. Fixes times for property page report
- function TBCBaseVideoRenderer.OnStopStreaming: HResult;
- begin
- // milenko start (better to use int64 instead of integer)
- // FStreamingStart := Integer(timeGetTime) - FStreamingStart;
- FStreamingStart := Int64(timeGetTime) - FStreamingStart;
- // milenko end
- Result := NOERROR;
- end;
- // Called when we start waiting for a rendering event.
- // Used to update times spent waiting and not waiting.
- procedure TBCBaseVideoRenderer.OnWaitStart;
- begin
- {$IFDEF PERF}
- MSR_START(FWaitReal);
- {$ENDIF}
- end;
- // Called when we are awoken from the wait in the window OR by our allocator
- // when it is hanging around until the next sample is due for rendering on a
- // DCI/DirectDraw surface. We add the wait time into our rolling average.
- // We grab the interface lock so that we're serialised with the application
- // thread going through the run code - which in due course ends up calling
- // ResetStreaming times - possibly as we run through this section of code
- procedure TBCBaseVideoRenderer.OnWaitEnd;
- {$IFDEF PERF}
- var
- RealStream, RefTime: TReferenceTime;
- // the real time now expressed as stream time.
- Late, Frame: Integer;
- {$ENDIF}
- begin
- {$IFDEF PERF}
- MSR_STOP(FWaitReal);
- // for a perf build we want to know just exactly how late we REALLY are.
- // even if this means that we have to look at the clock again.
- {$IFDEF 0}
- FClock.GetTime(RealStream); // Calling clock here causes W95 deadlock!
- {$ELSE}
- // We will be discarding overflows like mad here!
- // This is wrong really because timeGetTime() can wrap but it's
- // only for PERF
- RefTime := timeGetTime * 10000;
- RealStream := RefTime + FTimeOffset;
- {$ENDIF}
- Dec(RealStream, FStart); // convert to stream time (this is a reftime)
- if (FRememberStampForPerf = 0) then
- // This is probably the poster frame at the start, and it is not scheduled
- // in the usual way at all. Just count it. The rememberstamp gets set
- // in ShouldDrawSampleNow, so this does invalid frame recording until we
- // actually start playing.
- PreparePerformanceData(0, 0)
- else
- begin
- Late := RealStream - FRememberStampForPerf;
- Frame := RefTime - FRememberFrameForPerf;
- PreparePerformanceData(Late, Frame);
- end;
- FRememberFrameForPerf := RefTime;
- {$ENDIF}
- end;
- // Put data on one side that describes the lateness of the current frame.
- // We don't yet know whether it will actually be drawn. In direct draw mode,
- // this decision is up to the filter upstream, and it could change its mind.
- // The rules say that if it did draw it must call Receive(). One way or
- // another we eventually get into either OnRenderStart or OnDirectRender and
- // these both call RecordFrameLateness to update the statistics.
- procedure TBCBaseVideoRenderer.PreparePerformanceData(Late, Frame: Integer);
- begin
- FLate := Late;
- FFrame := Frame;
- end;
- // update the statistics:
- // m_iTotAcc, m_iSumSqAcc, m_iSumSqFrameTime, m_iSumFrameTime, m_cFramesDrawn
- // Note that because the properties page reports using these variables,
- // 1. We need to be inside a critical section
- // 2. They must all be updated together. Updating the sums here and the count
- // elsewhere can result in imaginary jitter (i.e. attempts to find square roots
- // of negative numbers) in the property page code.
- procedure TBCBaseVideoRenderer.RecordFrameLateness(Late, Frame: Integer);
- var
- _Late, _Frame: Integer;
- begin
- // Record how timely we are.
- _Late := Late div 10000;
- // Best estimate of moment of appearing on the screen is average of
- // start and end draw times. Here we have only the end time. This may
- // tend to show us as spuriously late by up to 1/2 frame rate achieved.
- // Decoder probably monitors draw time. We don't bother.
- {$IFDEF PERF}
- MSR_INTEGER(FFrameAccuracy, _Late);
- {$ENDIF}
- // This is a kludge - we can get frames that are very late
- // especially (at start-up) and they invalidate the statistics.
- // So ignore things that are more than 1 sec off.
- if (_Late > 1000) or (_Late < -1000) then
- if (FFramesDrawn <= 1) then
- _Late := 0
- else if (_Late > 0) then
- _Late := 1000
- else
- _Late := -1000;
- // The very first frame often has a invalid time, so don't
- // count it into the statistics. (???)
- if (FFramesDrawn > 1) then
- begin
- Inc(FTotAcc, _Late);
- Inc(FSumSqAcc, _Late * _Late);
- end;
- // calculate inter-frame time. Doesn't make sense for first frame
- // second frame suffers from invalid first frame stamp.
- if (FFramesDrawn > 2) then
- begin
- _Frame := Frame div 10000; // convert to mSec else it overflows
- // This is a kludge. It can overflow anyway (a pause can cause
- // a very long inter-frame time) and it overflows at 2**31/10**7
- // or about 215 seconds i.e. 3min 35sec
- if (_Frame > 1000) or (_Frame < 0) then
- _Frame := 1000;
- Inc(FSumSqFrameTime, _Frame * _Frame);
- Assert(FSumSqFrameTime >= 0);
- Inc(FSumFrameTime, _Frame);
- end;
- Inc(FFramesDrawn);
- end;
- procedure TBCBaseVideoRenderer.ThrottleWait;
- var
- Throttle: Integer;
- begin
- if (FThrottle > 0) then
- begin
- Throttle := FThrottle div 10000; // convert to mSec
- MSR_INTEGER(FThrottle, Throttle);
- {$IFDEF DEBUG}
- DbgLog(Self, Format('Throttle %d ms', [Throttle]));
- {$ENDIF}
- Sleep(Throttle);
- end
- else
- Sleep(0);
- end;
- // Whenever a frame is rendered it goes though either OnRenderStart
- // or OnDirectRender. Data that are generated during ShouldDrawSample
- // are added to the statistics by calling RecordFrameLateness from both
- // these two places.
- // Called in place of OnRenderStart..OnRenderEnd
- // When a DirectDraw image is drawn
- procedure TBCBaseVideoRenderer.OnDirectRender(MediaSample: IMediaSample);
- begin
- FRenderAvg := 0;
- FRenderLast := 5000000; // If we mode switch, we do NOT want this
- // to inhibit the new average getting going!
- // so we set it to half a second
- // MSR_INTEGER(m_idRenderAvg, m_trRenderAvg div 10000);
- RecordFrameLateness(FLate, FFrame);
- ThrottleWait;
- end;
- // Called just before we start drawing. All we do is to get the current clock
- // time (from the system) and return. We have to store the start render time
- // in a member variable because it isn't used until we complete the drawing
- // The rest is just performance logging.
- procedure TBCBaseVideoRenderer.OnRenderStart(MediaSample: IMediaSample);
- begin
- RecordFrameLateness(FLate, FFrame);
- FRenderStart := timeGetTime;
- end;
- // Called directly after drawing an image. We calculate the time spent in the
- // drawing code and if this doesn't appear to have any odd looking spikes in
- // it then we add it to the current average draw time. Measurement spikes may
- // occur if the drawing thread is interrupted and switched to somewhere else.
- procedure TBCBaseVideoRenderer.OnRenderEnd(MediaSample: IMediaSample);
- var
- RefTime: Integer;
- begin
- // The renderer time can vary erratically if we are interrupted so we do
- // some smoothing to help get more sensible figures out but even that is
- // not enough as figures can go 9,10,9,9,83,9 and we must disregard 83
- // milenko start
- // RefTime := (Integer(timeGetTime) - FRenderStart) * 10000;
- RefTime := (Int64(timeGetTime) - FRenderStart) * 10000;
- // milenko end
- // convert mSec->UNITS
- if (RefTime < FRenderAvg * 2) or (RefTime < 2 * FRenderLast) then
- // DO_MOVING_AVG(m_trRenderAvg, tr);
- FRenderAvg := (RefTime + (AVGPERIOD - 1) * FRenderAvg) div AVGPERIOD;
- FRenderLast := RefTime;
- ThrottleWait;
- end;
- function TBCBaseVideoRenderer.SetSink(QualityControl: IQualityControl): HResult;
- begin
- FQSink := QualityControl;
- Result := NOERROR;
- end;
- function TBCBaseVideoRenderer.Notify(Filter: IBaseFilter;
- Q: TQuality): HResult;
- begin
- // NOTE: We are NOT getting any locks here. We could be called
- // asynchronously and possibly even on a time critical thread of
- // someone else's - so we do the minumum. We only set one state
- // variable (an integer) and if that happens to be in the middle
- // of another thread reading it they will just get either the new
- // or the old value. Locking would achieve no more than this.
- // It might be nice to check that we are being called from m_pGraph, but
- // it turns out to be a millisecond or so per throw!
- // This is heuristics, these numbers are aimed at being "what works"
- // rather than anything based on some theory.
- // We use a hyperbola because it's easy to calculate and it includes
- // a panic button asymptote (which we push off just to the left)
- // The throttling fits the following table (roughly)
- // Proportion Throttle (msec)
- // >=1000 0
- // 900 3
- // 800 7
- // 700 11
- // 600 17
- // 500 25
- // 400 35
- // 300 50
- // 200 72
- // 125 100
- // 100 112
- // 50 146
- // 0 200
- // (some evidence that we could go for a sharper kink - e.g. no throttling
- // until below the 750 mark - might give fractionally more frames on a
- // P60-ish machine). The easy way to get these coefficients is to use
- // Renbase.xls follow the instructions therein using excel solver.
- if (q.Proportion >= 1000) then
- FThrottle := 0
- else
- // The DWORD is to make quite sure I get unsigned arithmetic
- // as the constant is between 2**31 and 2**32
- FThrottle := -330000 + (388880000 div (q.Proportion + 167));
- Result := NOERROR;
- end;
- // Send a message to indicate what our supplier should do about quality.
- // Theory:
- // What a supplier wants to know is "is the frame I'm working on NOW
- // going to be late?".
- // F1 is the frame at the supplier (as above)
- // Tf1 is the due time for F1
- // T1 is the time at that point (NOW!)
- // Tr1 is the time that f1 WILL actually be rendered
- // L1 is the latency of the graph for frame F1 = Tr1-T1
- // D1 (for delay) is how late F1 will be beyond its due time i.e.
- // D1 = (Tr1-Tf1) which is what the supplier really wants to know.
- // Unfortunately Tr1 is in the future and is unknown, so is L1
- //
- // We could estimate L1 by its value for a previous frame,
- // L0 = Tr0-T0 and work off
- // D1' = ((T1+L0)-Tf1) = (T1 + (Tr0-T0) -Tf1)
- // Rearranging terms:
- // D1' = (T1-T0) + (Tr0-Tf1)
- // adding (Tf0-Tf0) and rearranging again:
- // = (T1-T0) + (Tr0-Tf0) + (Tf0-Tf1)
- // = (T1-T0) - (Tf1-Tf0) + (Tr0-Tf0)
- // But (Tr0-Tf0) is just D0 - how late frame zero was, and this is the
- // Late field in the quality message that we send.
- // The other two terms just state what correction should be applied before
- // using the lateness of F0 to predict the lateness of F1.
- // (T1-T0) says how much time has actually passed (we have lost this much)
- // (Tf1-Tf0) says how much time should have passed if we were keeping pace
- // (we have gained this much).
- //
- // Suppliers should therefore work off:
- // Quality.Late + (T1-T0) - (Tf1-Tf0)
- // and see if this is "acceptably late" or even early (i.e. negative).
- // They get T1 and T0 by polling the clock, they get Tf1 and Tf0 from
- // the time stamps in the frames. They get Quality.Late from us.
- //
- function TBCBaseVideoRenderer.SendQuality(Late,
- RealStream: TReferenceTime): HResult;
- var
- q: TQuality;
- hr: HResult;
- QC: IQualityControl;
- OutputPin: IPin;
- begin
- // If we are the main user of time, then report this as Flood/Dry.
- // If our suppliers are, then report it as Famine/Glut.
- //
- // We need to take action, but avoid hunting. Hunting is caused by
- // 1. Taking too much action too soon and overshooting
- // 2. Taking too long to react (so averaging can CAUSE hunting).
- //
- // The reason why we use trLate as well as Wait is to reduce hunting;
- // if the wait time is coming down and about to go into the red, we do
- // NOT want to rely on some average which is only telling is that it used
- // to be OK once.
- q.TimeStamp := RealStream;
- if (FFrameAvg < 0) then
- q.Typ := Famine // guess
- // Is the greater part of the time taken bltting or something else
- else if (FFrameAvg > 2 * FRenderAvg) then
- q.Typ := Famine // mainly other
- else
- q.Typ := Flood; // mainly bltting
- q.Proportion := 1000; // default
- if (FFrameAvg < 0) then
- // leave it alone - we don't know enough
- else if (Late > 0) then
- begin
- // try to catch up over the next second
- // We could be Really, REALLY late, but rendering all the frames
- // anyway, just because it's so cheap.
- q.Proportion := 1000 - (Late div (UNITS div 1000));
- if (q.Proportion < 500) then
- q.Proportion := 500; // don't go daft. (could've been negative!)
- end
- // milenko start
- else if (FWaitAvg > 20000) and (Late < -20000) then
- begin
- // if (FWaitAvg > 20000) and (Late < -20000) then
- // Go cautiously faster - aim at 2mSec wait.
- if (FWaitAvg >= FFrameAvg) then
- begin
- // This can happen because of some fudges.
- // The waitAvg is how long we originally planned to wait
- // The frameAvg is more honest.
- // It means that we are spending a LOT of time waiting
- q.Proportion := 2000 // double.
- end else
- begin
- if (FFrameAvg + 20000 > FWaitAvg) then
- q.Proportion := 1000 * (FFrameAvg div (FFrameAvg + 20000 - FWaitAvg))
- else
- // We're apparently spending more than the whole frame time waiting.
- // Assume that the averages are slightly out of kilter, but that we
- // are indeed doing a lot of waiting. (This leg probably never
- // happens, but the code avoids any potential divide by zero).
- q.Proportion := 2000;
- end;
- if (q.Proportion > 2000) then
- q.Proportion := 2000; // don't go crazy.
- end;
- // milenko end
- // Tell the supplier how late frames are when they get rendered
- // That's how late we are now.
- // If we are in directdraw mode then the guy upstream can see the drawing
- // times and we'll just report on the start time. He can figure out any
- // offset to apply. If we are in DIB Section mode then we will apply an
- // extra offset which is half of our drawing time. This is usually small
- // but can sometimes be the dominant effect. For this we will use the
- // average drawing time rather than the last frame. If the last frame took
- // a long time to draw and made us late, that's already in the lateness
- // figure. We should not add it in again unless we expect the next frame
- // to be the same. We don't, we expect the average to be a better shot.
- // In direct draw mode the RenderAvg will be zero.
- q.Late := Late + FRenderAvg div 2;
- {$IFDEF PERF}
- // log what we're doing
- MSR_INTEGER(FQualityRate, q.Proportion);
- MSR_INTEGER(FQualityTime, refTimeToMiliSec(q.Late));
- {$ENDIF}
- // A specific sink interface may be set through IPin
- if (FQSink = nil) then
- begin
- // Get our input pin's peer. We send quality management messages
- // to any nominated receiver of these things (set in the IPin
- // interface), or else to our source filter.
- QC := nil;
- OutputPin := FInputPin.GetConnected;
- Assert(Assigned(OutputPin));
- // And get an AddRef'd quality control interface
- hr := OutputPin.QueryInterface(IID_IQualityControl, QC);
- if Succeeded(hr) then
- FQSink := QC;
- end;
- if Assigned(FQSink) then
- Result := FQSink.Notify(Self, q)
- else
- Result := S_FALSE;
- end;
- // We are called with a valid IMediaSample image to decide whether this is to
- // be drawn or not. There must be a reference clock in operation.
- // Return S_OK if it is to be drawn Now (as soon as possible)
- // Return S_FALSE if it is to be drawn when it's due
- // Return an error if we want to drop it
- // m_nNormal=-1 indicates that we dropped the previous frame and so this
- // one should be drawn early. Respect it and update it.
- // Use current stream time plus a number of heuristics (detailed below)
- // to make the decision
- (* ??? StartTime is changing inside routine:
- Inc(StartTime, E); // N.B. earliness is negative
- So, maybe it should be declared as var or out?
- *)
- function TBCBaseVideoRenderer.ShouldDrawSampleNow(MediaSample: IMediaSample;
- StartTime: TReferenceTime; out EndTime: TReferenceTime): HResult;
- var
- RealStream: TReferenceTime; // the real time now expressed as stream time.
- RefTime: TReferenceTime;
- TrueLate, Late, Duration, t, WaitAvg, L, Frame, E, Delay
- {$IFNDEF PERF} , Accuracy{$ENDIF}: Integer;
- hr: HResult;
- JustDroppedFrame, Res, PlayASAP: Boolean;
- begin
- // Don't call us unless there's a clock interface to synchronise with
- Assert(Assigned(FClock));
- {$IFDEF PERF}
- MSR_INTEGER(FTimeStamp, Integer(StartTime shr 32)); // high order 32 bits
- MSR_INTEGER(FTimeStamp, Integer(StartTime)); // low order 32 bits
- {$ENDIF}
- // We lose a bit of time depending on the monitor type waiting for the next
- // screen refresh. On average this might be about 8mSec - so it will be
- // later than we think when the picture appears. To compensate a bit
- // we bias the media samples by -8mSec i.e. 80000 UNITs.
- // We don't ever make a stream time negative (call it paranoia)
- if (StartTime >= 80000) then
- begin
- Dec(StartTime, 80000);
- Dec(EndTime, 80000); // bias stop to to retain valid frame duration
- end;
- // Cache the time stamp now. We will want to compare what we did with what
- // we started with (after making the monitor allowance).
- FRememberStampForPerf := StartTime;
- // Get reference times (current and late)
- FClock.GetTime(int64(RealStream));
- {$IFDEF PERF}
- // While the reference clock is expensive:
- // Remember the offset from timeGetTime and use that.
- // This overflows all over the place, but when we subtract to get
- // differences the overflows all cancel out.
- FTimeOffset := RealStream - timeGetTime * 10000;
- {$ENDIF}
- Dec(RealStream, FStart); // convert to stream time (this is a reftime)
- // We have to wory about two versions of "lateness". The truth, which we
- // try to work out here and the one measured against m_trTarget which
- // includes long term feedback. We report statistics against the truth
- // but for operational decisions we work to the target.
- // We use TimeDiff to make sure we get an integer because we
- // may actually be late (or more likely early if there is a big time
- // gap) by a very long time.
- TrueLate := TimeDiff(RealStream - StartTime);
- Late := TrueLate;
- {$IFDEF PERF}
- MSR_INTEGER(FSchLateTime, refTimeToMiliSec(TrueLate));
- {$ENDIF}
- // Send quality control messages upstream, measured against target
- hr := SendQuality(Late, RealStream);
- // Note: the filter upstream is allowed to this FAIL meaning "you do it".
- FSupplierHandlingQuality := (hr = S_OK);
- // Decision time! Do we drop, draw when ready or draw immediately?
- Duration := EndTime - StartTime;
- // We need to see if the frame rate of the file has just changed.
- // This would make comparing our previous frame rate with the current
- // frame rate inefficent. Hang on a moment though. I've seen files
- // where the frames vary between 33 and 34 mSec so as to average
- // 30fps. A minor variation like that won't hurt us.
- t := FDuration div 32;
- if (Duration > FDuration + t) or (Duration < FDuration - t) then
- begin
- // There's a major variation. Reset the average frame rate to
- // exactly the current rate to disable decision 9002 for this frame,
- // and remember the new rate.
- FFrameAvg := Duration;
- FDuration := Duration;
- end;
- {$IFDEF PERF}
- MSR_INTEGER(FEarliness, refTimeToMiliSec(FEarliness));
- MSR_INTEGER(FRenderAvg, refTimeToMiliSec(FRenderAvg));
- MSR_INTEGER(FFrameAvg, refTimeToMiliSec(FFrameAvg));
- MSR_INTEGER(FWaitAvg, refTimeToMiliSec(FWaitAvg));
- MSR_INTEGER(FDuration, refTimeToMiliSec(FDuration));
- if (S_OK = MediaSample.IsDiscontinuity) then
- MSR_INTEGER(FDecision, 9000);
- {$ENDIF}
- // Control the graceful slide back from slow to fast machine mode.
- // After a frame drop accept an early frame and set the earliness to here
- // If this frame is already later than the earliness then slide it to here
- // otherwise do the standard slide (reduce by about 12% per frame).
- // Note: earliness is normally NEGATIVE
- JustDroppedFrame :=
- (FSupplierHandlingQuality and
- // Can't use the pin sample properties because we might
- // not be in Receive when we call this
- (S_OK = MediaSample.IsDiscontinuity) // he just dropped one
- ) or
- (FNormal = -1); // we just dropped one
- // Set m_trEarliness (slide back from slow to fast machine mode)
- if (Late > 0) then
- FEarliness := 0 // we are no longer in fast machine mode at all!
- else if ((Late >= FEarliness) or JustDroppedFrame) then
- FEarliness := Late // Things have slipped of their own accord
- else
- FEarliness := FEarliness - FEarliness div 8; // graceful slide
- // prepare the new wait average - but don't pollute the old one until
- // we have finished with it.
- // We never mix in a negative wait. This causes us to believe in fast machines
- // slightly more.
- if (Late < 0) then
- L := -Late
- else
- L := 0;
- WaitAvg := (L + FWaitAvg * (AVGPERIOD - 1)) div AVGPERIOD;
- RefTime := RealStream - FLastDraw; // Cd be large - 4 min pause!
- if (RefTime > 10000000) then
- RefTime := 10000000; // 1 second - arbitrarily.
- Frame := RefTime;
- if FSupplierHandlingQuality then
- Res := (Late <= Duration * 4)
- else
- Res := (Late + Late < Duration);
- // We will DRAW this frame IF...
- if (
- // ...the time we are spending drawing is a small fraction of the total
- // observed inter-frame time so that dropping it won't help much.
- (3 * FRenderAvg <= FFrameAvg)
- // ...or our supplier is NOT handling things and the next frame would
- // be less timely than this one or our supplier CLAIMS to be handling
- // things, and is now less than a full FOUR frames late.
- or Res
- // ...or we are on average waiting for over eight milliseconds then
- // this may be just a glitch. Draw it and we'll hope to catch up.
- or (FWaitAvg > 80000)
- // ...or we haven't drawn an image for over a second. We will update
- // the display, which stops the video looking hung.
- // Do this regardless of how late this media sample is.
- or ((RealStream - FLastDraw) > UNITS)
- ) then
- begin
- // We are going to play this frame. We may want to play it early.
- // We will play it early if we think we are in slow machine mode.
- // If we think we are NOT in slow machine mode, we will still play
- // it early by m_trEarliness as this controls the graceful slide back.
- // and in addition we aim at being m_trTarget late rather than "on time".
- PlayASAP := False;
- // we will play it AT ONCE (slow machine mode) if...
- // ...we are playing catch-up
- if (JustDroppedFrame) then
- begin
- PlayASAP := True;
- {$IFDEF PERF}
- MSR_INTEGER(FDecision, 9001);
- {$ENDIF}
- end
- // ...or if we are running below the true frame rate
- // exact comparisons are glitchy, for these measurements,
- // so add an extra 5% or so
- else if (FFrameAvg > Duration + Duration div 16)
- // It's possible to get into a state where we are losing ground, but
- // are a very long way ahead. To avoid this or recover from it
- // we refuse to play early by more than 10 frames.
- and (Late > -Duration * 10) then
- begin
- PlayASAP := True;
- {$IFDEF PERF}
- MSR_INTEGER(FDecision, 9002);
- {$ENDIF}
- end
- {$IFDEF 0}
- // ...or if we have been late and are less than one frame early
- else if ((Late + Duration > 0) and
- (FWaitAvg <= 20000) then
- begin
- PlayASAP := True;
- {$IFDEF PERF}
- MSR_INTEGER(m_idDecision, 9003);
- {$ENDIF}
- end
- {$ENDIF}
- ;
- // We will NOT play it at once if we are grossly early. On very slow frame
- // rate movies - e.g. clock.avi - it is not a good idea to leap ahead just
- // because we got starved (for instance by the net) and dropped one frame
- // some time or other. If we are more than 900mSec early, then wait.
- if (Late < -9000000) then
- PlayASAP := False;
- if PlayASAP then
- begin
- FNormal := 0;
- {$IFDEF PERF}
- MSR_INTEGER(FDecision, 0);
- {$ENDIF}
- // When we are here, we are in slow-machine mode. trLate may well
- // oscillate between negative and positive when the supplier is
- // dropping frames to keep sync. We should not let that mislead
- // us into thinking that we have as much as zero spare time!
- // We just update with a zero wait.
- FWaitAvg := (FWaitAvg * (AVGPERIOD - 1)) div AVGPERIOD;
- // Assume that we draw it immediately. Update inter-frame stats
- FFrameAvg := (Frame + FFrameAvg * (AVGPERIOD - 1)) div AVGPERIOD;
- {$IFNDEF PERF}
- // If this is NOT a perf build, then report what we know so far
- // without looking at the clock any more. This assumes that we
- // actually wait for exactly the time we hope to. It also reports
- // how close we get to the manipulated time stamps that we now have
- // rather than the ones we originally started with. It will
- // therefore be a little optimistic. However it's fast.
- PreparePerformanceData(TrueLate, Frame);
- {$ENDIF}
- FLastDraw := RealStream;
- if (FEarliness > Late) then
- FEarliness := Late; // if we are actually early, this is neg
- Result := S_OK; // Draw it now
- end
- else
- begin
- Inc(FNormal);
- // Set the average frame rate to EXACTLY the ideal rate.
- // If we are exiting slow-machine mode then we will have caught up
- // and be running ahead, so as we slide back to exact timing we will
- // have a longer than usual gap at this point. If we record this
- // real gap then we'll think that we're running slow and go back
- // into slow-machine mode and vever get it straight.
- FFrameAvg := Duration;
- {$IFDEF PERF}
- MSR_INTEGER(FDecision, 1);
- {$ENDIF}
- // Play it early by m_trEarliness and by m_trTarget
- E := FEarliness;
- if (E < -FFrameAvg) then
- E := -FFrameAvg;
- Inc(StartTime, E); // N.B. earliness is negative
- Delay := -TrueLate;
- if (Delay <= 0) then
- Result := S_OK
- else
- Result := S_FALSE; // OK = draw now, FALSE = wait
- FWaitAvg := WaitAvg;
- // Predict when it will actually be drawn and update frame stats
- if (Result = S_FALSE) then // We are going to wait
- begin
- {$IFNDEF PERF}
- Frame := TimeDiff(StartTime - FLastDraw);
- {$ENDIF}
- FLastDraw := StartTime;
- end
- else
- // trFrame is already = trRealStream-m_trLastDraw;
- FLastDraw := RealStream;
- {$IFNDEF PERF}
- if (Delay > 0) then
- // Report lateness based on when we intend to play it
- Accuracy := TimeDiff(StartTime - FRememberStampForPerf)
- else
- // Report lateness based on playing it *now*.
- Accuracy := TrueLate; // trRealStream-RememberStampForPerf;
- PreparePerformanceData(Accuracy, Frame);
- {$ENDIF}
- end;
- Exit;
- end;
- // We are going to drop this frame!
- // Of course in DirectDraw mode the guy upstream may draw it anyway.
- // This will probably give a large negative wack to the wait avg.
- FWaitAvg := WaitAvg;
- {$IFDEF PERF}
- // Respect registry setting - debug only!
- if (FDrawLateFrames) then
- begin
- Result := S_OK; // draw it when it's ready
- // even though it's late.
- Exit;
- end;
- {$ENDIF}
- // We are going to drop this frame so draw the next one early
- // n.b. if the supplier is doing direct draw then he may draw it anyway
- // but he's doing something funny to arrive here in that case.
- {$IFDEF PERF}
- MSR_INTEGER(FDecision, 2);
- {$ENDIF}
- FNormal := -1;
- Result := E_FAIL; // drop it
- end;
- // NOTE we're called by both the window thread and the source filter thread
- // so we have to be protected by a critical section (locked before called)
- // Also, when the window thread gets signalled to render an image, it always
- // does so regardless of how late it is. All the degradation is done when we
- // are scheduling the next sample to be drawn. Hence when we start an advise
- // link to draw a sample, that sample's time will always become the last one
- // drawn - unless of course we stop streaming in which case we cancel links
- function TBCBaseVideoRenderer.ScheduleSample(MediaSample: IMediaSample):
- Boolean;
- begin
- // We override ShouldDrawSampleNow to add quality management
- Result := inherited ScheduleSample(MediaSample);
- if not Result then
- Inc(FFramesDropped);
- // m_cFramesDrawn must NOT be updated here. It has to be updated
- // in RecordFrameLateness at the same time as the other statistics.
- end;
- // Implementation of IQualProp interface needed to support the property page
- // This is how the property page gets the data out of the scheduler. We are
- // passed into the constructor the owning object in the COM sense, this will
- // either be the video renderer or an external IUnknown if we're aggregated.
- // We initialise our CUnknown base class with this interface pointer. Then
- // all we have to do is to override NonDelegatingQueryInterface to expose
- // our IQualProp interface. The AddRef and Release are handled automatically
- // by the base class and will be passed on to the appropriate outer object
- function TBCBaseVideoRenderer.get_FramesDroppedInRenderer(var FramesDropped:
- Integer): HResult;
- begin
- // milenko start
- if not Assigned(@FramesDropped) then
- begin
- Result := E_POINTER;
- Exit;
- end;
- // milenko end
- FInterfaceLock.Lock;
- try
- FramesDropped := FFramesDropped;
- Result := NOERROR;
- finally
- FInterfaceLock.UnLock;
- end;
- end;
- // Set *pcFramesDrawn to the number of frames drawn since
- // streaming started.
- function TBCBaseVideoRenderer.get_FramesDrawn(out FramesDrawn: Integer):
- HResult;
- begin
- // milenko start
- if not Assigned(@FramesDrawn) then
- begin
- Result := E_POINTER;
- Exit;
- end;
- // milenko end
- FInterfaceLock.Lock;
- try
- FramesDrawn := FFramesDrawn;
- Result := NOERROR;
- finally
- FInterfaceLock.UnLock;
- end;
- end;
- // Set iAvgFrameRate to the frames per hundred secs since
- // streaming started. 0 otherwise.
- function TBCBaseVideoRenderer.get_AvgFrameRate(out AvgFrameRate: Integer):
- HResult;
- var
- t: Integer;
- begin
- // milenko start
- if not Assigned(@AvgFrameRate) then
- begin
- Result := E_POINTER;
- Exit;
- end;
- // milenko end
- FInterfaceLock.Lock;
- try
- if (FIsStreaming) then
- // milenko start
- // t := Integer(timeGetTime) - FStreamingStart
- t := Int64(timeGetTime) - FStreamingStart
- // milenko end
- else
- t := FStreamingStart;
- if (t <= 0) then
- begin
- AvgFrameRate := 0;
- Assert(FFramesDrawn = 0);
- end
- else
- // i is frames per hundred seconds
- AvgFrameRate := MulDiv(100000, FFramesDrawn, t);
- Result := NOERROR;
- finally
- FInterfaceLock.UnLock;
- end;
- end;
- // Set *piAvg to the average sync offset since streaming started
- // in mSec. The sync offset is the time in mSec between when the frame
- // should have been drawn and when the frame was actually drawn.
- function TBCBaseVideoRenderer.get_AvgSyncOffset(out Avg: Integer): HResult;
- begin
- // milenko start
- if not Assigned(@Avg) then
- begin
- Result := E_POINTER;
- Exit;
- end;
- // milenko end
- FInterfaceLock.Lock;
- try
- if (nil = FClock) then
- begin
- Avg := 0;
- Result := NOERROR;
- Exit;
- end;
- // Note that we didn't gather the stats on the first frame
- // so we use m_cFramesDrawn-1 here
- if (FFramesDrawn <= 1) then
- Avg := 0
- else
- Avg := (FTotAcc div (FFramesDrawn - 1));
- Result := NOERROR;
- finally
- FInterfaceLock.UnLock;
- end;
- end;
- // To avoid dragging in the maths library - a cheap
- // approximate integer square root.
- // We do this by getting a starting guess which is between 1
- // and 2 times too large, followed by THREE iterations of
- // Newton Raphson. (That will give accuracy to the nearest mSec
- // for the range in question - roughly 0..1000)
- //
- // It would be faster to use a linear interpolation and ONE NR, but
- // who cares. If anyone does - the best linear interpolation is
- // to approximates sqrt(x) by
- // y = x * (sqrt(2)-1) + 1 - 1/sqrt(2) + 1/(8*(sqrt(2)-1))
- // 0r y = x*0.41421 + 0.59467
- // This minimises the maximal error in the range in question.
- // (error is about +0.008883 and then one NR will give error .0000something
- // (Of course these are integers, so you can't just multiply by 0.41421
- // you'd have to do some sort of MulDiv).
- // Anyone wanna check my maths? (This is only for a property display!)
- function isqrt(x: Integer): Integer;
- var
- s: Integer;
- begin
- s := 1;
- // Make s an initial guess for sqrt(x)
- if (x > $40000000) then
- s := $8000 // prevent any conceivable closed loop
- else
- begin
- while (s * s < x) do // loop cannot possible go more than 31 times
- s := 2 * s; // normally it goes about 6 times
- // Three NR iterations.
- if (x = 0) then
- s := 0 // Wouldn't it be tragic to divide by zero whenever our
- // accuracy was perfect!
- else
- begin
- s := (s * s + x) div (2 * s);
- if (s >= 0) then
- s := (s * s + x) div (2 * s);
- if (s >= 0) then
- s := (s * s + x) div (2 * s);
- end;
- end;
- Result := s;
- end;
- //
- // Do estimates for standard deviations for per-frame
- // statistics
- //
- function TBCBaseVideoRenderer.GetStdDev(Samples: Integer; out Res: Integer;
- SumSq, Tot: Int64): HResult;
- var
- x: Int64;
- begin
- // milenko start
- if not Assigned(@Res) then
- begin
- Result := E_POINTER;
- Exit;
- end;
- // milenko end
- FInterfaceLock.Lock;
- try
- if (nil = FClock) then
- begin
- Res := 0;
- Result := NOERROR;
- Exit;
- end;
- // If S is the Sum of the Squares of observations and
- // T the Total (i.e. sum) of the observations and there were
- // N observations, then an estimate of the standard deviation is
- // sqrt( (S - T**2/N) / (N-1) )
- if (Samples <= 1) then
- Res := 0
- else
- begin
- // First frames have invalid stamps, so we get no stats for them
- // So we need 2 frames to get 1 datum, so N is cFramesDrawn-1
- // so we use m_cFramesDrawn-1 here
- // ??? llMilDiv ???
- // milenko start (removed the 2 outputdebugstring messages...i added them and
- // they are not needed anymore)
- x := SumSq - llMulDiv(Tot, Tot, Samples, 0);
- x := x div (Samples - 1);
- // milenko end
- Assert(x >= 0);
- Res := isqrt(Longint(x));
- end;
- Result := NOERROR;
- finally
- FInterfaceLock.UnLock;
- end;
- end;
- // Set *piDev to the standard deviation in mSec of the sync offset
- // of each frame since streaming started.
- function TBCBaseVideoRenderer.get_DevSyncOffset(out Dev: Integer): HResult;
- begin
- // First frames have invalid stamps, so we get no stats for them
- // So we need 2 frames to get 1 datum, so N is cFramesDrawn-1
- Result := GetStdDev(FFramesDrawn - 1, Dev, FSumSqAcc, FTotAcc);
- end;
- // Set *piJitter to the standard deviation in mSec of the inter-frame time
- // of frames since streaming started.
- function TBCBaseVideoRenderer.get_Jitter(out Jitter: Integer): HResult;
- begin
- // First frames have invalid stamps, so we get no stats for them
- // So second frame gives invalid inter-frame time
- // So we need 3 frames to get 1 datum, so N is cFramesDrawn-2
- Result := GetStdDev(FFramesDrawn - 2, Jitter, FSumSqFrameTime, FSumFrameTime);
- end;
- // Overidden to return our IQualProp interface
- function TBCBaseVideoRenderer.NonDelegatingQueryInterface(const IID: TGUID;
- out Obj): HResult;
- begin
- // We return IQualProp and delegate everything else
- if IsEqualGUID(IID, IID_IQualProp) then
- if GetInterface(IID_IQualProp, Obj) then
- Result := S_OK
- else
- Result := E_FAIL
- else if IsEqualGUID(IID, IID_IQualityControl) then
- if GetInterface(IID_IQualityControl, Obj) then
- Result := S_OK
- else
- Result := E_FAIL
- else
- Result := inherited NonDelegatingQueryInterface(IID, Obj);
- end;
- // Override JoinFilterGraph so that, just before leaving
- // the graph we can send an EC_WINDOW_DESTROYED event
- function TBCBaseVideoRenderer.JoinFilterGraph(Graph: IFilterGraph;
- Name: PWideChar): HResult;
- var
- Filter: IBaseFilter;
- begin
- // Since we send EC_ACTIVATE, we also need to ensure
- // we send EC_WINDOW_DESTROYED or the resource manager may be
- // holding us as a focus object
- if (Graph = nil) and Assigned(FGraph) then
- begin
- // We were in a graph and now we're not
- // Do this properly in case we are aggregated
- QueryInterface(IID_IBaseFilter, Filter);
- NotifyEvent(EC_WINDOW_DESTROYED, Integer(Filter), 0);
- Filter := nil;
- end;
- Result := inherited JoinFilterGraph(Graph, Name);
- end;
- // milenko start (added TBCPullPin)
- constructor TBCPullPin.Create;
- begin
- inherited Create;
- FReader := nil;
- FAlloc := nil;
- FState := TM_Exit;
- end;
- destructor TBCPullPin.Destroy;
- begin
- Disconnect;
- end;
- procedure TBCPullPin.Process;
- var
- Discontinuity: Boolean;
- Actual: TAllocatorProperties;
- hr: HRESULT;
- Start, Stop, Current, AlignStop: TReferenceTime;
- Request: DWORD;
- Sample: IMediaSample;
- StopThis: Int64;
- begin
- // is there anything to do?
- if (FStop <= FStart) then
- begin
- EndOfStream;
- Exit;
- end;
- Discontinuity := True;
- // if there is more than one sample at the allocator,
- // then try to queue 2 at once in order to overlap.
- // -- get buffer count and required alignment
- FAlloc.GetProperties(Actual);
- // align the start position downwards
- Start := AlignDown(FStart div UNITS, Actual.cbAlign) * UNITS;
- Current := Start;
- Stop := FStop;
- if (Stop > FDuration) then Stop := FDuration;
- // align the stop position - may be past stop, but that
- // doesn't matter
- AlignStop := AlignUp(Stop div UNITS, Actual.cbAlign) * UNITS;
- if not FSync then
- begin
- // Break out of the loop either if we get to the end or we're asked
- // to do something else
- while (Current < AlignStop) do
- begin
- // Break out without calling EndOfStream if we're asked to
- // do something different
- if CheckRequest(@Request) then Exit;
- // queue a first sample
- if (Actual.cBuffers > 1) then
- begin
- hr := QueueSample(Current, AlignStop, True);
- Discontinuity := False;
- if FAILED(hr) then Exit;
- end;
- // loop queueing second and waiting for first..
- while (Current < AlignStop) do
- begin
- hr := QueueSample(Current, AlignStop, Discontinuity);
- Discontinuity := False;
- if FAILED(hr) then Exit;
- hr := CollectAndDeliver(Start, Stop);
- if (S_OK <> hr) then
- begin
- // stop if error, or if downstream filter said
- // to stop.
- Exit;
- end;
- end;
- if (Actual.cBuffers > 1) then
- begin
- hr := CollectAndDeliver(Start, Stop);
- if FAILED(hr) then Exit;
- end;
- end;
- end else
- begin
- // sync version of above loop
- while (Current < AlignStop) do
- begin
- // Break out without calling EndOfStream if we're asked to
- // do something different
- if CheckRequest(@Request) then Exit;
- hr := FAlloc.GetBuffer(Sample, nil, nil, 0);
- if FAILED(hr) then
- begin
- OnError(hr);
- Exit;
- end;
- StopThis := Current + (Sample.GetSize * UNITS);
- if (StopThis > AlignStop) then StopThis := AlignStop;
- Sample.SetTime(@Current, @StopThis);
- Current := StopThis;
- if Discontinuity then
- begin
- Sample.SetDiscontinuity(True);
- Discontinuity := False;
- end;
- hr := FReader.SyncReadAligned(Sample);
- if FAILED(hr) then
- begin
- Sample := nil;
- OnError(hr);
- Exit;
- end;
- hr := DeliverSample(Sample, Start, Stop);
- if (hr <> S_OK) then
- begin
- if FAILED(hr) then OnError(hr);
- Exit;
- end;
- end;
- end;
- EndOfStream;
- end;
- procedure TBCPullPin.CleanupCancelled;
- var
- Sample: IMediaSample;
- Unused: DWORD;
- begin
- while True do
- begin
- FReader.WaitForNext(
- 0, // no wait
- Sample,
- Unused);
- if Assigned(Sample) then Sample := nil
- else Exit;
- end;
- end;
- function TBCPullPin.PauseThread: HRESULT;
- begin
- FAccessLock.Lock;
- try
- if not ThreadExists then
- begin
- Result := E_UNEXPECTED;
- Exit;
- end;
- // need to flush to ensure the thread is not blocked
- // in WaitForNext
- Result := FReader.BeginFlush;
- if FAILED(Result) then Exit;
- FState := TM_Pause;
- Result := CallWorker(Cardinal(TM_Pause));
- FReader.EndFlush;
- finally
- FAccessLock.UnLock;
- end;
- end;
- function TBCPullPin.StartThread: HRESULT;
- begin
- FAccessLock.Lock;
- try
- if not Assigned(FAlloc) or not Assigned(FReader) then
- begin
- Result := E_UNEXPECTED;
- Exit;
- end;
- if not ThreadExists then
- begin
- // commit allocator
- Result := FAlloc.Commit;
- if FAILED(Result) then Exit;
- // start thread
- if not Create_ then
- begin
- Result := E_FAIL;
- Exit;
- end;
- end;
- FState := TM_Start;
- Result := HRESULT(CallWorker(DWORD(FState)));
- finally
- FAccessLock.UnLock;
- end;
- end;
- function TBCPullPin.StopThread: HRESULT;
- begin
- FAccessLock.Lock;
- try
- if not ThreadExists then
- begin
- Result := S_FALSE;
- Exit;
- end;
- // need to flush to ensure the thread is not blocked
- // in WaitForNext
- Result := FReader.BeginFlush;
- if FAILED(Result) then Exit;
- FState := TM_Exit;
- Result := CallWorker(Cardinal(TM_Exit));
- FReader.EndFlush;
- // wait for thread to completely exit
- Close;
- // decommit allocator
- if Assigned(FAlloc) then FAlloc.Decommit;
- Result := S_OK;
- finally
- FAccessLock.UnLock;
- end;
- end;
- function TBCPullPin.QueueSample(var tCurrent: TReferenceTime; tAlignStop: TReferenceTime; bDiscontinuity: Boolean): HRESULT;
- var
- Sample: IMediaSample;
- StopThis: Int64;
- begin
- Result := FAlloc.GetBuffer(Sample, nil, nil, 0);
- if FAILED(Result) then Exit;
- StopThis := tCurrent + (Sample.GetSize * UNITS);
- if (StopThis > tAlignStop) then StopThis := tAlignStop;
- Sample.SetTime(@tCurrent, @StopThis);
- tCurrent := StopThis;
- Sample.SetDiscontinuity(bDiscontinuity);
- Result := FReader.Request(Sample,0);
- if FAILED(Result) then
- begin
- Sample := nil;
- CleanupCancelled;
- OnError(Result);
- end;
- end;
- function TBCPullPin.CollectAndDeliver(tStart,tStop: TReferenceTime): HRESULT;
- var
- Sample: IMediaSample;
- Unused: DWORD;
- begin
- Result := FReader.WaitForNext(INFINITE,Sample,Unused);
- if FAILED(Result) then
- begin
- if Assigned(Sample) then Sample := nil;
- end else
- begin
- Result := DeliverSample(Sample, tStart, tStop);
- end;
- if FAILED(Result) then
- begin
- CleanupCancelled;
- OnError(Result);
- end;
- end;
- function TBCPullPin.DeliverSample(pSample: IMediaSample; tStart,tStop: TReferenceTime): HRESULT;
- var
- t1, t2: TReferenceTime;
- begin
- // fix up sample if past actual stop (for sector alignment)
- pSample.GetTime(t1, t2);
- if (t2 > tStop) then t2 := tStop;
- // adjust times to be relative to (aligned) start time
- dec(t1,tStart);
- dec(t2,tStart);
- pSample.SetTime(@t1, @t2);
- Result := Receive(pSample);
- pSample := nil;
- end;
- function TBCPullPin.ThreadProc: DWord;
- var
- cmd: DWORD;
- begin
- Result := 1; // ???
- while True do
- begin
- cmd := GetRequest;
- case TThreadMsg(cmd) of
- TM_Exit:
- begin
- Reply(S_OK);
- Result := 0;
- Exit;
- end;
- TM_Pause:
- begin
- // we are paused already
- Reply(S_OK);
- break;
- end;
- TM_Start:
- begin
- Reply(S_OK);
- Process;
- break;
- end;
- end;
- // at this point, there should be no outstanding requests on the
- // upstream filter.
- // We should force begin/endflush to ensure that this is true.
- // !!!Note that we may currently be inside a BeginFlush/EndFlush pair
- // on another thread, but the premature EndFlush will do no harm now
- // that we are idle.
- FReader.BeginFlush;
- CleanupCancelled;
- FReader.EndFlush;
- end;
- end;
- // returns S_OK if successfully connected to an IAsyncReader interface
- // from this object
- // Optional allocator should be proposed as a preferred allocator if
- // necessary
- function TBCPullPin.Connect(pUnk: IUnknown; pAlloc: IMemAllocator; bSync: Boolean): HRESULT;
- var
- Total, Avail: Int64;
- begin
- FAccessLock.Lock;
- try
- if Assigned(FReader) then
- begin
- Result := VFW_E_ALREADY_CONNECTED;
- Exit;
- end;
- Result := pUnk.QueryInterface(IID_IAsyncReader, FReader);
- if FAILED(Result) then Exit;
- Result := DecideAllocator(pAlloc, nil);
- if FAILED(Result) then
- begin
- Disconnect;
- Exit;
- end;
- Result := FReader.Length(Total, Avail);
- if FAILED(Result) then
- begin
- Disconnect;
- Exit;
- end;
- // convert from file position to reference time
- FDuration := Total * UNITS;
- FStop := FDuration;
- FStart := 0;
- FSync := bSync;
- Result := S_OK;
- finally
- FAccessLock.UnLock;
- end;
- end;
- // disconnect any connection made in Connect
- function TBCPullPin.Disconnect: HRESULT;
- begin
- FAccessLock.Lock;
- try
- StopThread;
- if Assigned(FReader) then FReader := nil;
- if Assigned(FAlloc) then FAlloc := nil;
- Result := S_OK;
- finally
- FAccessLock.UnLock;
- end;
- end;
- // agree an allocator using RequestAllocator - optional
- // props param specifies your requirements (non-zero fields).
- // returns an error code if fail to match requirements.
- // optional IMemAllocator interface is offered as a preferred allocator
- // but no error occurs if it can't be met.
- function TBCPullPin.DecideAllocator(pAlloc: IMemAllocator; pProps: PAllocatorProperties): HRESULT;
- var
- pRequest: PAllocatorProperties;
- Request: TAllocatorProperties;
- begin
- if (pProps = nil) then
- begin
- Request.cBuffers := 3;
- Request.cbBuffer := 64*1024;
- Request.cbAlign := 0;
- Request.cbPrefix := 0;
- pRequest := @Request;
- end else
- begin
- pRequest := pProps;
- end;
- Result := FReader.RequestAllocator(pAlloc,pRequest,FAlloc);
- end;
- function TBCPullPin.Seek(tStart, tStop: TReferenceTime): HRESULT;
- var
- AtStart: TThreadMsg;
- begin
- FAccessLock.Lock;
- try
- AtStart := FState;
- if (AtStart = TM_Start) then
- begin
- BeginFlush;
- PauseThread;
- EndFlush;
- end;
- FStart := tStart;
- FStop := tStop;
- Result := S_OK;
- if (AtStart = TM_Start) then Result := StartThread;
- finally
- FAccessLock.UnLock;
- end;
- end;
- function TBCPullPin.Duration(out ptDuration: TReferenceTime): HRESULT;
- begin
- ptDuration := FDuration;
- Result := S_OK;
- end;
- // start pulling data
- function TBCPullPin.Active: HRESULT;
- begin
- ASSERT(not ThreadExists);
- Result := StartThread;
- end;
- // stop pulling data
- function TBCPullPin.Inactive: HRESULT;
- begin
- StopThread;
- Result := S_OK;
- end;
- function TBCPullPin.AlignDown(ll: Int64; lAlign: LongInt): Int64;
- begin
- Result := ll and not (lAlign-1);
- end;
- function TBCPullPin.AlignUp(ll: Int64; lAlign: LongInt): Int64;
- begin
- Result := (ll + (lAlign -1)) and not (lAlign -1);
- end;
- function TBCPullPin.GetReader: IAsyncReader;
- begin
- Result := FReader;
- end;
- // milenko end
- // milenko start reftime implementation
- procedure TBCRefTime.Create_;
- begin
- FTime := 0;
- end;
- procedure TBCRefTime.Create_(msecs: Longint);
- begin
- FTime := MILLISECONDS_TO_100NS_UNITS(msecs);
- end;
- function TBCRefTime.SetTime(var rt: TBCRefTime): TBCRefTime;
- begin
- FTime := rt.FTime;
- Result := Self;
- end;
- function TBCRefTime.SetTime(var ll: LONGLONG): TBCRefTime;
- begin
- FTime := ll;
- end;
- function TBCRefTime.AddTime(var rt: TBCRefTime): TBCRefTime;
- begin
- TReferenceTime(Self) := TReferenceTime(Self) + TReferenceTime(rt);
- Result := Self;
- end;
- function TBCRefTime.SubstractTime(var rt: TBCRefTime): TBCRefTime;
- begin
- TReferenceTime(Self) := TReferenceTime(Self) - TReferenceTime(rt);
- Result := Self;
- end;
- function TBCRefTime.Millisecs: Longint;
- begin
- Result := fTime div (UNITS div MILLISECONDS);
- end;
- function TBCRefTime.GetUnits: LONGLONG;
- begin
- Result := fTime;
- end;
- // milenko end
- // milenko start schedule implementation
- constructor TBCAdvisePacket.Create;
- begin
- inherited Create;
- end;
- constructor TBCAdvisePacket.Create(Next: TBCAdvisePacket; Time: LONGLONG);
- begin
- inherited Create;
- FNext := Next;
- FEventTime := Time;
- end;
- procedure TBCAdvisePacket.InsertAfter(Packet: TBCAdvisePacket);
- begin
- Packet.FNext := FNext;
- FNext := Packet;
- end;
- function TBCAdvisePacket.IsZ: Boolean;
- begin
- Result := FNext = nil;
- end;
- function TBCAdvisePacket.RemoveNext: TBCAdvisePacket;
- var
- Next,
- NewNext : TBCAdvisePacket;
- begin
- Next := FNext;
- NewNext := Next.FNext;
- FNext := NewNext;
- Result := Next;
- end;
- procedure TBCAdvisePacket.DeleteNext;
- begin
- RemoveNext.Free;
- end;
- function TBCAdvisePacket.Next: TBCAdvisePacket;
- begin
- Result := FNext;
- if Result.IsZ then Result := nil;
- end;
- function TBCAdvisePacket.Cookie: DWORD;
- begin
- Result := FAdviseCookie;
- end;
- constructor TBCAMSchedule.Create(Event: THandle);
- begin
- inherited Create('TBCAMSchedule');
- FZ := TBCAdvisePacket.Create(nil,MAX_TIME);
- FHead := TBCAdvisePacket.Create(FZ,0);
- FNextCookie := 0;
- FAdviseCount := 0;
- FAdviseCache := nil;
- FCacheCount := 0;
- FEvent := Event;
- FSerialize := TBCCritSec.Create;
- FZ.FAdviseCookie := 0;
- FHead.FAdviseCookie := FZ.FAdviseCookie;
- end;
- destructor TBCAMSchedule.Destroy;
- var
- p, p_next : TBCAdvisePacket;
- begin
- FSerialize.Lock;
- try
- // Delete cache
- p := FAdviseCache;
- while (p <> nil) do
- begin
- p_next := p.FNext;
- FreeAndNil(p);
- p := p_next;
- end;
- ASSERT(FAdviseCount = 0);
- // Better to be safe than sorry
- if (FAdviseCount > 0) then
- begin
- DumpLinkedList;
- while not FHead.FNext.IsZ do
- begin
- FHead.DeleteNext;
- dec(FAdviseCount);
- end;
- end;
- // If, in the debug version, we assert twice, it means, not only
- // did we have left over advises, but we have also let m_dwAdviseCount
- // get out of sync. with the number of advises actually on the list.
- ASSERT(FAdviseCount = 0);
- finally
- FSerialize.Unlock;
- end;
- FreeAndNil(FSerialize);
- inherited Destroy;
- end;
- function TBCAMSchedule.GetAdviseCount: DWORD;
- begin
- // No need to lock, m_dwAdviseCount is 32bits & declared volatile
- // DCODER: No volatile in Delphi -> needs a lock ?
- FSerialize.Lock;
- try
- Result := FAdviseCount;
- finally
- FSerialize.UnLock;
- end;
- end;
- function TBCAMSchedule.GetNextAdviseTime: TReferenceTime;
- begin
- FSerialize.Lock; // Need to stop the linked list from changing
- try
- Result := FHead.FNext.FEventTime;
- finally
- FSerialize.UnLock;
- end;
- end;
- function TBCAMSchedule.AddAdvisePacket(const time1, time2: TReferenceTime;
- h: THandle; periodic: Boolean): DWORD;
- var
- p : TBCAdvisePacket;
- begin
- // Since we use MAX_TIME as a sentry, we can't afford to
- // schedule a notification at MAX_TIME
- ASSERT(time1 < MAX_TIME);
- FSerialize.Lock;
- try
- if Assigned(FAdviseCache) then
- begin
- p := FAdviseCache;
- FAdviseCache := p.FNext;
- dec(FCacheCount);
- end else
- begin
- p := TBCAdvisePacket.Create;
- end;
- if Assigned(p) then
- begin
- p.FEventTime := time1;
- p.FPeriod := time2;
- p.FNotify := h;
- p.FPeriodic := periodic;
- Result := AddAdvisePacket(p);
- end else
- begin
- Result := 0;
- end;
- finally
- FSerialize.UnLock;
- end;
- end;
- function TBCAMSchedule.Unadvise(AdviseCookie: DWORD): HRESULT;
- var
- p_prev, p_n : TBCAdvisePacket;
- begin
- Result := S_FALSE;
- p_prev := FHead;
- FSerialize.Lock;
- try
- p_n := p_prev.Next;
- while Assigned(p_n) do // The Next() method returns NULL when it hits z
- begin
- if (p_n.FAdviseCookie = AdviseCookie) then
- begin
- Delete(p_prev.RemoveNext);
- dec(FAdviseCount);
- Result := S_OK;
- // Having found one cookie that matches, there should be no more
- {$IFDEF DEBUG}
- p_n := p_prev.Next;
- while Assigned(p_n) do
- begin
- ASSERT(p_n.FAdviseCookie <> AdviseCookie);
- p_prev := p_n;
- p_n := p_prev.Next;
- end;
- {$ENDIF}
- break;
- end;
- p_prev := p_n;
- p_n := p_prev.Next;
- end;
- finally
- FSerialize.UnLock;
- end;
- end;
- function TBCAMSchedule.Advise(const Time_: TReferenceTime): TReferenceTime;
- var
- NextTime : TReferenceTime;
- Advise : TBCAdvisePacket;
- begin
- {$IFDEF DEBUG}
- DbgLog(
- Self, 'TBCAMSchedule.Advise( ' +
- inttostr((Time_ div (UNITS div MILLISECONDS))) + ' ms '
- );
- {$ENDIF}
- FSerialize.Lock;
- try
- {$IFDEF DEBUG}
- DumpLinkedList;
- {$ENDIF}
- // Note - DON'T cache the difference, it might overflow
- Advise := FHead.FNext;
- NextTime := Advise.FEventTime;
- while ((Time_ >= NextTime) and not Advise.IsZ) do
- begin
- // DCODER: assert raised here
- ASSERT(Advise.FAdviseCookie > 0); // If this is zero, its the head or the tail!!
- ASSERT(Advise.FNotify <> INVALID_HANDLE_VALUE);
- if (Advise.FPeriodic = True) then
- begin
- ReleaseSemaphore(Advise.FNotify,1,nil);
- Advise.FEventTime := Advise.FEventTime + Advise.FPeriod;
- ShuntHead;
- end else
- begin
- ASSERT(Advise.FPeriodic = False);
- SetEvent(Advise.FNotify);
- dec(FAdviseCount);
- Delete(FHead.RemoveNext);
- end;
- Advise := FHead.FNext;
- NextTime := Advise.FEventTime;
- end;
- finally
- FSerialize.UnLock;
- end;
- {$IFDEF DEBUG}
- DbgLog(
- Self, 'TBCAMSchedule.Advise(Next time stamp: ' +
- inttostr((NextTime div (UNITS div MILLISECONDS))) +
- ' ms, for advise ' + inttostr(Advise.FAdviseCookie)
- );
- {$ENDIF}
- Result := NextTime;
- end;
- function TBCAMSchedule.GetEvent: THandle;
- begin
- Result := FEvent;
- end;
- procedure TBCAMSchedule.DumpLinkedList;
- {$IFDEF DEBUG}
- var
- i : integer;
- p : TBCAdvisePacket;
- {$ENDIF}
- begin
- {$IFDEF DEBUG}
- FSerialize.Lock;
- try
- DbgLog(Self,'TBCAMSchedule.DumpLinkedList');
- i := 0;
- p := FHead;
- while True do
- begin
- if p = nil then break;
- DbgLog(
- Self, 'Advise List # ' + inttostr(i) + ', Cookie ' +
- inttostr(p.FAdviseCookie) + ', RefTime ' +
- inttostr(p.FEventTime div (UNITS div MILLISECONDS))
- );
- inc(i);
- p := p.Next;
- end;
- finally
- FSerialize.Unlock;
- end;
- {$ENDIF}
- end;
- function TBCAMSchedule.AddAdvisePacket(Packet: TBCAdvisePacket): DWORD;
- var
- p_prev, p_n : TBCAdvisePacket;
- begin
- ASSERT((Packet.FEventTime >= 0) and (Packet.FEventTime < MAX_TIME));
- {$IFDEF DEBUG}
- ASSERT(FSerialize.CritCheckIn);
- {$ENDIF}
- p_prev := FHead;
- inc(FNextCookie);
- Packet.FAdviseCookie := FNextCookie;
- Result := Packet.FAdviseCookie;
- // This relies on the fact that z is a sentry with a maximal m_rtEventTime
- while True do
- begin
- p_n := p_prev.FNext;
- if (p_n.FEventTime >= Packet.FEventTime) then break;
- p_prev := p_n;
- end;
- p_prev.InsertAfter(Packet);
- inc(FAdviseCount);
- {$IFDEF DEBUG}
- DbgLog(
- Self, 'Added advise ' + inttostr(Packet.FAdviseCookie) + ', for thread ' +
- inttostr(GetCurrentThreadId) + ', scheduled at ' +
- inttostr(Packet.FEventTime div (UNITS div MILLISECONDS))
- );
- {$ENDIF}
- // If packet added at the head, then clock needs to re-evaluate wait time.
- if (p_prev = FHead) then SetEvent(FEvent);
- end;
- procedure TBCAMSchedule.ShuntHead;
- var
- p_prev, p_n : TBCAdvisePacket;
- Packet : TBCAdvisePacket;
- begin
- p_prev := FHead;
- p_n := nil;
- FSerialize.Lock;
- try
- Packet := FHead.FNext;
- // This will catch both an empty list,
- // and if somehow a MAX_TIME time gets into the list
- // (which would also break this method).
- ASSERT(Packet.FEventTime < MAX_TIME);
- // This relies on the fact that z is a sentry with a maximal m_rtEventTime
- while True do
- begin
- p_n := p_prev.FNext;
- if (p_n.FEventTime >= Packet.FEventTime) then break;
- p_prev := p_n;
- end;
- // If p_prev == pPacket then we're already in the right place
- if (p_prev <> Packet) then
- begin
- FHead.FNext := Packet.FNext;
- p_prev.FNext := Packet;
- p_prev.FNext.FNext := p_n;
- end;
- {$IFDEF DEBUG}
- DbgLog(
- Self, 'Periodic advise ' + inttostr(Packet.FAdviseCookie) + ', shunted to ' +
- inttostr(Packet.FEventTime div (UNITS div MILLISECONDS))
- );
- {$ENDIF}
- finally
- FSerialize.Unlock;
- end;
- end;
- procedure TBCAMSchedule.Delete(Packet: TBCAdvisePacket);
- const
- CacheMax = 5; // Don't bother caching more than five
- begin
- if (FCacheCount >= CacheMax) then FreeAndNil(Packet)
- else
- begin
- FSerialize.Lock;
- try
- Packet.FNext := FAdviseCache;
- FAdviseCache := Packet;
- inc(FCacheCount);
- finally
- FSerialize.Unlock;
- end;
- end;
- end;
- // milenko end
- // milenko start refclock implementation
- function AdviseThreadFunction(p: Pointer): DWORD; stdcall;
- begin
- Result := TBCBaseReferenceClock(p).AdviseThread;
- end;
- constructor TBCBaseReferenceClock.Create(Name: String; Unk: IUnknown; out hr: HRESULT;
- Sched: TBCAMSchedule);
- var
- tc : TIMECAPS;
- ThreadID : DWORD;
- begin
- inherited Create(Name,Unk);
- FLastGotTime := 0;
- FTimerResolution := 0;
- FAbort := False;
- if not Assigned(Sched)
- then FSchedule := TBCAMSchedule.Create(CreateEvent(nil,False,False,nil))
- else FSchedule := Sched;
- ASSERT(fSchedule <> nil);
- if not Assigned(FSchedule) then
- begin
- hr := E_OUTOFMEMORY;
- end else
- begin
- FLock := TBCCritSec.Create;
- // Set up the highest resolution timer we can manage
- if (timeGetDevCaps(@tc, sizeof(tc)) = TIMERR_NOERROR)
- then FTimerResolution := tc.wPeriodMin
- else FTimerResolution := 1;
- timeBeginPeriod(FTimerResolution);
- // Initialise our system times - the derived clock should set the right values
- FPrevSystemTime := timeGetTime;
- FPrivateTime := (UNITS div MILLISECONDS) * FPrevSystemTime;
- {$IFDEF PERF}
- FGetSystemTime := MSR_REGISTER('TBCBaseReferenceClock.GetTime');
- {$ENDIF}
- if not Assigned(Sched) then
- begin
- FThread := CreateThread(nil, // Security attributes
- 0, // Initial stack size
- @AdviseThreadFunction, // Thread start address
- Self, // Thread parameter
- 0, // Creation flags
- ThreadID); // Thread identifier
- if (FThread > 0) then
- begin
- SetThreadPriority(FThread, THREAD_PRIORITY_TIME_CRITICAL);
- end else
- begin
- hr := E_FAIL;
- CloseHandle(FSchedule.GetEvent);
- FreeAndNil(FSchedule);
- end;
- end;
- end;
- end;
- destructor TBCBaseReferenceClock.Destroy;
- begin
- if (FTimerResolution > 0) then
- begin
- timeEndPeriod(FTimerResolution);
- FTimerResolution := 0;
- end;
- FSchedule.DumpLinkedList;
- if (FThread > 0) then
- begin
- FAbort := True;
- TriggerThread;
- WaitForSingleObject(FThread, INFINITE);
- CloseHandle(FSchedule.GetEvent);
- FreeAndNil(FSchedule);
- end;
- if Assigned(FLock) then FreeAndNil(FLock);
- inherited Destroy;
- end;
- function TBCBaseReferenceClock.AdviseThread: HRESULT;
- var
- dwWait : DWORD;
- rtNow : TReferenceTime;
- llWait : LONGLONG;
- begin
- dwWait := INFINITE;
- // The first thing we do is wait until something interesting happens
- // (meaning a first advise or shutdown). This prevents us calling
- // GetPrivateTime immediately which is goodness as that is a virtual
- // routine and the derived class may not yet be constructed. (This
- // thread is created in the base class constructor.)
- while not FAbort do
- begin
- // Wait for an interesting event to happen
- {$IFDEF DEBUG}
- DbgLog(Self,'AdviseThread Delay: ' + inttostr(dwWait) + ' ms');
- {$ENDIF}
- WaitForSingleObject(FSchedule.GetEvent, dwWait);
- if FAbort then break;
- // There are several reasons why we need to work from the internal
- // time, mainly to do with what happens when time goes backwards.
- // Mainly, it stop us looping madly if an event is just about to
- // expire when the clock goes backward (i.e. GetTime stop for a
- // while).
- rtNow := GetPrivateTime;
- {$IFDEF DEBUG}
- DbgLog(
- Self,'AdviseThread Woke at = ' + inttostr(RefTimeToMiliSec(rtNow)) + ' ms'
- );
- {$ENDIF}
- // We must add in a millisecond, since this is the resolution of our
- // WaitForSingleObject timer. Failure to do so will cause us to loop
- // franticly for (approx) 1 a millisecond.
- FNextAdvise := FSchedule.Advise(10000 + rtNow);
- llWait := FNextAdvise - rtNow;
- ASSERT(llWait > 0);
- llWait := RefTimeToMiliSec(llWait);
- // DON'T replace this with a max!! (The type's of these things is VERY important)
- if (llWait > REFERENCE_TIME(HIGH(DWORD))) then dwWait := HIGH(DWORD)
- else dwWait := DWORD(llWait)
- end;
- Result := NOERROR;
- end;
- function TBCBaseReferenceClock.NonDelegatingQueryInterface(const IID: TGUID;
- out Obj): HResult; stdcall;
- begin
- if (IsEqualGUID(IID,IID_IReferenceClock)) then
- begin
- if GetInterface(IID,Obj) then Result := S_OK
- else Result := E_NOINTERFACE;
- end
- else
- Result := inherited NonDelegatingQueryInterface(IID, Obj);
- end;
- function TBCBaseReferenceClock.GetTime(out Time: int64): HResult; stdcall;
- var
- Now_ : TReferenceTime;
- begin
- if Assigned(@Time) then
- begin
- FLock.Lock;
- try
- Now_ := GetPrivateTime;
- if (Now_ > FLastGotTime) then
- begin
- FLastGotTime := Now_;
- Result := S_OK;
- end else
- begin
- Result := S_FALSE;
- end;
- Time := FLastGotTime;
- finally
- FLock.UnLock;
- end;
- {$IFDEF PERF}
- MSR_INTEGER(FGetSystemTime, Time div (UNITS div MILLISECONDS));
- {$ENDIF}
- end else Result := E_POINTER;
- end;
- function TBCBaseReferenceClock.AdviseTime(BaseTime, StreamTime: int64;
- Event: THandle; out AdviseCookie: DWORD): HResult; stdcall;
- var
- RefTime : TReferenceTime;
- begin
- if @AdviseCookie = nil then
- begin
- Result := E_POINTER;
- Exit;
- end;
- AdviseCookie := 0;
- // Check that the event is not already set
- ASSERT(WAIT_TIMEOUT = WaitForSingleObject(Event,0));
- RefTime := BaseTime + StreamTime;
- if ((RefTime <= 0) or (RefTime = MAX_TIME)) then
- begin
- Result := E_INVALIDARG;
- end else
- begin
- AdviseCookie := FSchedule.AddAdvisePacket(RefTime, 0, Event, False);
- if AdviseCookie > 0 then Result := NOERROR
- else Result := E_OUTOFMEMORY;
- end;
- end;
- function TBCBaseReferenceClock.AdvisePeriodic(const StartTime, PeriodTime: int64;
- Semaphore: THandle; out AdviseCookie: DWORD): HResult; stdcall;
- begin
- if @AdviseCookie = nil then
- begin
- Result := E_POINTER;
- Exit;
- end;
- AdviseCookie := 0;
- if ((StartTime > 0) and (PeriodTime > 0) and (StartTime <> MAX_TIME)) then
- begin
- AdviseCookie := FSchedule.AddAdvisePacket(StartTime,PeriodTime,Semaphore,True);
- if AdviseCookie > 0 then Result := NOERROR
- else Result := E_OUTOFMEMORY;
- end
- else Result := E_INVALIDARG;
- end;
- function TBCBaseReferenceClock.Unadvise(AdviseCookie: DWORD): HResult; stdcall;
- begin
- Result := FSchedule.Unadvise(AdviseCookie);
- end;
- function TBCBaseReferenceClock.GetPrivateTime: TReferenceTime;
- var
- Time_ : DWORD;
- begin
- FLock.Lock;
- try
- (* If the clock has wrapped then the current time will be less than
- * the last time we were notified so add on the extra milliseconds
- *
- * The time period is long enough so that the likelihood of
- * successive calls spanning the clock cycle is not considered.
- *)
- Time_ := timeGetTime;
- FPrivateTime := FPrivateTime + Int32x32To64(UNITS div MILLISECONDS, DWORD(Time_ - FPrevSystemTime));
- FPrevSystemTime := Time_;
- finally
- FLock.UnLock;
- end;
- Result := FPrivateTime;
- end;
- function TBCBaseReferenceClock.SetTimeDelta(const TimeDelta: TReferenceTime): HRESULT; stdcall;
- {$IFDEF DEBUG}
- var
- llDelta : LONGLONG;
- usDelta : Longint;
- delta : DWORD;
- Severity : integer;
- {$ENDIF}
- begin
- {$IFDEF DEBUG}
- // Just break if passed an improper time delta value
- if TimeDelta > 0 then llDelta := TimeDelta
- else llDelta := -TimeDelta;
- if (llDelta > UNITS * 1000) then
- begin
- DbgLog(Self,'Bad Time Delta');
- // DebugBreak;
- end;
- // We're going to calculate a "severity" for the time change. Max -1
- // min 8. We'll then use this as the debug logging level for a
- // debug log message.
- usDelta := Longint(TimeDelta div 10); // Delta in micro-secs
- delta := abs(usDelta); // varying delta
- // Severity == 8 - ceil(log<base 8>(abs( micro-secs delta)))
- Severity := 8;
- while (delta > 0) do
- begin
- delta := delta shr 3; // div 8
- dec(Severity);
- end;
- // Sev == 0 => > 2 second delta!
- DbgLog(
- Self, 'Sev ' + inttostr(Severity) + ': CSystemClock::SetTimeDelta(' +
- inttostr(usDelta) + ' us) ' + inttostr(RefTimeToMiliSec(FPrivateTime)) +
- ' -> ' + inttostr(RefTimeToMiliSec(TimeDelta + FPrivateTime)) + ' ms'
- );
- {$ENDIF}
- FLock.Lock;
- try
- FPrivateTime := FPrivateTime + TimeDelta;
- // If time goes forwards, and we have advises, then we need to
- // trigger the thread so that it can re-evaluate its wait time.
- // Since we don't want the cost of the thread switches if the change
- // is really small, only do it if clock goes forward by more than
- // 0.5 millisecond. If the time goes backwards, the thread will
- // wake up "early" (relativly speaking) and will re-evaluate at
- // that time.
- if ((TimeDelta > 5000) and (FSchedule.GetAdviseCount > 0)) then TriggerThread;
- finally
- FLock.UnLock;
- end;
- Result := NOERROR;
- end;
- function TBCBaseReferenceClock.GetSchedule : TBCAMSchedule;
- begin
- Result := FSchedule;
- end;
- procedure TBCBaseReferenceClock.TriggerThread;
- begin
- {$IFDEF DEBUG}
- DbgLog(Self,'TriggerThread : ' + inttostr(FSchedule.GetEvent));
- {$ENDIF}
- SetEvent(FSchedule.GetEvent);
- end;
- // milenko end
- // milenko start sysclock implementation
- constructor TBCSystemClock.Create(Name: WideString; Unk : IUnknown; out hr : HRESULT);
- begin
- inherited Create(Name,Unk,hr);
- end;
- function TBCSystemClock.NonDelegatingQueryInterface(const IID: TGUID; out Obj): HResult;
- begin
- if IsEqualGUID(IID,IID_IPersist) then
- begin
- if GetInterface(IID,Obj) then Result := S_OK
- else Result := E_NOINTERFACE;
- end else
- if IsEqualGUID(IID,IID_IAMClockAdjust) then
- begin
- if GetInterface(IID,Obj) then Result := S_OK
- else Result := E_NOINTERFACE;
- end
- else Result := inherited NonDelegatingQueryInterface(IID,Obj);
- end;
- function TBCSystemClock.GetClassID(out classID: TCLSID): HResult; stdcall;
- begin
- if not Assigned(@ClassID) then
- begin
- Result := E_POINTER;
- Exit;
- end;
- classID := CLSID_SystemClock;
- Result := NOERROR;
- end;
- function TBCSystemClock.SetClockDelta(rtDelta: TReferenceTime): HResult; stdcall;
- begin
- Result := SetTimeDelta(rtDelta);
- end;
- // milenko end
- initialization
- {$IFDEF DEBUG}
- {$IFDEF VER130}
- AssertErrorProc := @DbgAssert;
- {$ELSE}
- AssertErrorProc := DbgAssert;
- {$ENDIF}
- {$IFNDEF MESSAGE}
- AssignFile(DebugFile, ParamStr(0) + '.log');
- if FileExists(ParamStr(0) + '.log') then
- Append(DebugFile) else
- Rewrite(DebugFile);
- {$ENDIF}
- {$ENDIF}
- finalization
- begin
- if TemplatesVar <> nil then TemplatesVar.Free;
- TemplatesVar := nil;
- {$IFDEF DEBUG}
- {$IFNDEF MESSAGE}
- Writeln(DebugFile, format('FactoryCount: %d, ObjectCount: %d.',[FactoryCount, ObjectCount]));
- CloseFile(DebugFile);
- {$ELSE}
- OutputDebugString(PChar(format('FactoryCount: %d, ObjectCount: %d.',[FactoryCount, ObjectCount])));
- {$ENDIF}
- {$ENDIF}
- // milenko start (only needed with PERF)
- {$IFDEF PERF}
- SetLength(Incidents, 0);
- SetLength(IncidentsLog, 0);
- {$ENDIF}
- // milenko end
- end;
- end.
|