iphone - Este código para escribir video+audio a través de AVAssetWriter y AVAssetWriterInputs no funciona. ¿Por qué?
(2)
En startVideoRecording, llamo (supongo que estás llamando esto en algún momento)
[_capSession startRunning] ;
En stopVideoRecording no llamo
[_videoWriterInput markAsFinished];
[_videoWriter endSessionAtSourceTime:lastSampleTime];
El markAsFinished es más para usar con el método de extracción de estilo de bloque. Consulte requestMediaDataWhenReadyOnQueue: usingBlock en AVAssetWriterInput para obtener una explicación. La biblioteca debe calcular el momento adecuado para entrelazar los almacenamientos intermedios.
No necesita llamar a endSessionAtSrouceTime. La última marca de tiempo en los datos de muestra se usará después de la llamada a
[_videoWriter finishWriting];
También compruebo explícitamente el tipo de salida de captura.
else if( captureOutput == _audioOutput) {
[self newAudioSample:sampleBuffer];
}
Esto es lo que tengo. El audio y el video vienen por mí. Es posible que haya cambiado algo. Si esto no funciona para usted, entonces publicaré todo lo que tengo.
-(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");
}
}
He intentado escribir un video + audio usando AVAssetWriter y AVAssetWriterInputs.
Leí varias publicaciones en este foro de personas que decían que podían lograr eso, pero que no funciona para mí. Si solo escribo video, entonces el código está haciendo su trabajo muy bien. Cuando agrego audio, el archivo de salida está dañado y no se puede reproducir.
Aquí está parte de mi código:
Configuración de AVCaptureVideoDataOutput y 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);
Configuración de AVAssetWriter y asociación de audio y video AVAssetWriterInputs a él:
- (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;
}
Aquí hay funciones para iniciar / detener la grabación de 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");
}
}
Y finalmente el código de 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");
}
}
}
Estaría muy contento si alguien pudiera encontrar cuál es el problema en este código.
Primero, no use [NSNumber numberWithInt: kCVPixelFormatType_32BGRA], ya que no es el formato nativo de la cámara. use [NSNumber numberWithInt: kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange]
Además, siempre debe verificar antes de llamar a startWriting que aún no se está ejecutando. No necesita establecer la hora de finalización de la sesión, ya que stopWriting lo hará.