audio - How to record perfect loops in iOS and Xcode -


i've been struggling year trying pin down problem , represent others see.

i've been writing app depends on 'garageband' recording. is, want record user 8 beats, , want them able loop this. playing metronome user @ same time (user wearing head phones hearing metronome, recording mic on device)

i can manage turn on recording 4.8 seconds (.6*8 beats), , timer says ran 4.8 seconds, audio recording bit shorter 4.8. it's 4.78, or 4.71 causes loop go out of whack.

i've experimented avaudiorecorder,audioqueue, , audiounits thinking 1 latter methods might result in solving problem.

i using nstimer fire off every .6 seconds playing short blip metronome. after 4 beats, metronome timer's function, turns on recorder metronnome waits 4.6 seconds stops recording.

i'm using time intervals time how long metro runs (looks pretty tight @ 4.800xxx) , comparing duration of audio file different.

i wish attach project, guess i'll have settle attaching header , implementation. test you'll have make project following ib characteristics:

record, play, stop buttons song/track duration label timer duration label debug label

if launch app, hit record, 'counted in' 4 beats, recorder starts. tap finger on desk until recorder stops. after 8 more beats (12 in total) recorder stops.

you can see in displays recorded track little shorter 4.8 seconds, , in cases, lot shorter, causing audio not loop properly.

does know can tighten up? reading.

here's code:

// //  viewcontroller.h //  speakagain // //  created 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 

implementation:

//     //  viewcontroller.m     //  speakagain     //     //  created 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];         // additional setup after loading view, typically 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);          (int = 0; < 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.             // play metro beep 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 8 beats (1,2,3,4 1,2,3,4)";             [self playsound];         }         else if (beatnumber < 12)         {   //play metronome 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 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 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             songduration.text = [nsstring stringwithformat: @"track duration: %f",audiodurationseconds];             timerduration.text = [nsstring stringwithformat:@"timer duration: %@",[nsstring stringwithformat: @"%.6f", elapsedtime]];             debuglabel.text = @"why duration of track less duration 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 4 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 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 resources can recreated.     }     @end 

this big topic doubt you'll answer big enough re-architect code you've provided. however, can give links supply vast majority of require.

first thing nstimer never work due synchronisation issues. also, forget audioqueue , avaudiorecorder. audiounit low level enough needs.

have @ answer here:

ios stream audio 1 ios device another

but true goldmine - , knowledge need intimately familiar - tasty pixel's blog. tasty pixel being vendor of loopy hd, kind enough share pretty in depth knowledge.

see:

a simple, fast circular buffer implementation audio processing

developing loopy, part 2: implementation

and

using remoteio audio unit

finally, make sure familiar packets, frames, samples, etc. needs sync perfectly.


Comments

Popular posts from this blog

java - WrongTypeOfReturnValue exception thrown when unit testing using mockito -

php - Magento - Deleted Base url key -

android - How to disable Button if EditText is empty ? -