iPhone - захват аудио с микрофона при использовании AVAssetWriterInputPixelBufferAdaptor для записи видео - PullRequest
4 голосов
/ 27 июля 2011

Я пытаюсь создать приложение, которое записывает содержимое UIImageView вместе со звуком микрофона и записывает их в видео в реальном времени. (Вроде как приложение Talking Tom Cat)

Я использую AVAssetWriterInputPixelBufferAdaptor для записи содержимого UIImageView без проблем, но я просто не знаю, как включить аудио с этим. И это не из-за отсутствия попыток, я работал над этой конкретной проблемой лучше, чем неделю, прочесывая этот веб-сайт, Google, форумы разработчиков iphone и т. Д. Это не моя чашка чая.

Ближайшая ссылка на это: Как экспортировать массив UIImage как фильм?

Я могу захватывать видео и аудио отдельно, а затем кодировать их вместе по факту (я действительно изо всех сил пытался решить эту проблему), но получить приложение для записи в режиме реального времени действительно идеально.

Вот некоторый каркасный код, который демонстрирует мою запись:

Вот файл .h

//
//  RecordingTestProjectViewController.h
//  RecordingTestProject
//
//  Created by Sean Luck on 7/26/11.
//  Copyright 2011 __MyCompanyName__. All rights reserved.
//

#import <UIKit/UIKit.h>
#import <AVFoundation/AVFoundation.h>
#import <CoreGraphics/CoreGraphics.h>
#import <CoreMedia/CoreMedia.h>
#import <CoreVideo/CoreVideo.h>
#import <QuartzCore/QuartzCore.h>

@interface RecordingTestProjectViewController : UIViewController 
    {
        UIImageView *testView;
        NSTimer *theTimer;

        NSTimer *assetWriterTimer;
        AVMutableComposition *mutableComposition;
        AVAssetWriter *assetWriter;
        AVAssetWriterInput *assetWriterInput;

        AVAssetWriterInput *_audioWriterInput;
        AVCaptureDeviceInput *audioInput;
        AVCaptureSession *_capSession;

        AVAssetWriterInputPixelBufferAdaptor *assetWriterPixelBufferAdaptor;
        CFAbsoluteTime firstFrameWallClockTime;
        int count;
    }

-(void) writeSample: (NSTimer*) _timer; 
-(void) startRecording;
-(void) pauseRecording;
-(void) stopRecording;
-(NSString*) pathToDocumentsDirectory; 


@end

И .m файл

//
//  RecordingTestProjectViewController.m
//  RecordingTestProject
//
//  Created by Sean Luck on 7/26/11.
//  Copyright 2011 __MyCompanyName__. All rights reserved.
//

#import "RecordingTestProjectViewController.h"
#import <AVFoundation/AVFoundation.h>
#import <CoreGraphics/CoreGraphics.h>
#import <CoreMedia/CoreMedia.h>
#import <CoreVideo/CoreVideo.h>
#import <QuartzCore/QuartzCore.h>

#define OUTPUT_FILE_NAME @"screen.mov"

#define TIME_SCALE 600

@implementation RecordingTestProjectViewController

- (void)dealloc
    {
        [super dealloc];
    }

- (void)didReceiveMemoryWarning
    {
        // Releases the view if it doesn't have a superview.
        [super didReceiveMemoryWarning];

        // Release any cached data, images, etc that aren't in use.
    }

#pragma mark - View lifecycle


// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad
    {

        testView = [[UIImageView alloc] initWithImage:nil];
        testView.frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height);
        testView.userInteractionEnabled=NO;
        testView.backgroundColor = [UIColor lightGrayColor];
        [self.view addSubview:testView];
        [testView release];



        [super viewDidLoad];
        [self startRecording];
    }







-(void) writeSample: (NSTimer*) _timer 
    {

        if ([assetWriterInput isReadyForMoreMediaData]) 
            {


                NSLog(@"count=%.i",count);
                count=count+10;

                UIGraphicsBeginImageContext(testView.frame.size);
                [testView.image drawInRect:CGRectMake(0, 0, testView.frame.size.width, testView.frame.size.height)];
                CGContextSetBlendMode(UIGraphicsGetCurrentContext(), kCGBlendModeNormal);
                CGContextSetRGBStrokeColor(UIGraphicsGetCurrentContext(), 0,1,1,1);
                CGContextSetLineWidth(UIGraphicsGetCurrentContext(), 5);
                CGContextBeginPath(UIGraphicsGetCurrentContext());
                CGContextMoveToPoint(UIGraphicsGetCurrentContext(), 10+count/2, 10+count);
                CGContextAddLineToPoint(UIGraphicsGetCurrentContext(), 20+count*2, 20+count);
                CGContextStrokePath(UIGraphicsGetCurrentContext());
                testView.image = UIGraphicsGetImageFromCurrentImageContext();
                UIGraphicsEndImageContext();


                CVReturn cvErr = kCVReturnSuccess;
                CGImageRef image = (CGImageRef) [testView.image CGImage];

                // prepare the pixel buffer
                CVPixelBufferRef pixelBuffer = NULL;
                CFDataRef imageData= CGDataProviderCopyData(CGImageGetDataProvider(image));
                cvErr = CVPixelBufferCreateWithBytes(kCFAllocatorDefault,testView.frame.size.width,testView.frame.size.height,kCVPixelFormatType_32BGRA,(void*)CFDataGetBytePtr(imageData),CGImageGetBytesPerRow(image),NULL,NULL,NULL,&pixelBuffer);


                // calculate the time
                CFAbsoluteTime thisFrameWallClockTime = CFAbsoluteTimeGetCurrent();
                CFTimeInterval elapsedTime = thisFrameWallClockTime - firstFrameWallClockTime;
                CMTime presentationTime =  CMTimeMake (elapsedTime * TIME_SCALE, TIME_SCALE);

                if (!cvErr) 
                    {
                        // write the sample
                        BOOL appended = [assetWriterPixelBufferAdaptor appendPixelBuffer:pixelBuffer withPresentationTime:presentationTime];

                        if (appended) 
                            {

                            } 
                        else 
                            {
                                NSLog (@"failed to append");
                                [self stopRecording];
                            }
                    }


                CVPixelBufferRelease(pixelBuffer);
                CFRelease(imageData);

            }

        if (count>1000) 
            {
                [self stopRecording];
            }
    }

-(void) startRecording 
    {
        // Doesn't record audio at all. Needs to be implemented.

        // create the AVAssetWriter
        NSString *moviePath = [[self pathToDocumentsDirectory] stringByAppendingPathComponent:OUTPUT_FILE_NAME];
        if ([[NSFileManager defaultManager] fileExistsAtPath:moviePath]) 
            {
                [[NSFileManager defaultManager] removeItemAtPath:moviePath error:nil];
            }

        NSURL *movieURL = [NSURL fileURLWithPath:moviePath];
        NSError *movieError = nil;

        [assetWriter release];
        assetWriter = [[AVAssetWriter alloc] initWithURL:movieURL fileType: AVFileTypeQuickTimeMovie error: &movieError];
        NSDictionary *assetWriterInputSettings = [NSDictionary dictionaryWithObjectsAndKeys: AVVideoCodecH264, AVVideoCodecKey,[NSNumber numberWithInt:testView.frame.size.width], AVVideoWidthKey,[NSNumber numberWithInt:testView.frame.size.height], AVVideoHeightKey,nil];

        [assetWriterInput release];
        assetWriterInput =[AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo outputSettings:assetWriterInputSettings];
        assetWriterInput.expectsMediaDataInRealTime = YES;
        [assetWriter addInput:assetWriterInput];


        [assetWriterPixelBufferAdaptor release];
        assetWriterPixelBufferAdaptor = [[AVAssetWriterInputPixelBufferAdaptor  alloc] initWithAssetWriterInput:assetWriterInput sourcePixelBufferAttributes:nil];
        [assetWriter startWriting];

        firstFrameWallClockTime = CFAbsoluteTimeGetCurrent();
        [assetWriter startSessionAtSourceTime: CMTimeMake(0, TIME_SCALE)];

        // start writing samples to it
        [assetWriterTimer release];
        assetWriterTimer = [NSTimer scheduledTimerWithTimeInterval:0.2 target:self selector:@selector (writeSample:) userInfo:nil repeats:YES];

        [movieURL release];
        [assetWriterInputSettings release];

    }


-(void) stopRecording 
    {
        if (assetWriterTimer!=nil) 
            {
                [assetWriterTimer invalidate];
                assetWriterTimer = nil;
                [assetWriter finishWriting];


                NSString *moviePath = [[self pathToDocumentsDirectory] stringByAppendingPathComponent:OUTPUT_FILE_NAME];
                UISaveVideoAtPathToSavedPhotosAlbum (moviePath, nil, nil, nil);

            }

    }





-(void) pauseRecording
    {
        // needs to be implemented
    }




-(NSString*) pathToDocumentsDirectory 
    {
        NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
        NSString *documentsDirectory = [paths objectAtIndex:0];
        return documentsDirectory;

    }


- (void)viewDidUnload
    {
        [super viewDidUnload];
        // Release any retained subviews of the main view.
        // e.g. self.myOutlet = nil;
    }

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
    {
        // Return YES for supported orientations
        return YES;
    }

@end

Так вот и все. Если кто-то может изменить этот код для записи звука с микрофона, я уверен, что есть много людей, кроме меня, которые будут благодарны. Как только аудио будет добавлено, это создаст прямую основу для скриншотов приложений, чтобы разработчики могли продемонстрировать свои приложения.

К вашему сведению: мой код с открытым исходным кодом, по большей части смоделированный после проекта VTMScreenRecorderTest от http://www.subfurther.com/blog/2011/04/12/voices-that-matter-iphone-spring-2011/. Я сделал небольшие умеренные модификации и устранил утечки памяти.

1 Ответ

1 голос
/ 19 августа 2013

Я только что обнаружил, что CMTimestamps должны быть синхронизированы, если вы хотите использовать микрофон вместе с записанным видео.Я попытался синхронизировать их, изменив время выборки аудио, используя CMSampleBufferSetOutputPresentationTimeStamp ... но я что-то упустил, или это не работает.Если вы улавливаете время, когда начинается ваш микрофон, и добавляете его в качестве смещения к вашему видео, каждый из примеров AVAssetWriter здесь, в StackOverflow, кажется, работает нормально -

Вот что я делаю, чтобы сохранить видео всинхронизация с микрофоном:

Img = Image
Snd = Sound
Avmedia = AVAssetWriter
etc.
#define Cmtm__Pref_Timescale_uk 10000
          CMTime Duration_cmtm = CMTimeMakeWithSeconds( Avmedia_v->Img_Duration__Sec_df, Cmtm__Pref_Timescale_uk );
          Avmedia_v->Img_cmtm = CMTimeAdd( Duration_cmtm, Avmedia_v->Exprt__Begin_cmtm );

          if( [ Avmedia_v->Clrbuf__Adaptor_v appendPixelBuffer: Clrbuf_v withPresentationTime:
               Avmedia_v->Img_cmtm ] is no )
          {
            //If the operation was unsuccessful,
            //invoke the AVAssetWriter object’s finishWriting method in order to save a partially completed asset.
            [ Avmedia_v->Avwriter_v finishWriting ];

            CLog( Lv_Minor, "Movie Img exprt failed" );
          }
...