Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    text
    copied!<p>Yes, there is and I have done it. My problem is being able to play it back IN the same streamer (asked elsewhere). It will play back with the standard AVAudioPlayer in iOS. However, this will save the data to a file by writing it out in the streamer code.</p> <p>This example is missing some error checks, but will give you the main idea. </p> <p>First, a call from the main thread to start and stop recording. This is in my viewController when someone presses record: </p> <pre><code>//--------------------------------------------------------- // Record button was pressed (toggle on/off) // writes a file to the documents directory using date and time for the name //--------------------------------------------------------- -(IBAction)recordButton:(id)sender { // only start if the streamer is playing (self.streamer is my streamer instance) if ([self.streamer isPlaying]) { NSDate *currentDateTime = [NSDate date]; // get current date and time NSDateFormatter *dateFormatter = [[[NSDateFormatter alloc] init] autorelease]; [dateFormatter setDateFormat:@"EEEE MMMM d YYYY 'at' HH:mm:ss"]; NSString *dateString = [dateFormatter stringFromDate:currentDateTime]; self.isRecording = !self.isRecording; // toggle recording state BOOL if (self.isRecording) { // start recording here // change the record button to show it is recording - this is an IBOutlet [self.recordButtonImage setImage:[UIImage imageNamed:@"Record2.png"] forState:0]; // call AudioStreamer to start recording. It returns the file pointer back // self.recordFilePath = [self.streamer recordStream:TRUE fileName:dateString]; // start file stream and get file pointer } else { //stop recording here // change the button back [self.recordButtonImage setImage:[UIImage imageNamed:@"Record.png"] forState:0]; // call streamer code, stop the recording. Also returns the file path again. self.recordFilePath = [self.streamer recordStream:FALSE fileName:nil]; // stop stream and get file pointer // add to "recorded files" for selecting a recorderd file later. // first, add channel, date, time dateString = [NSString stringWithFormat:@"%@ Recorded on %@",self.model.stationName, dateString]; // used to identify the item in a list laster // the dictionary will be used to hold the data on this recording for display elsewhere NSDictionary *row1 = [[[NSDictionary alloc] initWithObjectsAndKeys: self.recordFilePath, @"path", dateString, @"dateTime", nil] autorelease]; // save the stream info in an array of recorded Streams if (self.model.recordedStreamsArray == nil) { self.model.recordedStreamsArray = [[NSMutableArray alloc] init]// init the array } [self.model.recordedStreamsArray addObject:row1]; // dict for this recording } } } </code></pre> <p>NOW, in AudioStreamer.m I need to handle the record setup call above</p> <pre><code>- (NSString*)recordStream:(BOOL)record fileName:(NSString *)fileName { // this will start/stop recording, and return the file pointer if (record) { if (state == AS_PLAYING) { // now open a file to save the data into NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); NSString *documentsDirectory = [paths objectAtIndex:0]; // will call this an mp3 file for now (this may need to change) NSMutableString *temp = [NSMutableString stringWithString:[documentsDirectory stringByAppendingFormat:@"/%@.mp3",fileName]]; // remove the ':' in the time string, and create a file name w/ time &amp; date [temp replaceOccurrencesOfString:@":" withString:@"" options:NSLiteralSearch range:NSMakeRange(0, [temp length])]; self.filePath = temp; // file name is date time generated. NSLog(@"Stream Save File Open = %@", self.filePath); // open the recording file stream output self.fileStream = [NSOutputStream outputStreamToFileAtPath:self.filePath append:NO]; [self.fileStream open]; NSLog(@"recording to %@", self.fileStream); self.isRecording = TRUE; return (self.filePath); // if started, send back the file path } return (nil); // if not started, return nil for error checking } else { // save the stream here to a file. // we are done, close the stream. if (self.fileStream != nil) { [self.fileStream close]; self.fileStream = nil; } NSLog(@"stop recording"); self.isRecording = FALSE; return (self.filePath); // when stopping, return nil } } </code></pre> <p>LASTLY, we need to modify the data portion of the streamer to actually save the bytes. You need to modify the stream code in the method: -(void)handleReadFromStream:(CFReadStreamRef)aStreameventType:(CFStreamEventType)eventType Scroll down in that method until you find: </p> <pre><code>@synchronized(self) { if ([self isFinishing] || !CFReadStreamHasBytesAvailable(stream)) { return; } // // Read the bytes from the stream // length = CFReadStreamRead(stream, bytes, kAQDefaultBufSize); if (length == -1) { [self failWithErrorCode:AS_AUDIO_DATA_NOT_FOUND]; return; } </code></pre> <p>RIGHT after the length = line, add the following code:</p> <pre><code> // // if recording, save the raw data to a file // if(self.isRecording &amp;&amp; length != 0){ // // write the data to a file // NSInteger bytesWritten; NSInteger bytesWrittenSoFar; bytesWrittenSoFar = 0; do { bytesWritten = [self.fileStream write:&amp;bytes[bytesWrittenSoFar] maxLength:length - bytesWrittenSoFar]; NSLog(@"bytesWritten = %i",bytesWritten); if (bytesWritten == -1) { [self.fileStream close]; self.fileStream = nil; NSLog(@"File write error"); break; } else { bytesWrittenSoFar += bytesWritten; } } while (bytesWrittenSoFar != length); } </code></pre> <p>Here are the .h declarations: </p> <p>Added to the interface for AudioStreamer.h</p> <pre><code>// for recording and saving a stream NSString* filePath; NSOutputStream* fileStream; BOOL isRecording; BOOL isPlayingFile; </code></pre> <p>In your view controller you will need: </p> <pre><code>@property(nonatomic, assign) IBOutlet UIButton* recordButtonImage; @property(nonatomic, assign) BOOL isRecording; @property (nonatomic, copy) NSString* recordFilePath; </code></pre> <p>Hope this helps someone. Let me know if questions, and always happy to hear someone who can improve this.</p> <p>Also, someone asked about self.model.xxx Model is a Data Object I created to allow me to easily pass around data that is used by more than one object, and is also modified by more than one object. I know, global data is bad form, but there are times that just make it easier to access. I pass the data model to each new object when called. I save an array of channels, song name, artist name, and other stream related data inside the model. I also put any data I want to persist through launches here, like settings, and write this data model to a file each time a persistent data is changed. IN this example, you can keep the data locally. If you need help on the model passing, let me know.</p>
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload