2010-11-10 13 views
57

Ho cercato di scrivere un video + audio utilizzando AVAssetWriter e AVAssetWriterInputs.Questo codice per scrivere video + audio tramite AVAssetWriter e AVAssetWriterInputs non funziona. Perché?

Ho letto più post in questo forum di persone che dicono di essere in grado di farlo, ma non funziona per me. Se scrivo solo video, il codice sta facendo il suo lavoro molto bene. Quando aggiungo l'audio, il file di output è danneggiato e non può essere riprodotto.

Ecco parte del mio codice:

Impostazione AVCaptureVideoDataOutput e 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); 

Impostazione AVAssetWriter e associando entrambe AVAssetWriterInputs audio e video ad esso:

- (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; 
} 

qui sono funzioni per avviare/interrompere la registrazione video

- (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"); 
    } 
} 

E infine il codice 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"); 
     } 
    } 
} 

Sarei molto contento se qualcuno potesse trovare che è il problema in questo codice.

+0

Sto riscontrando problemi con le mie impostazioni audio con codice molto simile al tuo. La mia app registrerà i video ma non appena avrò detto all'AVAssetWritterInput che ho creato per l'audio ad appendSampleBuffer: mi dice 'Il buffer di input deve essere in un formato non compresso quando outputSettings non è nullo'.Hai mai incontrato questo problema? Sta guidando il mio leggermente nocciola! – Baza207

+1

ciao kalos, l'ingresso audio nel tuo esempio proviene dal microfono o dall'applicazione stessa? – justicepenny

+0

@kalos fratello puoi dirmi come possiamo usare videoURL. – morroko

risposta

23

In startVideoRecording chiamo (suppongo che si sta chiamando questo ad un certo punto)

[_capSession startRunning] ; 

In stopVideoRecording io non chiamo

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

Il markAsFinished è più per l'utilizzo con il pull blocco metodo. Vedi requestMediaDataWhenReadyOnQueue: usingBlock in AVAssetWriterInput per una spiegazione. La libreria dovrebbe calcolare il momento giusto per l'interleaving dei buffer.

Non è necessario chiamare endSessionAtSrouceTime. L'ultima data e ora nei dati di esempio verranno utilizzati dopo la chiamata a

[_videoWriter finishWriting]; 

ho anche controllare in modo esplicito per il tipo di output cattura.

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

Ecco quello che ho. L'audio e il video vengono fuori per me. È possibile che abbia cambiato qualcosa. Se questo non funziona per te, posterò tutto ciò che ho.

-(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

Grazie mille Steve, i tuoi suggerimenti sono stati molto utili. Ora la registrazione video ha anche l'audio! – kalos

+2

@Steve e kalos puoi fornire codice di lavoro corretto dove è possibile registrare sia audio che video? – sach

+0

Si consiglia di dare un'occhiata a AVCamDemo dal codice di esempio WWDC 2010. –

6

Innanzitutto, non utilizzare [NumeroNumerazione NSInt: kCVPixelFormatType_32BGRA], poiché non è il formato nativo della telecamera. utilizzare [NSNumber numberWithInt: kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange]

Inoltre, prima di chiamare startWriting è necessario verificare che non sia già in esecuzione. Non è necessario impostare l'ora di fine della sessione, poiché stopWriting lo farà.

+1

Cosa c'è di sbagliato in kCVPixelFormatType_32BGRA? Se utilizzi il formato nativo, molto probabilmente finirai per convertirli in BGRA tramite shader, che è probabilmente ciò che Apple fa per te quando specifichi BGRA ... – jjxtra

Problemi correlati