Menu

Tutorial details

How to Create a Talking Tom style App | App Code for Sale | Preview

How to Create a Talking Tom style App

This tutorial is based on the component TalkingApp
Download

Overview PAGE TOP

Among popular applications on store are those where animals talk, they are cute and they'll repeat in funny voice what user said so people find this interesting and entertaining. Some of those applications are „Talking Tom“, „Talking Ben the Dog“, „Talking Pierre“ and so on.

image157.png

In this tutorial we'll see how to create application that will record audio and play modified audio. We'll also add parameters so user will be able to change modified audio by slowing it down or by increasing its speed.

A sneak preview of what could be the final result of this tutorial. We will not cover the UI design, just coding :)

preview1.png

Project setup PAGE TOP

Create new project in XCode that will use single view application template.

image2.png

In Project navigator select your project and under Build Phases click + at end of Link Binary With Libraries to add additional frameworks:

image3.png

Frameworks we need are following: - AVFoundation.framework – used for managing and playing audio-visual media - AudioToolbox.framework - - Accelerate.framework

AVFoundation framework is used for managing and playing audio-visual media. Audio Toolbox framework provides interfaces for recording, playback, stream parsing and managing audio sessions. Accelerate framework contains C APIs for vector and matrix math, digital signal processing, large number handling, and image processing.

As you add listed frameworks list should look like this:

image4.png

Programming PAGE TOP

Include required frameworks in header file (.h) of your main view controller.

#import <AudioToolbox/AudioToolbox.h>
#import <Accelerate/Accelerate.h>
#import <AVFoundation/AVFoundation.h>

Adopt following protocols:

  • AVAudioPlayerDelegate
  • AVAudioRecorderDelegate
  • AVAudioSessionDelegate

And define following global variables:

NSURL *soundFileURL;
NSURL *outputSoundFileURL;
BOOL recording;
BOOL playing;
AVAudioRecorder *soundRecorder;
AVAudioPlayer *soundPlayer;
NSTimer *sliderTimer;
float speedFactor;

NSURLs soundFileURL and outputSoundFileURL will be used to define URLs to sound file and output sound file. Bool values recording and playing are used to determine are we recording or playing. AVAudioRecorder is used for sound recording and AVAudioPlayer is used for sound playing. NSTimer will update slider to show current sound time. Float value speedFactor is set by user and it represent value how fast will some sound be replayed.

Define 4 IBOutlets that we will later on connect on Interface Builder.

@property (retain, nonatomic) IBOutlet UIButton *recordOrStopButton;
@property (retain, nonatomic) IBOutlet UIButton *playOrStopButton;
@property (retain, nonatomic) IBOutlet UISlider *progressSlider;
@property (retain, nonatomic) IBOutlet UILabel *speedFactorLabel;

And last thing to define in header file is method modifySpeedOf.

- (void)modifySpeedOf:(CFURLRef)inputURL byFactor:(float)factor
andWriteTo:(CFURLRef)outputURL;

In main file (.m) first we'll need to synthesize defined IBOutlets.

@synthesize recordOrStopButton;
@synthesize playOrStopButton;
@synthesize progressSlider;
@synthesize speedFactorLabel;

Add dealloc method and release all allocated items:

- (void)dealloc {
   [recordOrStopButton release];
   [playOrStopButton release];
   [progressSlider release];
   [speedFactorLabel release];
   [super dealloc];
}

Create the Sound Path PAGE TOP

In viewDidLoad method add following lines to create sound paths and URLs for recorded audio file and output audio file (the modified audio file). Also we create audio session and we set our bools that we are not recording and not playing when application loads. Variable speedFactor is initially set to 1.

NSString *tempDir = NSTemporaryDirectory();
NSString *soundFilePath = [tempDir stringByAppendingString: @"sound.caf"];
NSString *outputSoundFilePath = [tempDir stringByAppendingString:@"soundO.caf"];

soundFileURL = [[NSURL alloc] initFileURLWithPath:soundFilePath];
outputSoundFileURL = [[NSURL alloc] initFileURLWithPath:outputSoundFilePath];

AVAudioSession *audioSession = [AVAudioSession sharedInstance];
audioSession.delegate = self;
[audioSession setActive:YES error:nil];

recording = NO;
playing = NO;
speedFactor = 1;

Set IBOutlets to nil in viewDidUnload method:

- (void)viewDidUnload
{
   [super viewDidUnload];
   [self setRecordOrStopButton:nil];
   [self setPlayOrStopButton:nil];
   [self setProgressSlider:nil];
   [self setSpeedFactorLabel:nil];
}

Set the Play/Stop State PAGE TOP

Now add AVAudioRecorderDelegate method audioRecorderDidFinishRecording.

If bool recording was true we'll stop soundRecorder and set this bool to NO. We also have to release soundRecorder and we set it to nil. We set our playOrStopButton to enabled and we set selected state of our recordOrStopButton to NO. This selected state we gonna use to show title Stop on button so user can stop recording, same thing we gonna do with playing button. We also set our audio session to inactive state. If flag is true, which means audio was recorded successfully we'll perform selector to update slider and we invalidate sliderTimer.

- (void)audioRecorderDidFinishRecording:(AVAudioRecorder *)recorder successfully:(BOOL)flag {
   if (recording) {
       [soundRecorder stop];
       recording = NO;
       [soundRecorder release];
       soundRecorder = nil;

       [playOrStopButton setEnabled:YES];
       [recordOrStopButton setSelected:NO];
       [[AVAudioSession sharedInstance] setActive:NO error:nil];
   }

   if (flag) {
       [self performSelector:@selector(updateSlider) withObject:nil afterDelay:0.1];
       [sliderTimer invalidate];
   }
}

Check if the Audio is Played Succesfully PAGE TOP

Following same logic we implement AVAudioPlayerDelegate delegate method audioPlayerDidFinishPlaying that tells us is audio file played successfully:

- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag {
   if (playing) {
       [soundPlayer stop];
       playing = NO;
       [soundPlayer release];
       soundPlayer = nil;

       [recordOrStopButton setEnabled:YES];
       [playOrStopButton setSelected:NO];
       [[AVAudioSession sharedInstance] setActive: NO error: nil];
   }

   if (flag) {
       progressSlider.value = progressSlider.maximumValue;
       [self performSelector:@selector(updateSlider) withObject:nil afterDelay:0.1];
       [sliderTimer invalidate];
   }
}

Progress Slider and Speed Slider PAGE TOP

Now we add method updateSlider that will set progressSlider value to soundPlayer current time if audio is playing or it'll show soundRecorder current time if audio is recording. If it's not playing or recording than progressSlider have minimal value.

- (void)updateSlider {
   if (playing) {
       progressSlider.value = soundPlayer.currentTime;
   }
   if (recording) {
       progressSlider.value = soundRecorder.currentTime;
   }
   if (!playing && !recording) {
       progressSlider.value = progressSlider.minimumValue;
   }
}

Another slider method is speedFactorSliderValueChanged that will set label to show value of our speedFactor variable:

- (IBAction)speedFactorSliderValueChanged:(id)sender {
   UISlider *slider = (UISlider*)sender;
   speedFactor = slider.value;
   [speedFactorLabel setText:[NSString stringWithFormat:@"%.2f", speedFactor]];
}

Record, Play and Stop Button PAGE TOP

Now we need button methods for record button and for play button. Record button will use recordOrStopButtonClick method, where we gonna determine are we recording at the moment. If so, than we stop recording, but in other case we set up AVAudioSession and we allocate AVAudioRecorder with NSDictionary that contain keys:

  • AVSampleRateKey
  • AVFormatIDKey
  • AVNumberOfChannelsKey
  • AVEncoderAudioQualityKey

Here we also set that maximum recording is 5 seconds.

- (IBAction)recordOrStopButtonClick:(id)sender {
   if (recording) {
       [soundRecorder stop];
       recording = NO;
       [soundRecorder release];
       soundRecorder = nil;
       [playOrStopButton setEnabled:YES];
       [recordOrStopButton setSelected:NO];
       [[AVAudioSession sharedInstance] setActive:NO error:nil];
   } else {
       [[AVAudioSession sharedInstance] 
          setCategory:AVAudioSessionCategoryPlayAndRecord
          error: nil];

       NSDictionary *recordSettings = [[NSDictionary alloc] initWithObjectsAndKeys:
                [NSNumber numberWithFloat:44100.0], AVSampleRateKey,
                [NSNumber numberWithInt:kAudioFormatLinearPCM], AVFormatIDKey,             
                [NSNumber numberWithInt: 1], AVNumberOfChannelsKey,
                [NSNumber numberWithInt:AVAudioQualityMax], AVEncoderAudioQualityKey,
                                  nil];

       soundRecorder = [[AVAudioRecorder alloc] initWithURL: soundFileURL
                                                   settings: recordSettings
                                                      error: nil];

       [recordSettings release];

       soundRecorder.delegate = self;
       sliderTimer = [NSTimer scheduledTimerWithTimeInterval:0.2
                                             target:self selector:@selector(updateSlider)
                                             userInfo:nil repeats:YES];
       progressSlider.maximumValue = 5;
       [soundRecorder recordForDuration:5];
       [playOrStopButton setEnabled:NO];
       [recordOrStopButton setSelected:YES];

       recording = YES;
   }
}

Method playOrStopButtonClick assigned to play button use similar logic. This method will stop playing if bool variable playing is set to true, otherwise it'll execute method modifySpeedOf and play modified output audio file.

- (IBAction)playOrStopButtonClick:(id)sender {
   if (playing) {
       [soundPlayer stop];
       playing = NO;
       [soundPlayer release];
       soundPlayer = nil;
       [recordOrStopButton setEnabled:YES];
       [playOrStopButton setSelected:NO];
       [[AVAudioSession sharedInstance] setActive:NO error:nil];
   } else {
       [[AVAudioSession sharedInstance]
          setCategory:AVAudioSessionCategoryPlayAndRecord error:nil];

       sliderTimer = [NSTimer scheduledTimerWithTimeInterval:0.2
                             target:self selector:@selector(updateSlider) userInfo:nil
                             repeats:YES];

       [self modifySpeedOf:(CFURLRef)soundFileURL
              byFactor:speedFactor andWriteTo:(CFURLRef)outputSoundFileURL];

       soundPlayer = [[AVAudioPlayer alloc] 
                               initWithContentsOfURL:outputSoundFileURL
                               error:nil];
       soundPlayer.delegate = self;
       progressSlider.maximumValue = soundPlayer.duration;
       [soundPlayer prepareToPlay];
       [soundPlayer play];

       [recordOrStopButton setEnabled:NO];
       [playOrStopButton setSelected:YES];

       playing = YES;
   }
}

Modify Speed PAGE TOP

Method modifySpeedOf that you can see below use recorded audio to change it by factor value we set and writes it to output file. We define destFormat variable and change its sample rate by factor value. So main trick here is that if we multiply sample rate by value lower than 1.0 output audio will be longer and voice we recorded will sound deeper. Otherwise if we multiply by value higher than 1.0 output audio will sound higher, just like in games we mention.

- (void)modifySpeedOf:(CFURLRef)inputURL byFactor:(float)factor
andWriteTo:(CFURLRef)outputURL {

   ExtAudioFileRef inputFile = NULL;
   ExtAudioFileRef outputFile = NULL;

   AudioStreamBasicDescription destFormat;

   destFormat.mFormatID = kAudioFormatLinearPCM;
   destFormat.mFormatFlags = kAudioFormatFlagsCanonical;
   destFormat.mSampleRate = 44100 * factor;
   destFormat.mBytesPerPacket = 2;
   destFormat.mFramesPerPacket = 1;
   destFormat.mBytesPerFrame = 2;
   destFormat.mChannelsPerFrame = 1;
   destFormat.mBitsPerChannel = 16;
   destFormat.mReserved = 0;

   ExtAudioFileCreateWithURL(outputURL, kAudioFileCAFType,
       &destFormat, NULL, kAudioFileFlags_EraseFile, &outputFile);

   ExtAudioFileOpenURL(inputURL, &inputFile);

   //find out how many frames is this file long
   SInt64 length = 0;
   UInt32 dataSize2 = (UInt32)sizeof(length);
   ExtAudioFileGetProperty(inputFile,
       kExtAudioFileProperty_FileLengthFrames, &dataSize2, &length);

   SInt16 *buffer = malloc(kBufferSize * sizeof(SInt16));

   UInt32 totalFramecount = 0;

   AudioBufferList bufferList;
   bufferList.mNumberBuffers = 1;
   bufferList.mBuffers[0].mNumberChannels = 1;
   bufferList.mBuffers[0].mData = buffer; // pointer to buffer of audio data
   bufferList.mBuffers[0].mDataByteSize = kBufferSize *
      sizeof(SInt16); // number of bytes in the buffer

   while(true) {

       UInt32 frameCount = kBufferSize * sizeof(SInt16) / 2;
       // Read a chunk of input
       ExtAudioFileRead(inputFile, &frameCount, &bufferList);
       totalFramecount += frameCount;

       if (!frameCount || totalFramecount >= length) {
           //termination condition
           break;
       }
       ExtAudioFileWrite(outputFile, frameCount, &bufferList);
   }

   free(buffer);

   ExtAudioFileDispose(inputFile);
   ExtAudioFileDispose(outputFile);

}

Interface Builder PAGE TOP

Since we defined everything in code it is time to set up our xib. Create 2 UIButtons, 2 UISliders and one UILabel:

  • UIButton – record button
  • UIButton – play button
  • UISlider – progress slider
  • UISlider – slider to set speed
  • UILabel – label to display current speed

image5.png

First connect defined IBOutlets with record and play buttons, progress slider and speed factor label. Than connect record button with recordOrStopButtonClick method and connect play button with playOrStopButtonClick method. Also connect slider to set speed event value changed with speedFactorSliderValueChanged method.

Set speed label to „1.0“ since that is initial value we use in code. On image above there is additional label with text „Current speed“.

For Record and Play buttons set title for selected state to „Stop“ because we use selected state in code.

image6.png

Application is now ready for testing, have fun modifying your voice.

Download the improved TalkingApp iOS Project Source Code PAGE TOP

preview2.png

We created an improved project of what we covered above adding a cool UI and features like:

  • Record any sound
  • Playback recorded sound
  • Alter recorded sound speed
  • Share original or altered sound on Facebook or Twitter
  • application display HUD when sharing
  • iPhone 4G & 4S Retina support
  • and much more...

Download the Source Code here.

9 Comments Leave a comment

Please login in order to leave a comment.

Newest first
  • iphone 2014-02-01 08:17:53 Thread id 58

    I want to make an applications similar to Talking Tom Cat, Touch Pets Cats/, Virtual Monkey and the Tap Zoo and cat action like crying and animation .

    I have knowledge of Iphone basic .

    kindly help out me

  • laurentmikhail 2013-07-25 00:18:18 Thread id 47

    I have a problem with the JSON framework. If i upload the framework which is now called JBSon, then several errors come out because some names have changed. Please advise

  • Kalaichelvan 2013-07-10 16:55:47 Thread id 42

    Add the following line in your view controller

    #define kBufferSize 2048

  • Dutzuway 2013-05-20 11:27:26 Thread id 37

    same here - KBufferSize; error with it. anybody knows the resolution>?

  • ashokdy 2013-05-10 10:03:26 Thread id 36

    Hi Thanks for the tutorial but it is showing an error for kBufferSize where to decalre that and what type it is? please reply me

  • Jessentow 2012-07-10 10:25:03 Thread id 14

    where can i get the development tools.

  • abdo.alteer 2012-07-03 20:14:07 Thread id 13

    tnx

  • fablord 2012-05-23 08:01:48 Thread id 11

    I love that app, didn know it was so easy to use implement its functionalities. Thanks for making in available eand explaining how to do!

  • AppStrong 2012-05-14 16:35:37 Thread id 7

    Amazing Tutorial! Thanks for sharing.

!