J'ai essayé d'écrire une vidéo + audio en utilisant AVAssetWriter et AVAssetWriterInputs.
J'ai lu plusieurs messages dans ce forum de gens disant qu'ils étaient capables d'accomplir cela, mais cela ne fonctionne pas pour moi. Si je viens 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 pas être reproduit.
Voici une partie de mon code:
Configuration d'AVCaptureVideoDataOutput et d'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);
Configurer AVAssetWriter et y associer des entrées audio et vidéo AVAssetWriter:
- (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 pour 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.
Dans startVideoRecording j'appelle (je suppose que vous appelez cela à un moment donné)
[_capSession startRunning] ;
En stopVideoRecording je n'appelle pas
[_videoWriterInput markAsFinished];
[_videoWriter endSessionAtSourceTime:lastSampleTime];
Le markAsFinished est plus destiné à être utilisé avec la méthode d'extraction de style bloc. Voir requestMediaDataWhenReadyOnQueue: usingBlock dans AVAssetWriterInput pour une explication. La bibliothèque doit calculer le timing approprié pour l'entrelacement des tampons.
Vous n'avez pas besoin d'appeler endSessionAtSrouceTime. Le dernier horodatage des exemples de données sera utilisé après l'appel à
[_videoWriter finishWriting];
Je vérifie également 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'ai changé quelque chose. Si cela ne fonctionne pas pour vous, je publierai 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");
}
}
Tout d'abord, n'utilisez pas [NSNumber numberWithInt:kCVPixelFormatType_32BGRA]
, car ce n'est pas le format natif de l'appareil photo. utilisation [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. Vous n'avez pas besoin de définir l'heure de fin de session, car stopWriting le fera.