2014-03-21 1 views
1

Я боролся с этим около года, пытаясь определить проблему и представить ее для просмотра другими.Как записывать идеальные петли в iOS и Xcode

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

Я могу включить запись в течение примерно 4,8 секунд (.6 * 8 битов), а таймер говорит, что он длился 4,8 секунды, однако моя аудиозапись всегда немного короче 4.8. Это как 4.78 или 4.71, что заставляет петлю выйти из строя.

Я экспериментировал с AVAudioRecorder, AudioQueue и AudioUnits, думая, что последние методы могут привести к решению моей проблемы.

Я использую NSTimer, чтобы выстрелить каждые 0,6 секунды, играя короткую вспышку для метронома. После 4 ударов функция таймера метронома включит метрономер рекордера, который ждет 4.6 секунды остановки записи.

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

Мне жаль, что я не мог приложить свой проект, но, я думаю, мне просто нужно согласиться с приложением моего заголовка и реализации. Чтобы проверить, что вам придется сделать проект со следующими характеристиками: IB

запись, воспроизведение, стоп кнопки Song/Track Длительность этикетки Таймер продолжительности этикетки Debug ярлык

Если вы запустите приложение, затем хит-запись, вы «подсчитаны» с 4 ударами, затем начинается рекордер. Нажмите пальцем на стол, пока рекордер не остановится. После 8 ударов (всего 12) рекордер останавливается.

Вы можете видеть на дисплеях, что записанный трек немного короче 4,8 секунд, а в некоторых случаях намного короче, что приводит к неправильной петле.

Кто-нибудь знает, что я могу сделать, чтобы подтянуть это? Спасибо за прочтение.

Вот мой код:

// 
// ViewController.h 
// speakagain 
// 
// Created by NOTHING on 2014-03-18. 
// 

#import <UIKit/UIKit.h> 
#import <Foundation/Foundation.h> 
#import "CoreAudio/CoreAudioTypes.h" 
#import <AudioToolbox/AudioQueue.h> 
#import <AudioToolbox/AudioFile.h> 
#import <AVFoundation/AVFoundation.h> 

@interface ViewController : UIViewController 
{ 
    IBOutlet UIButton *btnRecord; 
    IBOutlet UIButton *btnPlay; 
    IBOutlet UIButton *btnStop; 
    IBOutlet UILabel *debugLabel; 
    IBOutlet UILabel *timerDuration; 
    IBOutlet UILabel *songDuration; 

    //UILabel *labelDebug; 

    struct AQRecorderState { 
     AudioStreamBasicDescription mDataFormat; 
     AudioQueueRef    mQueue; 
     AudioQueueBufferRef   mBuffers[kNumberBuffers]; 
     AudioFileID     mAudioFile; 
     UInt32      bufferByteSize; 
     SInt64      mCurrentPacket; 
     bool       mIsRunning;     // 8 

    }; 
    struct AQRecorderState aqData; 
    AVAudioPlayer *audioPlayer; 

    NSString *songName; 
    NSTimer *recordTimer; 
    NSTimer *metroTimer; 
    NSTimeInterval startTime, endTime, elapsedTime; 

    int inputBuffer; 
    int beatNumber; 

} 
@property (nonatomic, retain) IBOutlet UIButton *btnRecord; 
@property (nonatomic, retain) IBOutlet UIButton *btnPlay; 
@property (nonatomic, retain) IBOutlet UIButton *btnStop; 
@property (nonatomic, retain) IBOutlet UILabel *debugLabel; 
@property (nonatomic, retain) IBOutlet UILabel *timerDuration; 
@property (nonatomic, retain) IBOutlet UILabel *songDuration; 


- (IBAction) record; 
- (IBAction) stop; 
- (IBAction) play; 

static void HandleInputBuffer (void *aqData,AudioQueueRef inAQ,AudioQueueBufferRef inBuffer,const AudioTimeStamp *inStartTime, UInt32 inNumPackets,const AudioStreamPacketDescription *inPacketDesc); 

@end 

Реализация:

// 
    // ViewController.m 
    // speakagain 
    // 
    // Created by NOTHING on 2014-03-18. 
    // 

    #import "ViewController.h" 


    @interface ViewController() 

    @end 

    @implementation ViewController 
    @synthesize btnPlay, btnRecord,btnStop,songDuration, timerDuration, debugLabel; 


    - (void)viewDidLoad 
    { 
     debugLabel.text = @""; 
     songName =[[NSString alloc ]init]; 
     //NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); 
     //NSString *documentsDirectory = [paths objectAtIndex:0]; 
     songName = @"TestingQueue.caf"; 



     [super viewDidLoad]; 
     // Do any additional setup after loading the view, typically from a nib. 
    } 
    - (void)prepareAudioQueue 
    { 
     //struct AQRecorderState *pAqData; 
     inputBuffer=0; 
     aqData.mDataFormat.mFormatID   = kAudioFormatLinearPCM; 
     aqData.mDataFormat.mSampleRate  = 44100.0; 
     aqData.mDataFormat.mChannelsPerFrame = 1; 
     aqData.mDataFormat.mBitsPerChannel = 16; 
     aqData.mDataFormat.mBytesPerPacket = 
     aqData.mDataFormat.mBytesPerFrame = aqData.mDataFormat.mChannelsPerFrame * sizeof (SInt16); 
     aqData.mDataFormat.mFramesPerPacket = 1; 

     // AudioFileTypeID fileType    = kAudioFileAIFFType; 
     AudioFileTypeID fileType    = kAudioFileCAFType; 
     aqData.mDataFormat.mFormatFlags = kLinearPCMFormatFlagIsBigEndian| kLinearPCMFormatFlagIsSignedInteger| kLinearPCMFormatFlagIsPacked; 

     AudioQueueNewInput (&aqData.mDataFormat,HandleInputBuffer, &aqData,NULL, kCFRunLoopCommonModes, 0,&aqData.mQueue); 

     UInt32 dataFormatSize = sizeof (aqData.mDataFormat); 

     // in Mac OS X, instead use 
     // kAudioConverterCurrentInputStreamDescription 
     AudioQueueGetProperty (aqData.mQueue,kAudioQueueProperty_StreamDescription,&aqData.mDataFormat,&dataFormatSize); 

     //Verify 
     NSFileManager *fileManager = [NSFileManager defaultManager]; 
     NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); 
     NSString *documentsDirectory = [paths objectAtIndex:0]; 
     NSString *txtPath = [documentsDirectory stringByAppendingPathComponent:songName]; 

     NSLog(@"INITIALIZING FILE"); 
     if ([fileManager fileExistsAtPath:txtPath] == YES) { 
      NSLog(@"PREVIOUS FILE REMOVED"); 
      [fileManager removeItemAtPath:txtPath error:nil]; 
     } 


     const char *filePath = [txtPath UTF8String]; 
     CFURLRef audioFileURL = CFURLCreateFromFileSystemRepresentation (NULL,(const UInt8 *) filePath,strlen (filePath),false); 
     AudioFileCreateWithURL (audioFileURL,fileType,&aqData.mDataFormat, kAudioFileFlags_EraseFile,&aqData.mAudioFile); 

     DeriveBufferSize (aqData.mQueue,aqData.mDataFormat,0.5,&aqData.bufferByteSize); 

     for (int i = 0; i < kNumberBuffers; ++i) 
     { 
      AudioQueueAllocateBuffer (aqData.mQueue,aqData.bufferByteSize,&aqData.mBuffers[i]); 
      AudioQueueEnqueueBuffer (aqData.mQueue,aqData.mBuffers[i], 0,NULL); 
     } 

    } 

    - (void) metronomeFire 
    { 
     if(beatNumber < 5) 
     { 
      //count in time. 
      // just play the metro beep but don't start recording 
      debugLabel.text = @"count in (1,2,3,4)"; 
      [self playSound]; 
     } 
     if(beatNumber == 5) 
     { 
      //start recording 
      aqData.mCurrentPacket = 0; 
      aqData.mIsRunning = true; 
      startTime = [NSDate timeIntervalSinceReferenceDate]; 
      recordTimer = [NSTimer scheduledTimerWithTimeInterval:4.8 target:self selector:@selector(killTimer) userInfo:nil repeats:NO]; 
      AudioQueueStart (aqData.mQueue,NULL); 
      debugLabel.text = @"Recording for 8 beats (1,2,3,4 1,2,3,4)"; 
      [self playSound]; 
     } 
     else if (beatNumber < 12) 
     { //play metronome from beats 6-16 
      [self playSound]; 
     } 
     if(beatNumber == 12) 
     { 
      [metroTimer invalidate]; metroTimer = nil; 
      [self playSound]; 
     } 

     beatNumber++; 

    } 
    - (IBAction) play 
    { 
     NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); 
     NSString *documentsDirectory = [paths objectAtIndex:0]; 
     NSString *txtPath = [documentsDirectory stringByAppendingPathComponent:songName]; 
     NSURL *url = [NSURL fileURLWithPath:[NSString stringWithFormat:@"%@",txtPath]]; 

     if (audioPlayer) 
     { 
      [audioPlayer stop]; 
      audioPlayer = nil; 
     } 
     NSError *error; 
     audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:&error]; 

     if (audioPlayer == nil) 
     { 
      NSLog(@"%@",[error description]); 
     } 
     else 
     { 
      [audioPlayer play]; 
      [audioPlayer setNumberOfLoops:-1]; 
     } 
    } 
    - (void) killTimer 
    { 
     //this is the timer function. Runs once after 4.8 seconds. 
     [self stop]; 

    } 
    - (IBAction) stop 
    { 
     if (audioPlayer) 
     { 
      [audioPlayer stop]; 
      audioPlayer = nil; 



     } 
     else 
     { 

      if(metroTimer) 
      { 
       [metroTimer invalidate];metroTimer = nil; 
      } 
      //Stop the audio queue 
      AudioQueueStop (aqData.mQueue,true); 
      aqData.mIsRunning = false; 
      AudioQueueDispose (aqData.mQueue,true); 
      AudioFileClose (aqData.mAudioFile); 

      //Get elapsed time of timer 
      endTime = [NSDate timeIntervalSinceReferenceDate]; 
      elapsedTime = endTime - startTime; 

      //Get elapsed time of audio file 
      NSArray *pathComponents = [NSArray arrayWithObjects: 
             [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject], 
             songName, 
             nil]; 
      NSURL *audioFileURL = [NSURL fileURLWithPathComponents:pathComponents]; 
      AVURLAsset* audioAsset = [AVURLAsset URLAssetWithURL:audioFileURL options:nil]; 
      CMTime audioDuration = audioAsset.duration; 
      float audioDurationSeconds = CMTimeGetSeconds(audioDuration); 

      //Log values 
      NSLog(@"Track Duration: %f",audioDurationSeconds); 
      NSLog(@"Timer Duration: %.6f", elapsedTime); 

      //Show values on GUI too 
      songDuration.text = [NSString stringWithFormat: @"Track Duration: %f",audioDurationSeconds]; 
      timerDuration.text = [NSString stringWithFormat:@"Timer Duration: %@",[NSString stringWithFormat: @"%.6f", elapsedTime]]; 
      debugLabel.text = @"Why is the duration of the track less than the duration the timer ran?"; 
     } 


    } 
    -(void) playSound 
    { 
     NSString *path = [[NSBundle mainBundle] pathForResource:@"blip2" ofType:@"aif"]; 
     SystemSoundID soundID; 
     AudioServicesCreateSystemSoundID((__bridge CFURLRef)[NSURL fileURLWithPath:path], &soundID); 
     AudioServicesPlaySystemSound (soundID); 
    } 

    - (IBAction) record 
    { 
     [self prepareAudioQueue]; 
     songDuration.text = @""; 
     timerDuration.text = @""; 
     //debugLabel.text = @"Please wait 12 beats (The first four are count in)"; 
     //init beat number 
     beatNumber = 1; 

     //safe guard 
     if(aqData.mIsRunning) 
     { 
      AudioQueueStop (aqData.mQueue,true); 

      aqData.mIsRunning = false; 

      AudioQueueDispose (aqData.mQueue,true); 
      AudioFileClose (aqData.mAudioFile); 
     } 

     //start count in (metro will start recording) 
     //aqData.mCurrentPacket = 0; 
     //aqData.mIsRunning = true; 
     startTime = [NSDate timeIntervalSinceReferenceDate]; 
     metroTimer = [NSTimer scheduledTimerWithTimeInterval:.6 target:self selector:@selector(metronomeFire) userInfo:nil repeats:YES]; 
     //recordTimer = [NSTimer scheduledTimerWithTimeInterval:4.8 target:self selector:@selector(killTimer) userInfo:nil repeats:NO]; 
     //AudioQueueStart (aqData.mQueue,NULL); 

    } 
    static void HandleInputBuffer (void *aqData,AudioQueueRef inAQ,AudioQueueBufferRef inBuffer,const AudioTimeStamp *inStartTime,UInt32 inNumPackets,const AudioStreamPacketDescription *inPacketDesc) 
    { 
     //boiler plate 
     NSLog(@"HandleInputBuffer"); 

     struct AQRecorderState *pAqData = (struct AQRecorderState *) aqData; 

     if (inNumPackets == 0 && pAqData->mDataFormat.mBytesPerPacket != 0) 
      inNumPackets = inBuffer->mAudioDataByteSize/pAqData->mDataFormat.mBytesPerPacket; 

     if (AudioFileWritePackets (pAqData->mAudioFile,false,inBuffer->mAudioDataByteSize,inPacketDesc,pAqData->mCurrentPacket,&inNumPackets,inBuffer->mAudioData) == noErr) 
     { 
      pAqData->mCurrentPacket += inNumPackets; 
     } 

     if (pAqData->mIsRunning == 0) 
      return; 

     AudioQueueEnqueueBuffer (pAqData->mQueue,inBuffer,0,NULL); 
    } 

    void DeriveBufferSize(AudioQueueRef audioQueue,AudioStreamBasicDescription ASBDescription,Float64 seconds,UInt32 *outBufferSize) 
    { 
     //boiler plate 
     static const int maxBufferSize = 0x50000; 
     int maxPacketSize = ASBDescription.mBytesPerPacket; 
     if(maxPacketSize == 0) 
     { 
      UInt32 maxVBRPacketSize = sizeof(maxPacketSize); 
      AudioQueueGetProperty(audioQueue, kAudioQueueProperty_MaximumOutputPacketSize, &maxPacketSize, &maxVBRPacketSize); 
      NSLog(@"max buffer = %d",maxPacketSize); 
     } 
     Float64 numBytesForTime = ASBDescription.mSampleRate * maxPacketSize * seconds; 
     *outBufferSize = (UInt32)(numBytesForTime < maxBufferSize ? numBytesForTime : maxBufferSize); 
    } 

    OSStatus SetMagicCookieForFile (AudioQueueRef inQueue, AudioFileID inFile) 
    { 
     //boiler plate 
     OSStatus result = noErr; 
     UInt32 cookieSize; 
     if (AudioQueueGetPropertySize (inQueue,kAudioQueueProperty_MagicCookie,&cookieSize) == noErr) 
     { 
      char* magicCookie =(char *) malloc (cookieSize); 
      if (AudioQueueGetProperty (inQueue,kAudioQueueProperty_MagicCookie,magicCookie,&cookieSize) == noErr) 
      { 
       result = AudioFileSetProperty (inFile,kAudioFilePropertyMagicCookieData,cookieSize,magicCookie); 
      } 

      free (magicCookie); 
     } 
     return result; 

    } 













    - (void)didReceiveMemoryWarning 
    { 
     [super didReceiveMemoryWarning]; 
     // Dispose of any resources that can be recreated. 
    } 
    @end 
+0

EDIT - я могу включить запись для (8 ударов: при 100BPM) около 4,8 секунды (0,6 * 8 ударов), а таймер говорит, что он длился 4,8 секунды, однако моя аудиозапись всегда немного короче чем 4.8. Это как 4.78 или 4.71, что заставляет петлю выйти из строя. – user3447100

ответ

1

Это большая тема, поэтому я сомневаюсь, что вы получите ответ, достаточно большой, чтобы повторно архитекторам код, который вы имеете предоставлена. Тем не менее, я могу дать вам ссылки, которые обеспечат подавляющее большинство того, что вам нужно.

Первым делом NSTimer никогда не будет работать из-за проблем с синхронизацией. Кроме того, забудьте AudioQueue и AVAudioRecorder. Только AudioUnit достаточно низок для ваших нужд.

Посмотрите мой ответ здесь:

iOS Stream Audio from one iOS Device to Another

Но истинный кладезь - и знания, которые вы должны будете быть хорошо знакомы с - это блог Вкусные пикселя. Tasty Pixel является продавцом Loopy HD, но также и человеком, который достаточно любезен, чтобы поделиться некоторыми довольно глубокими знаниями.

См:

A simple, fast circular buffer implementation for audio processing

Developing Loopy, Part 2: Implementation

и

Using RemoteIO audio unit

Наконец, убедитесь, что вы знакомы с пакетами, кадры, образцов и т.д. Все должно синхронизировать в совершенстве.

+1

Спасибо за быстрый ответ! Чтение, чтение, чтение :) – user3447100

+0

Я второй комментарий о 'NSTimer'. Сроки для вашего приложения всегда должны быть звуковыми часами. Даже если ваш таймер срабатывает точно, когда вы ожидали его (что связано с назначением основного потока вашего процесса), вы все равно можете быть периодом визуализации звука + любое время, которое требуется синхронизировать с потоком рендеринга аудио. – marko