67 votes

Ce code pour écrire de la vidéo+audio à travers AVAssetWriter et AVAssetWriterInputs ne fonctionne pas. Pourquoi ?

J'ai essayé d'écrire une vidéo+audio en utilisant AVAssetWriter et AVAssetWriterInputs.

J'ai lu plusieurs messages dans ce forum de personnes disant qu'elles avaient réussi à le faire, mais cela ne fonctionne pas pour moi. Si je me contente d'écrire une vidéo, le code fait très bien son travail. Lorsque j'ajoute de l'audio, le fichier de sortie est corrompu et ne peut être reproduit.

Voici une partie de mon code :

Configuration de AVCaptureVideoDataOutput et AVCaptureAudioDataOutput :

NSError *error = nil;

// Setup the video input
AVCaptureDevice *videoDevice = [AVCaptureDevice defaultDeviceWithMediaType: AVMediaTypeVideo];
// Create a device input with the device and add it to the session.
AVCaptureDeviceInput *videoInput = [AVCaptureDeviceInput deviceInputWithDevice:videoDevice error:&error];
// Setup the video output
_videoOutput = [[AVCaptureVideoDataOutput alloc] init];
_videoOutput.alwaysDiscardsLateVideoFrames = NO;
_videoOutput.videoSettings =
[NSDictionary dictionaryWithObject:
[NSNumber numberWithInt:kCVPixelFormatType_32BGRA] forKey:(id)kCVPixelBufferPixelFormatTypeKey];     

// Setup the audio input
AVCaptureDevice *audioDevice     = [AVCaptureDevice defaultDeviceWithMediaType: AVMediaTypeAudio];
AVCaptureDeviceInput *audioInput = [AVCaptureDeviceInput deviceInputWithDevice:audioDevice error:&error ];     
// Setup the audio output
_audioOutput = [[AVCaptureAudioDataOutput alloc] init];

// Create the session
_capSession = [[AVCaptureSession alloc] init];
[_capSession addInput:videoInput];
[_capSession addInput:audioInput];
[_capSession addOutput:_videoOutput];
[_capSession addOutput:_audioOutput];

_capSession.sessionPreset = AVCaptureSessionPresetLow;     

// Setup the queue
dispatch_queue_t queue = dispatch_queue_create("MyQueue", NULL);
[_videoOutput setSampleBufferDelegate:self queue:queue];
[_audioOutput setSampleBufferDelegate:self queue:queue];
dispatch_release(queue);

Configuration de l'AVAssetWriter et association des entrées audio et vidéo de l'AVAssetWriterInputs :

- (BOOL)setupWriter {
    NSError *error = nil;
    _videoWriter = [[AVAssetWriter alloc] initWithURL:videoURL 
                                             fileType:AVFileTypeQuickTimeMovie
                                                error:&error];
    NSParameterAssert(_videoWriter);

    // Add video input
    NSDictionary *videoCompressionProps = [NSDictionary dictionaryWithObjectsAndKeys:
                                                 [NSNumber numberWithDouble:128.0*1024.0], AVVideoAverageBitRateKey,
                                                        nil ];

    NSDictionary *videoSettings = [NSDictionary dictionaryWithObjectsAndKeys:
                                              AVVideoCodecH264, AVVideoCodecKey,
                                              [NSNumber numberWithInt:192], AVVideoWidthKey,
                                              [NSNumber numberWithInt:144], AVVideoHeightKey,
                                              videoCompressionProps, AVVideoCompressionPropertiesKey,
                                              nil];

    _videoWriterInput = [[AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo
                                                            outputSettings:videoSettings] retain];

    NSParameterAssert(_videoWriterInput);
    _videoWriterInput.expectsMediaDataInRealTime = YES;

    // Add the audio input
    AudioChannelLayout acl;
    bzero( &acl, sizeof(acl));
    acl.mChannelLayoutTag = kAudioChannelLayoutTag_Mono;

    NSDictionary* audioOutputSettings = nil;          
    // Both type of audio inputs causes output video file to be corrupted.
    if (NO) {
        // should work from iphone 3GS on and from ipod 3rd generation
        audioOutputSettings = [NSDictionary dictionaryWithObjectsAndKeys:
                              [ NSNumber numberWithInt: kAudioFormatMPEG4AAC ], AVFormatIDKey,
                                     [ NSNumber numberWithInt: 1 ], AVNumberOfChannelsKey,
                              [ NSNumber numberWithFloat: 44100.0 ], AVSampleRateKey,
                              [ NSNumber numberWithInt: 64000 ], AVEncoderBitRateKey,
                              [ NSData dataWithBytes: &acl length: sizeof( acl ) ], AVChannelLayoutKey,
                              nil];
    } else {
        // should work on any device requires more space
        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 ];
    } 

    _audioWriterInput = [[AVAssetWriterInput 
                            assetWriterInputWithMediaType: AVMediaTypeAudio 
                  outputSettings: audioOutputSettings ] retain];

    _audioWriterInput.expectsMediaDataInRealTime = YES;

    // add input
    [_videoWriter addInput:_videoWriterInput];
    [_videoWriter addInput:_audioWriterInput];

    return YES;
}

voici les fonctions permettant de démarrer/arrêter l'enregistrement vidéo

- (void)startVideoRecording
{
    if (!_isRecording) {
        NSLog(@"start video recording...");
        if (![self setupWriter]) {
             return;
        }
        _isRecording = YES;
    }
}

- (void)stopVideoRecording
{
    if (_isRecording) {
        _isRecording = NO;

        [_videoWriterInput markAsFinished];
        [_videoWriter endSessionAtSourceTime:lastSampleTime];

        [_videoWriter finishWriting];

        NSLog(@"video recording stopped");
    }
}

Et enfin le code CaptureOutput

- (void)captureOutput:(AVCaptureOutput *)captureOutput
didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
       fromConnection:(AVCaptureConnection *)connection
{
    if (!CMSampleBufferDataIsReady(sampleBuffer)) {
        NSLog( @"sample buffer is not ready. Skipping sample" );
        return;
    }

    if (_isRecording == YES) {
        lastSampleTime = CMSampleBufferGetPresentationTimeStamp(sampleBuffer);
        if (_videoWriter.status != AVAssetWriterStatusWriting ) {
            [_videoWriter startWriting];
            [_videoWriter startSessionAtSourceTime:lastSampleTime];
        }

        if (captureOutput == _videoOutput) {
            [self newVideoSample:sampleBuffer];
        }

        /*
        // If I add audio to the video, then the output file gets corrupted and it cannot be reproduced
        } else {
            [self newAudioSample:sampleBuffer];
        }
    */
    }
}

- (void)newVideoSample:(CMSampleBufferRef)sampleBuffer
{     
    if (_isRecording) {
        if (_videoWriter.status > AVAssetWriterStatusWriting) {
             NSLog(@"Warning: writer status is %d", _videoWriter.status);
             if (_videoWriter.status == AVAssetWriterStatusFailed)
                  NSLog(@"Error: %@", _videoWriter.error);
             return;
        }

        if (![_videoWriterInput appendSampleBuffer:sampleBuffer]) {
             NSLog(@"Unable to write to video input");
        }
    }
}

- (void)newAudioSample:(CMSampleBufferRef)sampleBuffer
{     
    if (_isRecording) {
        if (_videoWriter.status > AVAssetWriterStatusWriting) {
             NSLog(@"Warning: writer status is %d", _videoWriter.status);
             if (_videoWriter.status == AVAssetWriterStatusFailed)
                  NSLog(@"Error: %@", _videoWriter.error);
             return;
        }

        if (![_audioWriterInput appendSampleBuffer:sampleBuffer]) {
             NSLog(@"Unable to write to audio input");
        }
    }
}

Je serais très heureux si quelqu'un pouvait trouver quel est le problème dans ce code.

0 votes

J'ai des problèmes avec mon réglage audio avec un code très similaire au vôtre. Mon application enregistre la vidéo mais dès que je demande à l'AVAssetWritterInput que j'ai créé pour l'audio d'ajouter appendSampleBuffer : il me dit 'Input buffer must be in an uncompressed format when outputSettings is not nil'. Avez-vous déjà rencontré ce problème ? Cela me rend un peu dingue !

1 votes

Bonjour Kalos, l'entrée audio dans votre exemple provient du microphone ou de l'application elle-même ?

0 votes

@kalos brother pouvez-vous me dire comment utiliser videoURL.

25voto

Steve McFarlin Points 2303

Dans startVideoRecording, j'appelle (je suppose que vous l'appelez à un moment donné)

[_capSession startRunning] ;

Dans stopVideoRecording je n'appelle pas

[_videoWriterInput markAsFinished];
[_videoWriter endSessionAtSourceTime:lastSampleTime];

L'option markAsFinished est plutôt destinée à être utilisée avec la méthode de tirage en bloc. Voir requestMediaDataWhenReadyOnQueue:usingBlock dans AVAssetWriterInput pour une explication. La bibliothèque doit calculer le moment approprié pour entrelacer les tampons.

Il n'est pas nécessaire d'appeler endSessionAtSrouceTime. Le dernier horodatage dans les données échantillons sera utilisé après l'appel à

[_videoWriter finishWriting];

Je vérifie aussi explicitement le type de sortie de capture.

else if( captureOutput == _audioOutput) {
    [self newAudioSample:sampleBuffer]; 
}

Voici ce que j'ai. L'audio et la vidéo me parviennent. Il est possible que j'aie changé quelque chose. Si cela ne fonctionne pas pour vous, je posterai tout ce que j'ai.

-(void) startVideoRecording
    {
        if( !_isRecording )
         {
            NSLog(@"start video recording...");
            if( ![self setupWriter] ) {
                NSLog(@"Setup Writer Failed") ;

                return;
            }

            [_capSession startRunning] ;
            _isRecording = YES;
         }
    }

    -(void) stopVideoRecording
    {
        if( _isRecording )
         {
            _isRecording = NO;

            [_capSession stopRunning] ;

            if(![_videoWriter finishWriting]) { 
                NSLog(@"finishWriting returned NO") ;
            }
            //[_videoWriter endSessionAtSourceTime:lastSampleTime];
            //[_videoWriterInput markAsFinished];
            //[_audioWriterInput markAsFinished];

            NSLog(@"video recording stopped");
         }
    }

1 votes

Merci beaucoup Steve, vos conseils ont été très très utiles. Maintenant l'enregistrement vidéo a aussi de l'audio !

3 votes

@Steve et kalos pouvez-vous donner un code correct où il est possible d'enregistrer à la fois l'audio et la vidéo ?

0 votes

Vous pouvez jeter un coup d'œil à AVCamDemo, qui fait partie de l'échantillon de code de la WWDC 2010.

7voto

JeffMc Points 51

Premièrement, n'utilisez pas [NSNumber numberWithInt:kCVPixelFormatType_32BGRA] car il ne s'agit pas du format natif de l'appareil photo. utiliser [NSNumber numberWithInt:kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange]

En outre, vous devez toujours vérifier avant d'appeler startWriting qu'il n'est pas déjà en cours d'exécution. Il n'est pas nécessaire de définir l'heure de fin de session, car stopWriting s'en chargera.

1 votes

Quel est le problème avec kCVPixelFormatType_32BGRA ? Si vous utilisez le format natif, vous finirez très probablement par convertir en BGRA de toute façon via les shaders, ce qui est probablement ce qu'Apple fait pour vous lorsque vous spécifiez BGRA...

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X