NSTask et utilisation CPU

Ludopac

Membre actif
5 Avril 2001
970
4
42
Nancy
www.objectifmac.com
J'ai un problème avec la class NSTask. Je voudrais en fait faire une interface graphique pour un logiciel UNIX.

Pas de problème si c'est un programme qui renvoie un certain nombre de ligne et qui se termine (genre ls, ps, grep ...).

Mais dans mon cas, c'est ffmpeg, logiciel d'encodage qui envoie en continue des données.

J'arrive à récupérer les données que ffmpeg transmet au fur et à mesure et récupérer ce qui m'intéresse, mais ce mécanisme occupe 80 % du processeur ne laissant que 20 % à ffmpeg.

Le problème vient certainement du fait que les données arrivent en continue et très vite. Donc je me demandais si il n'y avait pas moyen de lancer ffmpeg par NSTask et ensuite d'aller chercher les données que toutes les 2 secondes par exemple.

Voilà le code que j'utilise (ça vient de Cocoa Dev Central) ...
- (IBAction)launchFfmpeg:(id)sender
{

NSTask *ffmprgGo = [[NSTask alloc] init];
NSPipe *pipe=[[NSPipe alloc] init];
NSFileHandle *handle;

NSString *pathToFfmpeg = [NSString stringWithFormat:@"%@%@",[[NSBundle mainBundle] resourcePath],@"/ffmpeg"];
[ffmprgGo setLaunchPath: pathToFfmpeg];

[ffmprgGo setArguments:[NSArray arrayWithObjects:@"-vcodec", @"mpeg4", @"-acodec", @"mp3", @"-b", @"1000", [pathToSplit stringValue] , nil]];

[ffmprgGo setStandardOutput: pipe];
handle=[pipe fileHandleForReading];

[ffmprgGo launch];

[NSThread detachNewThreadSelector:@selector(copyData:) toTarget:self withObject:handle];

[pipe release];
[ffmprgGo release];

}



- (void)copyData:(NSFileHandle*)handle {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSData *data;
while([data=[handle availableData] length]) {

NSString *string=[[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding];

[pourcent setStringValue:string];
[string release];
}

[pool release];
}

@end

Ce qui occupe toutes les ressources, c'est la fonction copyData:.
C'est une boucle qui lit en permanence les données transmises par ffmpeg et les recopie dans un champ de texte.

Pour régler le problème, je pensais faire en sorte que ce qui se trouve dans la boucle ne soit exécuté qu'une fois toutes les 1 ou 2 secondes. Mais est-ce possible ? Ou dois-je procéder autrement ?

Je débute totalement en Objectif C donc si quelqu'un pouvait m'aider, ça serait sympa parce là je suis perdu
frown.gif
 
Si tu veux que ta boucle soit exécutée toutes les deux secondes, tu dois la declarer dans une méthode comme copyData et tu dois créer un timer par
NSTimer *myTimer=[NSTimer timerWithTimeInterval:2 target:self selector: @selector(copyData:) userInfo:nil repeats:YES];

ce timer se déclenchera toutes les 2 secondes en exécutant de manière répétée la methode copyData.

Une façon plus élégante de résoudre ton problème c'est d'utiliser l'une des notifications envoyées par NSFileHandle en se déclarant comme observer de cette notification et en spécifiant au NotificationCenter d'exécuter la méthode copyData à chaque fois qu'il y a des données à récupérer. C'est l'une des forces du NSFileHandler.
 
Manu a dit:
Une façon plus élégante de résoudre ton problème c'est d'utiliser l'une des notifications envoyées par NSFileHandle en se déclarant comme observer de cette notification et en spécifiant au NotificationCenter d'exécuter la méthode copyData à chaque fois qu'il y a des données à récupérer. C'est l'une des forces du NSFileHandler.

Pourrais tu me donner un exemple rapide de la façon dont s'implémenterait cette solution, parce que j'ai fait un essai, et ça me fait pareille. 80% du proc pour l'interface et 20 % pour ffmpeg.

Le truc, c'est que ffmpeg envoie réellement beaucoup de données et trés vite.
frown.gif


En tout cas merci pour ton aide.
 
C'est bon j'ai réussi avec les notifications comme tu me l'as indiqué ...

C'est vraiment cool, maintenant j'ai 10-20% pour l'interface et 90-80 pour ffmepg, ce qui est pas mal du tout
tongue.gif


Un grand merci pour ton aide
cool.gif
 
Content que t'ai compris l'utilité des NotificationCenter. L'autre truc très cool à connaitre pour bien comprendre cocoa c'est la Delegation, puis les protocoles et les categories.

Salut
 
Manu a dit:
L'autre truc très cool à connaitre pour bien comprendre cocoa c'est la Delegation, puis les protocoles et les categories.

Merci pour les conseils. Les notifications j'utilise déjà, et ça rend bien service. Les catégories je connais aussi, faudra juste que je regarde ce que sont les protocoles
smile.gif



 
Les protocoles c'est ce que les développeurs Java appellent Interface. Pour ma part je trouve que le terme protocole est plus approprié. C'est le fait de rajouter à une classe existante un certain nombre de méthodes sans les implémenter.
Quand tu lis la description d'une Classe dans Foundation Kit ou Application Kit, tu remarques qu'au début il y a les annotations semblables à celles de la classe NSMutableString décrits comme suit :

Inherits from: NSString : NSObject
Conforms to: NSCoding (NSString)
NSCopying (NSString)
NSMutableCopying (NSString)
NSObject (NSObject)

les inherits c'est les classes mères, alors que Conforms to indique la liste des protocoles auxquels se conforme la classe.
Par exemple le fait que la classe obeit au potocole NSCopying cela veut dire que tu peux faire des copies d'une instance de la classe.
Ainsi si tu crées une Classe, si tu veux qu'on puisse faire des copies des objets de ta classe, il faut que ta classe obeisse au protocole NSCopying. Voila comment tu la déclares :
@interface myClasse : NSObject <NSCopying> {
attributs de ta classe
}
methodes de ta classe
@end
dans l'implémentation de tes méthodes, tu implémntes la méthode copyWithZone.
Ainsi si ta classe s'appelle MyClasse, on pourra faire
MyClasse *myCopy = [truc copy] la methode copy utilise la méthode copyWithZone du protocole NSCopying qu tu as implémentée. En fait la copie consiste a copier un à un les attributs de ton objet.
J'espère qu j'ai été clair. Va dans les sample fournis par Apple tu dois trouver des exemples d'implémentation.

 
Manu a dit:
Les protocoles c'est ce que les développeurs Java appellent Interface. Pour ma part je trouve que le terme protocole est plus approprié. C'est le fait de rajouter à une classe existante un certain nombre de méthodes sans les implémenter.
Quand tu lis la description d'une Classe dans Foundation Kit ou Application Kit, tu remarques qu'au début il y a les annotations semblables à celles de la classe NSMutableString décrits comme suit :

Inherits from: NSString : NSObject
Conforms to: NSCoding (NSString)
NSCopying (NSString)
NSMutableCopying (NSString)
NSObject (NSObject)

les inherits c'est les classes mères, alors que Conforms to indique la liste des protocoles auxquels se conforme la classe.
Par exemple le fait que la classe obeit au potocole NSCopying cela veut dire que tu peux faire des copies d'une instance de la classe.
Ainsi si tu crées une Classe, si tu veux qu'on puisse faire des copies des objets de ta classe, il faut que ta classe obeisse au protocole NSCopying. Voila comment tu la déclares :
@interface myClasse : NSObject <NSCopying> {
attributs de ta classe
}
methodes de ta classe
@end
dans l'implémentation de tes méthodes, tu implémntes la méthode copyWithZone.
Ainsi si ta classe s'appelle MyClasse, on pourra faire
MyClasse *myCopy = [truc copy] la methode copy utilise la méthode copyWithZone du protocole NSCopying qu tu as implémentée. En fait la copie consiste a copier un à un les attributs de ton objet.
J'espère qu j'ai été clair. Va dans les sample fournis par Apple tu dois trouver des exemples d'implémentation.


Merci pour les explications
smile.gif
J'vais jeter un oeil sur des exemples pour voir tout ça en pratique ...

En tout cas c'est sympa d'avoir des conseils d'un spécialise
tongue.gif
 
Pendant qu'on y est on va aller plus loin. En fait les protocoles permettent d'implémenter le polymorphisme. Si tu ne le sais pas, le polymorphisme c'est le fait d'avoir un nom de methode utilisé par des classes différentes pour exécuter une même fonction. Mais l'implémentation de cette méthode dépend de la classe. Exemple si j'ai deux objets différents, la fonction copy ne s'exécute pas de la même manière car ils ont des attributs différents. Hors une copie c'est ni plus ni moins une duplication d'attributs.
Dans la Foundation Kit et l'Application Kit , après la liste des classes, il y a celle des protocoles.
Généralement les protocoles sont utilisés dans les objets de base ou objets modèle. Le terme modèle est celui du paradigme MVC (Model View Controller) qui est le schéma directeur de toute application Cocoa.
Il y a des protocoles qui sont très interresants. Par exemple le protocol NSCoding (tu remarqueras la terminaison ing des protocol qui veut dire possibilité de) definit deux méthodes initWithCoder et encodeWithCoder. Si ton objet obeit à ce protocole, cela veut dire que des instances de cet objet peuvent être archivées dans un fichier ou une base de données avec encodeWithCoder, puis récupérés du fichier et reconstruit avec initWithCoder.
Donc des objets archivables.
L'autre protocol interressant c'est NSKeyValueCoding. Il est très important car si tous les objets de ton appli obeissent à ce protocole, ton application est très facilement scriptable. En effet AppleScript utilise énormément les méthodes de ce protocole pour retouver des objets d'après la valeur d'un attribut. ( exemple la 3ème cellule de la seconde ligne).
La force de Cocoa c'est d'offrir des Framework (Foundation Application Kit) avec des éléments de bases très fournis et donnant beaucoup de possibilité et de souplesse pour développer rapidement et surtout pour obtenir les résultats qu'on attend.
 
arreter tous

tu as besoin de piper ton stream c'est tout

tu pipes donc ton process

et tu while true
c'est la meme chose que pour piper un netstat -w1

tu lis le stream c'est tout est quand tu en as marre tu referme le pipe
laugh.gif



arretes manu tu vas lui faire peur

#import "activityController.h"

// private methods
@interface activityController(Private)
- (void)start;
- (void)stop;
- (void)output:(NSNotification *)aNotification;
- (void)error:(NSNotification *)errNotification;
@end

@implementation activityController

- (IBAction)startStop:(id)sender
{
if (theTask) {
[self stop];
} else {
[self start];
}
}

- (void)start
{
// initialize arguments for CLI -
// poll every second for network stats
NSMutableArray *theArguments = [NSMutableArray arrayWithArray:[NSArray arrayWithObjects:@"-w1", nil]];

// open a pipe for netstat to send its output to
NSPipe *thePipe = [NSPipe pipe];

// open another pipe for the errors
NSPipe *errorPipe = [NSPipe pipe];

// make sure there isn't netstat task currently running
NSAssert( !theTask, @"Task already exists" );

// set sent packet widget at 0, received at 0
[outboundProgressIndicator setDoubleValue:0];
[inboundProgressIndicator setDoubleValue:0];

// initialize storage
totalSeconds = 0;
totalIn = 0;
totalOut = 0;

// clear data fields
[inboundCurrent setIntValue:0];
[inboundAvg setIntValue:0];
[inboundMax setIntValue:0];
[outboundCurrent setIntValue:0];
[outboundAvg setIntValue:0];
[outboundMax setIntValue:0];

// create the subprocess
theTask = [[NSTask alloc] init];

// set the subprocess to start a netstat session
[theTask setLaunchPath:@"/usr/sbin/netstat"];

// set up arguments for netstat CLI
[theTask setArguments:theArguments];

// point the output of the netstat session to the pipe,
// and the error to the other pipe
[theTask setStandardOutput:thePipe];
[theTask setStandardError:errorPipe];

// launch netstat
[theTask launch];

// make sure I haven't already got an output object
NSAssert( !theOutput, @"Output already exists" );

// create file handles for the output and error
theOutput = [[thePipe fileHandleForReading] retain];
theError = [[errorPipe fileHandleForReading] retain];

// register myself as an observer for this pipe
// updates trigger output method
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(output:) name:NSFileHandleReadCompletionNotification object:theOutput];

// register myself as an observer for the error pipe
// updates trigger error method
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(error:) name:NSFileHandleReadCompletionNotification object:theError];

// put it in the background - don't hold UI hostage
[theOutput readInBackgroundAndNotify];
[theError readInBackgroundAndNotify];

// make the UI signal user - session in progress
// change button to stop
[theButton setTitle:@"Stop"];

// this thing should be purring like a kitten
}

- (void)stop
{
// kill the subprocess, clear from memory
[theTask interrupt];
theTask = nil;

// unregister for notification
[[NSNotificationCenter defaultCenter] removeObserver:self name:NSFileHandleReadCompletionNotification object:theOutput];
[[NSNotificationCenter defaultCenter] removeObserver:self name:NSFileHandleReadCompletionNotification object:theError];

// clear the output object from memory
[theOutput release];
[theError release];
theOutput = nil;
theError = nil;

// reset the UI
[theButton setTitle:@"Start"];
}

- (void)output:(NSNotification *)aNotification
{
// get the data from the notification
NSData *theData = [[aNotification userInfo] objectForKey:NSFileHandleNotificationDataItem];

NSString *theString = [[[NSString alloc] initWithData:theData encoding:NSNEXTSTEPStringEncoding] autorelease];

int thisInbound = 0;
int thisOutbound = 0;
float outPercent = 0;
float inPercent = 0;
int avgIn = 0;
int avgOut = 0;

// set up a scanner to fish out the numbers
NSScanner *theScanner = [NSScanner scannerWithString:theString];

// increment totalSeconds for averaging purposes
totalSeconds = totalSeconds + 1;

// strip out headers
[theScanner scanUpToCharactersFromSet:[NSCharacterSet decimalDigitCharacterSet] intoString:nil];

if([theScanner isAtEnd] == NO){
// the first two number are to be skipped
[theScanner scanInt:NULL];
[theScanner scanInt:NULL];


// the next number is the incoming bytes
[theScanner scanInt:&thisInbound];

// skip two more numbers
[theScanner scanInt:nil];
[theScanner scanInt:nil];

// the next number is the outgoing bytes
[theScanner scanInt:&thisOutbound];

// see if inbound has exceeded max
if([inboundMax intValue] < thisInbound){
[inboundMax setIntValue:thisInbound];
}

// see if outbound has exceeded max
if([outboundMax intValue] < thisOutbound){
[outboundMax setIntValue:thisOutbound];
}

// put current numbers into UI
[outboundCurrent setIntValue:thisOutbound];
[inboundCurrent setIntValue:thisInbound];

// calculate percentage - don't divide by zero
if([inboundMax intValue] > 0){
inPercent = (thisInbound * 100) / [inboundMax intValue];
} else {
inPercent = 0;
}

if([outboundMax intValue] > 0){
outPercent = (thisOutbound * 100) / [outboundMax intValue];
} else {
outPercent = 0;
}

// set the progress bars
[outboundProgressIndicator setDoubleValue:outPercent];
[inboundProgressIndicator setDoubleValue:inPercent];

// add in and out to accumulated total
totalIn = totalIn + thisInbound;
totalOut = totalOut + thisOutbound;

// calculate average
avgIn = (totalIn / totalSeconds);
avgOut = (totalOut / totalSeconds);

// set average fields
[outboundAvg setIntValue:avgOut];
[inboundAvg setIntValue:avgIn];

}

// reset the notifier
[theOutput readInBackgroundAndNotify];
}

- (void)error:(NSNotification *)errNotification
{
// get the data from the notification
NSData *errData = [[errNotification userInfo] objectForKey:NSFileHandleNotificationDataItem];

NSString *errString = [[[NSString alloc] initWithData:errData encoding:NSNEXTSTEPStringEncoding] autorelease];

// tell the user what the problem is
NSBeginAlertSheet( @"Bad News", @"Ok", nil, nil, [theApp keyWindow], nil, NULL, NULL, NULL, errString);

// reset progress meters, this run was a dud
[inboundProgressIndicator setDoubleValue:0];
[outboundProgressIndicator setDoubleValue:0];

// we're done
[self stop];
}

@end
 
plumber a dit:
arreter tous

tu as besoin de piper ton stream c'est tout

tu pipes donc ton process

et tu while true
c'est la meme chose que pour piper un netstat -w1

tu lis le stream c'est tout est quand tu en as marre tu referme le pipe
laugh.gif


Non, mais c'est bon j'ai dit que j'avais réussi avec les notifications
tongue.gif


Mais merci quand même pour tout ce code
laugh.gif