J'ai implémenté une RPScreenRecorder
, qui enregistre l'écran ainsi que l'audio d'un micro. Lorsque plusieurs enregistrements sont terminés, j’arrête l’enregistrement et fusionne les Audios avec Vidéos à l’aide de AVMutableComposition
, puis fusionne toutes les vidéos pour former une seule vidéo.
Pour l'enregistrement d'écran et pour obtenir les fichiers vidéo et audio, j'utilise
- (void)startCaptureWithHandler:(nullable void(^)(CMSampleBufferRef sampleBuffer, RPSampleBufferType bufferType, NSError * _Nullable error))captureHandler completionHandler:
Pour arrêter l'enregistrement. J'appelle cette fonction:
- (void)stopCaptureWithHandler:(void (^)(NSError *error))handler;
Et ceux-ci sont assez simples.
La plupart du temps, cela fonctionne très bien, je reçois à la fois des CMSampleBuffers audio et vidéo. Mais il arrive parfois questartCaptureWithHandler
m'envoie uniquement des tampons audio mais pas des tampons vidéo. Et une fois que je rencontre ce problème, il ne disparaîtra pas avant le redémarrage de mon appareil et la réinstallation de l'application. Cela rend mon application si peu fiable pour l'utilisateur. Je pense qu’il s’agit d’un problème lié au kit de relecture mais qu’il est impossible de découvrir des problèmes connexes avec d’autres développeurs. Faites-moi savoir si l’un d’entre vous a découvert ce problème et obtenu la solution.
J'ai vérifié plusieurs fois, mais je n'ai vu aucun problème de configuration. Mais le voilà quand même.
NSError *videoWriterError;
videoWriter = [[AVAssetWriter alloc] initWithURL:fileString fileType:AVFileTypeQuickTimeMovie
error:&videoWriterError];
NSError *audioWriterError;
audioWriter = [[AVAssetWriter alloc] initWithURL:audioFileString fileType:AVFileTypeAppleM4A
error:&audioWriterError];
CGFloat width =UIScreen.mainScreen.bounds.size.width;
NSString *widthString = [NSString stringWithFormat:@"%f", width];
CGFloat height =UIScreen.mainScreen.boNSString *heightString = [NSString stringWithFormat:@"%f", height];unds.size.height;
NSDictionary * videoOutputSettings= @{AVVideoCodecKey : AVVideoCodecTypeH264,
AVVideoWidthKey: widthString,
AVVideoHeightKey : heightString};
videoInput = [[AVAssetWriterInput alloc] initWithMediaType:AVMediaTypeVideo outputSettings:videoOutputSettings];
videoInput.expectsMediaDataInRealTime = true;
AudioChannelLayout acl;
bzero( &acl, sizeof(acl));
acl.mChannelLayoutTag = kAudioChannelLayoutTag_Mono;
NSDictionary * audioOutputSettings = [ NSDictionary dictionaryWithObjectsAndKeys:
[ NSNumber numberWithInt: kAudioFormatAppleLossless ], AVFormatIDKey,
[ NSNumber numberWithInt: 16 ], AVEncoderBitDepthHintKey,
[ NSNumber numberWithFloat: 44100.0 ], AVSampleRateKey,
[ NSNumber numberWithInt: 1 ], AVNumberOfChannelsKey,
[ NSData dataWithBytes: &acl length: sizeof( acl ) ], AVChannelLayoutKey,
nil ];
audioInput = [[AVAssetWriterInput alloc] initWithMediaType:AVMediaTypeAudio outputSettings:audioOutputSettings];
[audioInput setExpectsMediaDataInRealTime:YES];
[videoWriter addInput:videoInput];
[audioWriter addInput:audioInput];
[[AVAudioSession sharedInstance] setCategory: AVAudioSessionCategoryPlayAndRecord withOptions:AVAudioSessionCategoryOptionDefaultToSpeaker error:nil];
[RPScreenRecorder.sharedRecorder startCaptureWithHandler:^(CMSampleBufferRef _Nonnull sampleBuffer, RPSampleBufferType bufferType, NSError * _Nullable myError) {
Block
}
La fonction startCaptureWithHandler a également une fonctionnalité assez simple:
[RPScreenRecorder.sharedRecorder startCaptureWithHandler:^(CMSampleBufferRef _Nonnull sampleBuffer, RPSampleBufferType bufferType, NSError * _Nullable myError) {
dispatch_sync(dispatch_get_main_queue(), ^{
if(CMSampleBufferDataIsReady(sampleBuffer))
{
if (self->videoWriter.status == AVAssetWriterStatusUnknown)
{
self->writingStarted = true;
[self->videoWriter startWriting];
[self->videoWriter startSessionAtSourceTime:CMSampleBufferGetPresentationTimeStamp(sampleBuffer)];
[self->audioWriter startWriting];
[self->audioWriter startSessionAtSourceTime:CMSampleBufferGetPresentationTimeStamp(sampleBuffer)];
}
if (self->videoWriter.status == AVAssetWriterStatusFailed) {
return;
}
if (bufferType == RPSampleBufferTypeVideo)
{
if (self->videoInput.isReadyForMoreMediaData)
{
[self->videoInput appendSampleBuffer:sampleBuffer];
}
}
else if (bufferType == RPSampleBufferTypeAudioMic)
{
// printf("\n+++ bufferAudio received %d \n",arc4random_uniform(100));
if (writingStarted){
if (self->audioInput.isReadyForMoreMediaData)
{
[self->audioInput appendSampleBuffer:sampleBuffer];
}
}
}
}
});
}
En outre, lorsque cette situation se produit, l’enregistreur d’écran du système est également corrompu. En cliquant sur l'enregistreur système, cette erreur apparaît:
L'erreur indique "L'enregistrement d'écran s'est arrêté en raison de: Echec lors de l'enregistrement en raison d'une erreur de Mediaservices".
Il doit y avoir deux raisons:
Si c'est question non. 1, alors pas de problème. S'il s'agit du problème no. 2 alors je dois savoir où je peux me tromper?
Les opinions et l'aide seront appréciées.
Ainsi, je suis tombé sur des scénarios dans lesquels le kit Replay se bloque complètement et l’enregistreur système affiche une erreur à chaque fois que vous ne redémarrez pas le périphérique.
1er scénario
Lorsque vous démarrez l'enregistrement et que vous l'arrêtez dans le gestionnaire d'achèvement
[RPScreenRecorder.sharedRecorder startCaptureWithHandler:^(CMSampleBufferRef _Nonnull sampleBuffer, RPSampleBufferType bufferType, NSError * _Nullable error) {
printf("recording");
} completionHandler:^(NSError * _Nullable error) {
[RPScreenRecorder.sharedRecorder stopCaptureWithHandler:^(NSError * _Nullable error) {
printf("Ended");
}];
}];
2e scénario
Lorsque vous démarrez l'enregistrement et que vous l'arrêtez directement dans le gestionnaire de capture
__block BOOL stopDone = NO;
[RPScreenRecorder.sharedRecorder startCaptureWithHandler:^(CMSampleBufferRef _Nonnull sampleBuffer, RPSampleBufferType bufferType, NSError * _Nullable error) {
if (!stopDone){
[RPScreenRecorder.sharedRecorder stopCaptureWithHandler:^(NSError * _Nullable error) {
printf("Ended");
}];
stopDone = YES;
}
printf("recording");
} completionHandler:^(NSError * _Nullable error) {}];
Plus de scénarios doivent encore être découverts et je continuerai à mettre à jour la réponse
Mise à jour 1
Il est vrai que l’écran système enregistré donne une erreur lorsque nous arrêtons d’enregistrer juste après le début, mais cela semble bien fonctionner après avoir appelé startcapture à nouveau.
J'ai également rencontré un scénario dans lequel je n'obtiens pas de mémoire tampon vidéo uniquement dans mon application Et où l'enregistreur d'écran système fonctionne correctement et mettra à jour la solution Bientôt.
Mise à jour 2
Donc, voici le problème, mon application réelle est ancienne et elle est maintenue et mise à jour en temps opportun. Lorsque le kit de rejeu devient erroné, mon application d'origine ne peut pas recevoir de mémoire tampon vidéo, je ne sais pas s'il existe une configuration qui rend cela possible, ou quoi?
Mais le nouvel exemple d'application semble bien fonctionner et, une fois le kit rejoué, devenu erroné. la prochaine fois que j'appellerai startCapture, le kit de relecture sera en bon état. Bizarre
Mise à jour 3
J'ai observé un nouveau problème. Lorsque l'alerte d'autorisation s'affiche, l'application passe en arrière-plan. Depuis que j'ai codé que chaque fois que l'application passe en arrière-plan, des modifications de l'interface utilisateur auront lieu et l'enregistrement sera arrêté. Cela a conduit à l'erreur de
Enregistrement interrompu par le multitâche et le redimensionnement du contenu
Je ne suis pas encore certain du changement de l'interface utilisateur qui crée cet échec, mais il ne vient que lorsque l'alerte d'autorisation s'affiche et que les modifications de l'interface utilisateur sont effectuées. Si quelqu'un a remarqué un cas particulier pour ce problème, veuillez nous en informer.
Dans videoOutputSettings
faites AVVideoWidthKey
& AVVideoHeightKey
NSNumber
au lieu de NSString
.
Dans audioOutputSettings
supprimer AVEncoderBitDepthHintKey
& AVChannelLayoutKey.
Ajoutez AVEncoderBitRateKey
avec NSNumber
64000
et remplacez AVFormatIDKey
par kAudioFormatMPEG4AAC
en remplaçant kAudioFormatAppleLossless
.
Dans mon projet, j'ai rencontré un problème similaire. Autant que je me souvienne, le problème venait de mes paramètres de sortie.
Vous pouvez également essayer de déplacer tout votre code dans le bloc de succès startCaptureWithHandler
à l'intérieur d'un bloc synchrone.
dispatch_sync(dispatch_get_main_queue(), ^ {
// your block code
}
Si l'écran n'a pas changé, ReplayKit n'appelle pas processSampleBuffer () avec vidéo. Par exemple, dans une présentation PowerPoint, processSampleBuffer () n'est appelé que lorsqu'un nouveau diaporama est affiché. ProcessSampleBuffer () avec vidéo n'est appelé pendant 10 secondes ou 1 minute Parfois, Replaykit n'appelle pas processSampleBuffer () dans la nouvelle diapositive. Non, il manque une diapositive à l'utilisateur. Il est critique et montre le bogue d'arrêt.
En revanche, processSampleBuffer with Audio est appelé toutes les 500 ms sur iOS 11.4.