framework2
779 строк · 21.0 Кб
1//
2// ofAVFoundationPlayer.mm
3// Created by Lukasz Karluk on 06/07/14.
4// Merged with code by Sam Kronick, James George and Elie Zananiri.
5//
6
7//--------------------------------------------------------------
8#import "ofAVFoundationPlayer.h"
9#import "ofAVFoundationVideoPlayer.h"
10#include "ofRectangle.h"
11#include "ofGLUtils.h"
12
13#ifdef TARGET_OSX
14#include "ofTexture.h"
15#endif
16
17//--------------------------------------------------------------
18ofAVFoundationPlayer::ofAVFoundationPlayer() {
19videoPlayer = nullptr;
20pixelFormat = OF_PIXELS_RGBA;
21
22bFrameNew = false;
23bResetPixels = false;
24bUpdatePixels = false;
25bUpdateTexture = false;
26bUseTextureCache = true;
27}
28
29//--------------------------------------------------------------
30ofAVFoundationPlayer::~ofAVFoundationPlayer() {
31disposePlayer();
32}
33
34//--------------------------------------------------------------
35ofAVFoundationPlayer& ofAVFoundationPlayer::operator=(ofAVFoundationPlayer other)
36{
37// clear pixels
38pixels.clear();
39videoTexture.clear();
40
41// get rid of the textures
42killTextureCache();
43
44bFrameNew = false;
45bResetPixels = false;
46bUpdatePixels = false;
47bUpdateTexture = false;
48bUseTextureCache = true;
49
50std::swap(videoPlayer, other.videoPlayer);
51return *this;
52}
53
54//--------------------------------------------------------------
55void ofAVFoundationPlayer::loadAsync(std::string name){
56loadPlayer(name, true);
57}
58
59//--------------------------------------------------------------
60bool ofAVFoundationPlayer::load(std::string name) {
61return loadPlayer(name, false);
62}
63
64//--------------------------------------------------------------
65bool ofAVFoundationPlayer::loadPlayer(std::string name, bool bAsync) {
66if( ofGetUsingArbTex() == false ){
67killTextureCache();
68bUseTextureCache = false;
69}
70
71NSString * videoPath = [NSString stringWithUTF8String:name.c_str()];
72NSString * videoLocalPath = [NSString stringWithUTF8String:ofToDataPath(name).c_str()];
73
74BOOL bStream = NO;
75
76bStream = bStream || (ofIsStringInString(name, "http://"));
77bStream = bStream || (ofIsStringInString(name, "https://"));
78bStream = bStream || (ofIsStringInString(name, "rtsp://"));
79
80NSURL * url = nil;
81if(bStream == YES) {
82url = [NSURL URLWithString:videoPath];
83} else {
84url = [NSURL fileURLWithPath:videoLocalPath];
85}
86
87bFrameNew = false;
88bResetPixels = true;
89bUpdatePixels = true;
90bUpdateTexture = true;
91
92bool bLoaded = false;
93
94if(videoPlayer == nullptr) {
95// create a new player if its not allocated
96videoPlayer = [[ofAVFoundationVideoPlayer alloc] init];
97[videoPlayer setWillBeUpdatedExternally:YES];
98}
99
100bLoaded = [videoPlayer loadWithURL:url async:bAsync stream:bStream];
101
102pixels.clear();
103videoTexture.clear();
104
105bool bCreateTextureCache = bLoaded && bUseTextureCache && (_videoTextureCache == nullptr);
106
107if(bCreateTextureCache == true) {
108
109CVReturn err;
110
111#if defined(TARGET_OF_IOS) && defined(__IPHONE_6_0)
112err = CVOpenGLESTextureCacheCreate(kCFAllocatorDefault,
113nullptr,
114[EAGLContext currentContext],
115nullptr,
116&_videoTextureCache);
117#endif
118
119#if defined(TARGET_OF_IOS) && !defined(__IPHONE_6_0)
120err = CVOpenGLESTextureCacheCreate(kCFAllocatorDefault,
121nullptr,
122(__bridge void *)[EAGLContext currentContext],
123nullptr,
124&_videoTextureCache);
125#endif
126
127#ifdef TARGET_OSX
128err = CVOpenGLTextureCacheCreate(kCFAllocatorDefault,
129nullptr,
130CGLGetCurrentContext(),
131CGLGetPixelFormat(CGLGetCurrentContext()),
132nullptr,
133&_videoTextureCache);
134#endif
135
136if(err) {
137ofLogWarning("ofAVFoundationPlayer") << "load(): error when creating texture cache, " << err << ".";
138}
139}
140
141
142if( bAsync == false && bLoaded ){
143pixels.allocate(getWidth(), getHeight(), getPixelFormat());
144}
145
146return bLoaded;
147}
148
149//--------------------------------------------------------------
150void ofAVFoundationPlayer::disposePlayer() {
151
152if (videoPlayer != nullptr) {
153
154// clear pixels
155pixels.clear();
156videoTexture.clear();
157
158// dispose videoplayer
159__block ofAVFoundationVideoPlayer *currentPlayer = videoPlayer;
160dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
161@autoreleasepool {
162[currentPlayer unloadVideo]; // synchronious call to unload video
163}
164});
165
166videoPlayer = nullptr;
167}
168
169// get rid of the textures
170killTextureCache();
171
172
173bFrameNew = false;
174bResetPixels = false;
175bUpdatePixels = false;
176bUpdateTexture = false;
177bUseTextureCache = true;
178}
179
180//--------------------------------------------------------------
181void ofAVFoundationPlayer::close() {
182if(videoPlayer != nullptr) {
183
184pixels.clear();
185videoTexture.clear();
186
187[videoPlayer close];
188}
189
190bFrameNew = false;
191bResetPixels = false;
192bUpdatePixels = false;
193bUpdateTexture = false;
194bUseTextureCache = true;
195}
196
197//--------------------------------------------------------------
198bool ofAVFoundationPlayer::setPixelFormat(ofPixelFormat value) {
199bool bValid = false;
200bValid = bValid || (value == OF_PIXELS_RGB);
201bValid = bValid || (value == OF_PIXELS_RGBA);
202
203if(bValid == false) {
204ofLogWarning("ofAVFoundationPlayer") << "setPixelFormat(): unsupported ofPixelFormat, "
205<< ofToString(value) << ".";
206return false;
207}
208
209if(pixelFormat == value) {
210return true;
211}
212
213pixelFormat = value;
214bResetPixels = true;
215
216return true;
217}
218
219//--------------------------------------------------------------
220ofPixelFormat ofAVFoundationPlayer::getPixelFormat() const{
221return pixelFormat;
222}
223
224//--------------------------------------------------------------
225void ofAVFoundationPlayer::update() {
226
227bFrameNew = false; // default.
228
229if(!isLoaded() || !isReady()) {
230return;
231}
232
233[videoPlayer update];
234bFrameNew = [videoPlayer isNewFrame]; // check for new frame staright after the call to update.
235
236if(bFrameNew) {
237/**
238* mark pixels to be updated.
239* pixels are then only updated if the getPixels() method is called,
240* internally or externally to this class.
241* this ensures the pixels are updated only once per frame.
242*/
243bUpdatePixels = true;
244bUpdateTexture = true;
245}
246}
247
248//--------------------------------------------------------------
249void ofAVFoundationPlayer::draw() {
250draw(0, 0);
251}
252
253void ofAVFoundationPlayer::draw(float x, float y) {
254draw(x, y, getWidth(), getHeight());
255}
256
257void ofAVFoundationPlayer::draw(const ofRectangle & rect) {
258draw(rect.x, rect.y, rect.width, rect.height);
259}
260
261void ofAVFoundationPlayer::draw(float x, float y, float w, float h) {
262if(isLoaded() && isReady()) {
263
264ofTexture * texturePtr = getTexturePtr();
265if( texturePtr != NULL ){
266if( texturePtr->isAllocated() ){
267texturePtr->draw(x, y, w, h);
268}
269}
270}
271}
272
273//--------------------------------------------------------------
274void ofAVFoundationPlayer::play() {
275if(videoPlayer == nullptr) {
276ofLogWarning("ofAVFoundationPlayer") << "play(): video not loaded.";
277}
278
279[videoPlayer play];
280}
281
282//--------------------------------------------------------------
283void ofAVFoundationPlayer::stop() {
284if(videoPlayer == nullptr) {
285return;
286}
287
288[videoPlayer stop];
289}
290
291//--------------------------------------------------------------
292bool ofAVFoundationPlayer::isFrameNew() const {
293if(videoPlayer != nullptr) {
294return bFrameNew;
295}
296return false;
297}
298
299//--------------------------------------------------------------
300const ofPixels & ofAVFoundationPlayer::getPixels() const {
301return const_cast<ofAVFoundationPlayer *>(this)->getPixels();
302}
303
304ofPixels & ofAVFoundationPlayer::getPixels() {
305if(isLoaded() == false || pixels.size() == 0) {
306ofLogError("ofAVFoundationPlayer") << "getPixels(): Returning pixels that may be unallocated. Make sure to initialize the video player before calling getPixels.";
307return pixels;
308}
309
310if(bUpdatePixels == false) {
311// if pixels have not changed,
312// return the already calculated pixels.
313return pixels;
314}
315
316if(bResetPixels == true) {
317pixels.allocate(getWidth(), getHeight(), pixelFormat);
318bResetPixels = false;
319}
320
321CVImageBufferRef imageBuffer = [videoPlayer getCurrentFrame];
322
323CVPixelBufferLockBaseAddress(imageBuffer, kCVPixelBufferLock_ReadOnly);
324
325unsigned long imageBufferPixelFormat = CVPixelBufferGetPixelFormatType(imageBuffer);
326
327vImage_Buffer src = {
328CVPixelBufferGetBaseAddress(imageBuffer),
329CVPixelBufferGetHeight(imageBuffer),
330CVPixelBufferGetWidth(imageBuffer),
331CVPixelBufferGetBytesPerRow(imageBuffer)
332};
333
334vImage_Buffer dest = {
335pixels.getData(),
336static_cast<vImagePixelCount>(pixels.getHeight()),
337static_cast<vImagePixelCount>(pixels.getWidth()),
338static_cast<size_t>(pixels.getWidth() * pixels.getNumChannels())
339};
340
341vImage_Error err = kvImageNoError;
342
343if(pixelFormat == OF_PIXELS_RGBA) {
344
345if(imageBufferPixelFormat == kCVPixelFormatType_32ARGB) {
346
347uint8_t permuteMap[4] = { 1, 2, 3, 0 };
348err = vImagePermuteChannels_ARGB8888(&src, &dest, permuteMap, 0);
349
350} else if(imageBufferPixelFormat == kCVPixelFormatType_32BGRA) {
351
352uint8_t permuteMap[4] = { 2, 1, 0, 3 };
353err = vImagePermuteChannels_ARGB8888(&src, &dest, permuteMap, 0);
354}
355
356} else if(pixelFormat == OF_PIXELS_RGB) {
357
358if(imageBufferPixelFormat == kCVPixelFormatType_32ARGB) {
359
360err = vImageConvert_ARGB8888toRGB888(&src, &dest, 0);
361
362} else if(imageBufferPixelFormat == kCVPixelFormatType_32BGRA) {
363
364#ifdef __IPHONE_6_0
365err = vImageConvert_BGRA8888toRGB888(&src, &dest, 0);
366#else
367ofLogError("ofAVFoundationPlayer") << "getPixels(): OF_PIXELS_RGB is not supported, use setPixelFormat() to set the pixel format to OF_PIXELS_RGBA.";
368#endif
369}
370}
371
372CVPixelBufferUnlockBaseAddress(imageBuffer, kCVPixelBufferLock_ReadOnly);
373
374if(err != kvImageNoError) {
375ofLogError("ofAVFoundationPlayer") << "getPixels(): error in pixel copy, vImage_error = " << err << ".";
376}
377
378bUpdatePixels = false;
379
380return pixels;
381}
382
383//--------------------------------------------------------------
384ofTexture * ofAVFoundationPlayer::getTexturePtr() {
385
386if( bUseTextureCache == false ){
387return NULL;
388}
389
390if(isLoaded() == false || isReady() == false) {
391return &videoTexture;
392}
393
394if(bUpdateTexture == false) {
395return &videoTexture;
396}
397
398initTextureCache();
399
400bUpdateTexture = false;
401
402return &videoTexture;
403}
404
405//-------------------------------------------------------------- texture cache
406void ofAVFoundationPlayer::initTextureCache() {
407//just in case - we return here if we shouldn't be using a texture cache
408if( bUseTextureCache == false ){
409return;
410}
411
412CVImageBufferRef imageBuffer = [videoPlayer getCurrentFrame];
413if(imageBuffer == nil) {
414return;
415}
416
417CVPixelBufferLockBaseAddress(imageBuffer, kCVPixelBufferLock_ReadOnly);
418
419/**
420* video texture cache is available.
421* this means we don't have to copy any pixels,
422* and we can reuse the already existing video texture.
423* this is very fast! :)
424*/
425
426/**
427* CVOpenGLESTextureCache does this operation for us.
428* it automatically returns a texture reference which means we don't have to create the texture ourselves.
429* this creates a slight problem because when we create an ofTexture objects, it also creates a opengl texture for us,
430* which is unecessary in this case because the texture already exists.
431* so... we can use ofTexture::setUseExternalTextureID() to get around this.
432*/
433
434if (!videoTexture.isAllocated()) {
435int videoTextureW = getWidth();
436int videoTextureH = getHeight();
437videoTexture.allocate(videoTextureW, videoTextureH, GL_RGBA);
438
439ofTextureData & texData = videoTexture.getTextureData();
440texData.tex_t = 1.0f; // these values need to be reset to 1.0 to work properly.
441texData.tex_u = 1.0f; // assuming this is something to do with the way ios creates the texture cache.
442videoTexture.setTextureMinMagFilter(GL_LINEAR, GL_LINEAR);
443videoTexture.setTextureWrap(GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE);
444}
445
446CVReturn err;
447unsigned int textureCacheID;
448
449#ifdef TARGET_OF_IOS
450
451/**
452* create video texture from video image.
453* inside this function, ios is creating the texture for us.
454* a video texture reference is returned.
455*/
456err = CVOpenGLESTextureCacheCreateTextureFromImage(kCFAllocatorDefault, // CFAllocatorRef allocator
457_videoTextureCache, // CVOpenGLESTextureCacheRef textureCache
458imageBuffer, // CVImageBufferRef sourceImage
459nullptr, // CFDictionaryRef textureAttributes
460texData.textureTarget, // GLenum target
461texData.glInternalFormat, // GLint internalFormat
462texData.width, // GLsizei width
463texData.height, // GLsizei height
464GL_BGRA, // GLenum format
465GL_UNSIGNED_BYTE, // GLenum type
4660, // size_t planeIndex
467&_videoTextureRef); // CVOpenGLESTextureRef *textureOut
468
469textureCacheID = CVOpenGLESTextureGetName(_videoTextureRef);
470
471#endif
472
473#ifdef TARGET_OSX
474
475err = CVOpenGLTextureCacheCreateTextureFromImage(nullptr,
476_videoTextureCache,
477imageBuffer,
478nullptr,
479&_videoTextureRef);
480
481textureCacheID = CVOpenGLTextureGetName(_videoTextureRef);
482
483#endif
484
485videoTexture.setUseExternalTextureID(textureCacheID);
486if(ofIsGLProgrammableRenderer() == false) {
487videoTexture.bind();
488glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
489videoTexture.unbind();
490}
491
492if(err) {
493ofLogError("ofAVFoundationPlayer") << "initTextureCache(): error creating texture cache from image " << err << ".";
494}
495
496CVPixelBufferUnlockBaseAddress(imageBuffer, kCVPixelBufferLock_ReadOnly);
497
498#ifdef TARGET_OF_IOS
499CVOpenGLESTextureCacheFlush(_videoTextureCache, 0);
500#endif
501
502#ifdef TARGET_OSX
503CVOpenGLTextureCacheFlush(_videoTextureCache, 0);
504#endif
505killTexture();
506}
507
508void ofAVFoundationPlayer::killTexture() {
509#ifdef TARGET_OF_IOS
510if(_videoTextureRef) {
511CFRelease(_videoTextureRef);
512_videoTextureRef = nullptr;
513}
514#elif defined TARGET_OSX
515if (_videoTextureRef != nullptr) {
516CVOpenGLTextureRelease(_videoTextureRef);
517_videoTextureRef = nullptr;
518}
519#endif
520}
521
522void ofAVFoundationPlayer::killTextureCache() {
523
524killTexture();
525
526#ifdef TARGET_OF_IOS
527if(_videoTextureCache) {
528CFRelease(_videoTextureCache);
529_videoTextureCache = nullptr;
530}
531#endif
532
533#ifdef TARGET_OSX
534if(_videoTextureCache != nullptr) {
535CVOpenGLTextureCacheRelease(_videoTextureCache);
536_videoTextureCache = nullptr;
537}
538
539#endif
540}
541
542//--------------------------------------------------------------
543float ofAVFoundationPlayer::getWidth() const {
544if(videoPlayer == nullptr) {
545return 0;
546}
547
548return [videoPlayer getWidth];
549}
550
551//--------------------------------------------------------------
552float ofAVFoundationPlayer::getHeight() const {
553if(videoPlayer == nullptr) {
554return 0;
555}
556
557return [videoPlayer getHeight];
558}
559
560//--------------------------------------------------------------
561bool ofAVFoundationPlayer::isPaused() const {
562if(videoPlayer == nullptr) {
563return false;
564}
565
566return [videoPlayer isPaused];
567}
568
569//--------------------------------------------------------------
570bool ofAVFoundationPlayer::isLoaded() const {
571if(videoPlayer == nullptr) {
572return false;
573}
574
575return [videoPlayer isLoaded];
576}
577
578//--------------------------------------------------------------
579bool ofAVFoundationPlayer::isReady() const {
580if(videoPlayer == nullptr) {
581return false;
582}
583
584return [videoPlayer isReady];
585}
586
587//--------------------------------------------------------------
588bool ofAVFoundationPlayer::isPlaying() const {
589if(videoPlayer == nullptr) {
590return false;
591}
592
593return [videoPlayer isPlaying];
594}
595
596//--------------------------------------------------------------
597float ofAVFoundationPlayer::getPosition() const {
598if(videoPlayer == nullptr) {
599return 0;
600}
601
602return [videoPlayer getPosition];
603}
604
605//--------------------------------------------------------------
606float ofAVFoundationPlayer::getSpeed() const {
607if(videoPlayer == nullptr) {
608return 0;
609}
610
611return [videoPlayer getSpeed];
612}
613
614//--------------------------------------------------------------
615float ofAVFoundationPlayer::getDuration() const {
616if(videoPlayer == nullptr) {
617return 0;
618}
619
620return [videoPlayer getDurationInSec];
621}
622
623//--------------------------------------------------------------
624bool ofAVFoundationPlayer::getIsMovieDone() const {
625if(videoPlayer == nullptr) {
626return false;
627}
628
629return [videoPlayer isFinished];
630}
631
632//--------------------------------------------------------------
633void ofAVFoundationPlayer::setPaused(bool bPause) {
634if(videoPlayer == nullptr) {
635return;
636}
637
638if(bPause) {
639[videoPlayer pause];
640} else {
641[videoPlayer play];
642}
643}
644
645//--------------------------------------------------------------
646void ofAVFoundationPlayer::setPosition(float pct) {
647if(videoPlayer == nullptr) {
648return;
649}
650
651[videoPlayer setPosition:pct];
652}
653
654//--------------------------------------------------------------
655void ofAVFoundationPlayer::setVolume(float volume) {
656if(videoPlayer == nullptr) {
657return;
658}
659if(volume > 1.0) {
660ofLogWarning("ofAVFoundationPlayer") << "setVolume(): expected range is 0-1, limiting requested volume " << volume << " to 1.0.";
661volume = 1.0;
662}
663[videoPlayer setVolume:volume];
664}
665
666//--------------------------------------------------------------
667void ofAVFoundationPlayer::setLoopState(ofLoopType state) {
668if(videoPlayer == nullptr) {
669return;
670}
671
672[videoPlayer setLoop:(playerLoopType)state];
673}
674
675//--------------------------------------------------------------
676void ofAVFoundationPlayer::setSpeed(float speed) {
677if(videoPlayer == nullptr) {
678return;
679}
680
681[videoPlayer setSpeed:speed];
682}
683
684//--------------------------------------------------------------
685void ofAVFoundationPlayer::setFrame(int frame) {
686if(videoPlayer == nullptr) {
687return;
688}
689
690[videoPlayer setFrame:frame];
691}
692
693//--------------------------------------------------------------
694int ofAVFoundationPlayer::getCurrentFrame() const {
695if(videoPlayer == nullptr){
696return 0;
697}
698return [videoPlayer getCurrentFrameNum];
699}
700
701//--------------------------------------------------------------
702int ofAVFoundationPlayer::getTotalNumFrames() const {
703if(videoPlayer == nullptr){
704return 0;
705}
706return [videoPlayer getDurationInFrames];
707}
708
709//--------------------------------------------------------------
710ofLoopType ofAVFoundationPlayer::getLoopState() const {
711if(videoPlayer == nullptr) {
712return OF_LOOP_NONE;
713}
714
715bool bLoop = [videoPlayer getLoop];
716if(bLoop) {
717return OF_LOOP_NORMAL;
718}
719return OF_LOOP_NONE;
720}
721
722//--------------------------------------------------------------
723void ofAVFoundationPlayer::firstFrame() {
724if(videoPlayer == nullptr) {
725return;
726}
727
728[videoPlayer setPosition:0];
729}
730
731//--------------------------------------------------------------
732void ofAVFoundationPlayer::nextFrame() {
733if(videoPlayer == nullptr) {
734return;
735}
736
737[videoPlayer stepByCount:1];
738}
739
740//--------------------------------------------------------------
741void ofAVFoundationPlayer::previousFrame() {
742if(videoPlayer == nullptr) {
743return;
744}
745
746[videoPlayer stepByCount:-1];
747}
748
749//--------------------------------------------------------------
750#ifdef __OBJC__
751
752ofAVFoundationVideoPlayer * ofAVFoundationPlayer::getAVFoundationVideoPlayer() {
753return videoPlayer;
754}
755
756#else
757
758void * ofAVFoundationPlayer::getAVFoundationVideoPlayer() {
759return videoPlayer;
760}
761
762#endif
763
764//-------------------------------------------------------------- DEPRECATED.
765bool ofAVFoundationPlayer::loadMovie(std::string name) {
766return load(name);
767}
768
769ofPixels & ofAVFoundationPlayer::getPixelsRef() {
770return getPixels();
771}
772
773const ofPixels & ofAVFoundationPlayer::getPixelsRef() const {
774return getPixels();
775}
776
777ofTexture * ofAVFoundationPlayer::getTexture() {
778return getTexturePtr();
779}
780