QTMovieView & hanging threads

Hi,

sorry, this might be a bit off topic considering the Forum's name (AVFoundation), but I don't see where else I should post.

Here's my problem: I've to play say 200 short movies one after the other. For the purpose, I use QTMovie and QTMovieView from the QTKit framework, which I instatiate and delete when I'm done, for each movie. That works just fine up to the first 55 movies, then the application crashes (and not always at the same point in the code). I don't understand the cause of the crash, but I've investigated a bit the situation and I've seen some weird things which might be related to it:


- At each new movie, 3 threads are added, but then, when the movie ends and I release the resources, only one disappear, so that the number of threads related to my application grows linearly with the number of movies played.


- The applications crashes always at the same amount of allocated threads.


- The 2 threads that are never terminated are marked QTCALayerRenderPendingQWorkLoop and QTVisualContextImageProviderWorkLoop and are started with the instruction [view setMovie: movie] is executed.


I don't see any reason why those threads keep hanging around when I've released all the resources related to a movie. Does anybody knows how I can terminate them?


Here's below is the relevant code:


// Movie creation (Open function)

window = [[NSWindow alloc] autorelease];

view = [[[QTMovieView alloc] initWithFrame: frame autorelease];

[view setControllerVisible:NO];

[view setMovie: movie];

[[window contentView] addSubview: view];


....


// Movie deletion (Close function)


movie = nil;

window = nil;

view = nil;


Many thanks in advance for you help.

Cheers

Accepted Reply

Problem fixed!


Actually, the QTMovie is properly allocated and initialised but, due to what I believe is a bug in XCode, every time I inspected the relative pointer value in debugging, it was reported as NIL and therefore my considerations were totally misled. What failed was instead another instruction which is executed later in the code:


window = [[NSWindow alloc] initWithWindowRef: windowRef];


The error here is to not perform a CFRetain(windowRef) and later, at movie played, releasing the window pointer. In fact the doc states:


"For historical reasons, contrary to normal memory management policy

initWithWindowRef:
does not retain
windowRef
. It is therefore recommended that you make sure you retain
windowRef
before calling this method. If
windowRef
is still valid when the Cocoa window is deallocated, the Cocoa window will release it"


So now, with the movie code execution encapsulated within an autorelease pool block, everything works just fine.

I apologize for the wrong description of the problem, but I got myself a wrong hint.


Thanks for your contribution which have been inspiring and explanatory.

Luca

Replies

Are you properly (auto-) releasing the QTMovie instance somewhere? Not releasing a QTMovie instance prevents the exact two threads you mentioned from being properly terminated.

Hint: Looks like non-ARC code, so setting movie = nil may not do what you expect it to do... (?)


\nhb

FWIW, all of QTKit with the exception of QTMovieModernizer was deprecated in macOS 10.9, which was 5 or so years ago.


The practical consequence is that if there's any regression in QTKit in terms of performance or functionality (or bugs), the chance that Apple would do anything about it is approximately 0%.


Is there any reason why you can't switch to AVFoundation? There should be something that translates pretty directly from your older QT code.

Sure, I know that QTKit has been deprecated long time ago. Unfortunately the application I'm working on is a very old 32-bit application.

It should be rewritten entirely from scratch, but there's not time nor the resources to do that.


Actually, I tried to use AVFoundation initially but it didn't work as it relies on some lower level 64-bit libraries, for what I understand...


Thanks

I've checked some Apple sample code before implementing my solution and when a movie it's deleted it does nothing special but a release, that's why I'm puzzled.


The only thing I do differently respect to the examples taht I examined, is that the movie window and the relative view are allocated programmatically. I've tried also to call the "close" method to the window before releasing, but it didn't help.


Another thing which I've noticed, is that, if I keep the number of movies below the critical value which causes the crash, all the threads that are cumulated during the run are finally properly disposed at the end So it seems that I'm doing nothing wrong. I just wonder then, how I could force to purge those hanging threads before any new movie is played...


Any idea?


Thanks a lot.

That last bit about all the threads getting terminated at once: Are your playing all the movies in an local loop, without ever hitting the run loop? If so, you may want to put the inner part of the loop in an autoreleasepool (NSAutoreleasePool or @autoreleasepool.


Otherwise, the following code plays movies sequentially with threads and memory being released properly:


// _window and _movie are ivars  
-(void)playNextMovie
{
NSString* moviePath = [self nextMoviePath]; // returns path to next movie, or nil if there are no more movies
if (moviePath)
{
NSError* error = nil;
_movie = [[QTMovie alloc] initWithAttributes:@{ QTMovieFileNameAttribute:moviePath} error:&error];
// ignoring error - anti pattern, don't do that
NSRect frame = NSMakeRect( 0, 0, 320, 240);
QTMovieView* view = [[[QTMovieView alloc] initWithFrame:frame] autorelease];
[view setControllerVisible:NO];
[view setMovie:_movie];
_window = [[NSWindow alloc] initWithContentRect:frame styleMask:NSWindowStyleMaskTitled backing:NSBackingStoreBuffered defer:YES];
[_window setContentView:view];
[_window makeKeyAndOrderFront:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(movieDidEnd:) name:QTMovieDidEndNotification object:_movie];
[_movie play];
}
}
-(void)movieDidEnd:(NSNotification*)notification
{
[[NSNotificationCenter defaultCenter] removeObserver:self name:QTMovieDidEndNotification object:_movie];
[_movie release];
[_window close];
[self playNextMovie];
}

Hi, thanks for the example and the suggestions which pointed my attention to the right direction.


The situation is a bit complicated: the application is an old app which has been carbonized long time ago and into which I've injected some Objective-C code, in this case to manage the movie playback.


There is one autorelease pool which is allocated in the main() and periodically released and reallocated. It's when this pool is released that the hanging threads are purged all at once. So I thought to allocate another autorelease pool in the stack to encapsulate each single movie playback. Now, when I release this sub-pool the threads related to the movie are properly deleted too. However, I've stumbled into another issue: after the first sub-pool release and reallocation, [QTMovie alloc] always return NIL, and therefore I can play only the first movie.


I can't really understand. I guess I'm missing some fundamentals. Could someone shed some light on that?


Cheers,

Luca

Below is an implementation based on a local loop, with a local autorelease pool. But it's a weird way to play movies; it's blocking the UI, and may even truncate playback. I would strongly prefer 'chaining' playback with movieDidEnd notifications, as shown above.


\nhb


-(void)playMovies:(NSArray*)moviePaths

{

NSRect frame = NSMakeRect( 0, 0, 320, 240);

QTMovieView* view = [[QTMovieView alloc] initWithFrame:frame];

[view setControllerVisible:NO];


NSWindow* window = [[NSWindow alloc] initWithContentRect:frame styleMask:NSWindowStyleMaskTitled backing:NSBackingStoreBuffered defer:YES];

[window setContentView:view];

[window makeKeyAndOrderFront:nil];


for (NSUInteger i = 0; i < [moviePaths count]; i++)

{

NSAutoreleasePool* autoreleasePool = [[NSAutoreleasePool alloc] init];


NSError* error = nil;

NSDictionary* attributes = [NSDictionary dictionaryWithObject:[moviePaths objectAtIndex:i] forKey:QTMovieFileNameAttribute];

QTMovie* movie = [[[QTMovie alloc] initWithAttributes:attributes error:&error] autorelease];


[view setMovie:movie];

[movie play];


NSTimeInterval duration = 0;

QTGetTimeInterval( [movie duration], &duration);

[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:duration]];


[autoreleasePool release];

}


[window close];

[view release];

}

Hi,

thanks for this new sample of code.

Problem is I can't use a NSRunLoop and block the application. It isn't an event oriented application. It still uses function like WaitNextEvent() and a polling paradigm (yes, it's very very old stuff). The movie player is a sort of plugin, which implements a well defined API. To give you an idea of the basic structure of the code, it looks roughly like this:


void load_resources(sched_evt) {
     ...
     if (sched_evt->resource_type == MOVIE) {
          load_movie(sched_evt->resource_data)
     }
     ...
}

void play_resources(sched_evt) {
     ...
     if (sched_evt->resource_type == MOVIE) {
          play_movie(sched_evt->resource_data)
     }
     ...
}

void stop_resources(sched_evt) {
     ...
     if (sched_evt->resource_type == MOVIE) {
          stop_movie(sched_evt->resource_data)
     }
     ...
}

void unload_resources(sched_evt) {
     ...
     if (sched_evt->resource_type == MOVIE) {
          unload_movie(sched_evt->resource_data)
     }
     ...
}

void run() {
     scheduler = new_scheduler()
     while (!scheduler_done(&scheduler)) {
          if (scheduler_event(&sched_evt)) {
               ns_pool = new_autorelease_pool()
               switch(sched_evt.type) {
                    case LOAD:
                         load_resources(&sched_evt)
                    case PLAY:
                         play_resources(&sched_evt);
                    case STOP:
                         stop_resources(&sched_evt);
                    case UNLOAD:
                         unload_resources(&sched_evt);
               }
               del_autorelease_pool(ns_pool);
          }
     }
     del_scheduler(&scheduler)
}

void processEvent(...) {
     switch (event) {
        case (mouse down on "run menu"):
           // ns_pool = new_autorelease_pool() // moved into run()
           run();
           // del_autorelease_pool(ns_pool); // moved into run()
           break;
        case (...):
          ...
     }
}

int main() {
    while(!quitApp) {
       if (WaitNextEvent(&event)) {
            processEvent(&event)
       }
    }
}

Now, the issue is that after the first movie's load/play/stop/unload, the following don't work any more for [QTMovie alloc] constantly fails returnuning NIL, since I moved the the new/del_autorelease_pool() from processEvent() into the run() function). Before, when new/del_autorelease_pool() were in the processEvent() function, the application crashed when reached a certain number of movies played, as described before.


So the question is why I [QTMovie alloc] fails when the memory pool is released and reallocated?


Many thanks again for your time and help.

Cheers,

Luca

Problem fixed!


Actually, the QTMovie is properly allocated and initialised but, due to what I believe is a bug in XCode, every time I inspected the relative pointer value in debugging, it was reported as NIL and therefore my considerations were totally misled. What failed was instead another instruction which is executed later in the code:


window = [[NSWindow alloc] initWithWindowRef: windowRef];


The error here is to not perform a CFRetain(windowRef) and later, at movie played, releasing the window pointer. In fact the doc states:


"For historical reasons, contrary to normal memory management policy

initWithWindowRef:
does not retain
windowRef
. It is therefore recommended that you make sure you retain
windowRef
before calling this method. If
windowRef
is still valid when the Cocoa window is deallocated, the Cocoa window will release it"


So now, with the movie code execution encapsulated within an autorelease pool block, everything works just fine.

I apologize for the wrong description of the problem, but I got myself a wrong hint.


Thanks for your contribution which have been inspiring and explanatory.

Luca