Become a MacRumors Supporter for $50/year with no ads, ability to filter front page stories, and private forums.

spt

macrumors member
Original poster
Oct 27, 2009
39
7
Hello,

I would like to make a command line program that is able to record audio using the internal mike of my MBP. I've done some experimenting, but the problem is that as soon as a leave out the GUI the program won't run properly. It seems as if the program is 'waiting' for something. I've tried to run things in separate threats, which seems to help, as the code below works well.

However, as soon as I leave out starting NSApplicationMain, it stops working properly.

Any ideas on what I might have done wrong?

Code:
#import <Cocoa/Cocoa.h>
#import <QTKit/QTkit.h>
#import <pthread.h>

QTCaptureSession            *mCaptureSession;
QTCaptureMovieFileOutput    *mCaptureMovieFileOutput;
QTCaptureDeviceInput        *mCaptureAudioDeviceInput;

pthread_t *t_cap, *t_stop;

void capture_tread(void *arch);
void capture_stop(void *arch);

int main(int argc, char *argv[])
{
    pthread_create(&t_cap, NULL, capture_tread, NULL);
    pthread_create(&t_stop, NULL, capture_stop, NULL);
    printf("Starting threads...\n");

   // As soon as I leave out this part (and add pthreads_join to ensure the main thread doesn't exit premature), no errors occur but sound will never be written to a file...
    return NSApplicationMain(argc,  (const char **) argv);
}

void capture_tread(void *arch)
{    
    printf("Start capture thread\n");
    
    // Create the capture session
	mCaptureSession = [[QTCaptureSession alloc] init];
    
    // Connect inputs and outputs to the session	
	BOOL success = NO;
	NSError *error;
	
    QTCaptureDevice *audioDevice = [QTCaptureDevice defaultInputDeviceWithMediaType:QTMediaTypeSound];
    success = [audioDevice open:&error];
    
    if (!success) {
        audioDevice = nil;
        // Handle error
    }
    
    if (audioDevice) {
        mCaptureAudioDeviceInput = [[QTCaptureDeviceInput alloc] initWithDevice:audioDevice];
        
        success = [mCaptureSession addInput:mCaptureAudioDeviceInput error:&error];
        if (!success) {
            // Handle error
        }
    }
    
    // Create the audi file output and add it to the session
    mCaptureMovieFileOutput = [[QTCaptureMovieFileOutput alloc] init];
    success = [mCaptureSession addOutput:mCaptureMovieFileOutput error:&error];
    if (!success) {
        // Handle error
    }
    
    //[mCaptureMovieFileOutput setDelegate:0];
    
    // Set the compression for the audio/video that is recorded to the hard disk
	NSEnumerator *connectionEnumerator = [[mCaptureMovieFileOutput connections] objectEnumerator];
	QTCaptureConnection *connection;
	
	// iterate over each output connection for the capture session and specify the desired compression
	while ((connection = [connectionEnumerator nextObject])) {
		NSString *mediaType = [connection mediaType];
		QTCompressionOptions *compressionOptions = nil;
		// specify the audio compression options
        if ([mediaType isEqualToString:QTMediaTypeSound]) {
			// use AAC Audio
			compressionOptions = [QTCompressionOptions compressionOptionsWithIdentifier:@"QTCompressionOptionsHighQualityAACAudio"];
		}
		
		// set the compression options for the movie file output
		[mCaptureMovieFileOutput setCompressionOptions:compressionOptions forConnection:connection];
	} 
    
    [mCaptureSession startRunning];
    
    [mCaptureMovieFileOutput recordToOutputFileURL:[NSURL fileURLWithPath:@"/Users/shared/test.mov"]];
    
}

void capture_stop(void *arch)
{
    printf("Pausing...\n");
    sleep(5);
    [mCaptureMovieFileOutput recordToOutputFileURL:[NSURL fileURLWithPath:nil]];
    printf("Stop capturing\n");
}
 
Last edited:

mfram

Contributor
Jan 23, 2010
1,307
344
San Diego, CA USA
Well, I can think of a couple issues. First, it's my understanding that separate threads should have their own autorelease pools. You should create separate autorelease pools for each thread.

Second, if any of the classes you are using use NSTimers internally then then timers won't work unless there's a run loop created. A GUI program gets a run loop created automatically. Maybe that's the magic that the GUI program is giving you.
 

chown33

Moderator
Staff member
Aug 9, 2009
10,751
8,425
A sea of green
The main() function must not return until everything else is completed. When main() returns, the exit() function is called. That will exit the program, regardless of any other threads that might be running. That's pretty much standard Posix behavior, too, so it shouldn't be surprising.

The main thread, which runs the main() function, needs to run its run-loop, which will do its normal event handling. That's what NSApplicationMain() does for your, because that's what its reference doc says it does.

This is also worth reading:
http://cocoawithlove.com/2009/01/demystifying-nsapplication-by.html

And instead of pthreads, it might be better to use NSThread. That's what I've done before when I had a command-line tool that needed threading. I also implemented a main-thread run-loop, which was pretty easy to do with NSRunLoop.
 

spt

macrumors member
Original poster
Oct 27, 2009
39
7
The main() function must not return until everything else is completed. When main() returns, the exit() function is called. That will exit the program, regardless of any other threads that might be running. That's pretty much standard Posix behavior, too, so it shouldn't be surprising.

I know, that's why I added pthread_join in that case.

The main thread, which runs the main() function, needs to run its run-loop, which will do its normal event handling. That's what NSApplicationMain() does for your, because that's what its reference doc says it does.

This is also worth reading:
http://cocoawithlove.com/2009/01/demystifying-nsapplication-by.html

This may be useful, as the problem indeed occurs when the main thread is sleeping.

----------

Well, I can think of a couple issues. First, it's my understanding that separate threads should have their own autorelease pools. You should create separate autorelease pools for each thread.

Second, if any of the classes you are using use NSTimers internally then then timers won't work unless there's a run loop created. A GUI program gets a run loop created automatically. Maybe that's the magic that the GUI program is giving you.

This did the trick.

Everything worked after simply adding
Code:
    [[NSRunLoop currentRunLoop] run];

in the main loop.

Thanks!
 
Register on MacRumors! This sidebar will go away, and you'll see fewer ads.