Да, есть, и я сделал это. Моя проблема в том, что я могу воспроизвести его в одном и том же стримере (спрашивается в другом месте). Он будет воспроизводиться с помощью стандартного AVAudioPlayer в iOS. Однако это сохранит данные в файл, записав их в коде стримера.
В этом примере пропущены некоторые проверки ошибок, но вы получите основную идею.
Во-первых, звонок из основного потока, чтобы начать и остановить запись. Это в моем viewController, когда кто-то нажимает запись:
//---------------------------------------------------------
// 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
}
}
}
СЕЙЧАС, в AudioStreamer.m мне нужно обработать вызов настройки записи выше
- (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 & 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
}
}
ПОСЛЕДНЕЕ, нам нужно изменить часть данных стримера, чтобы фактически сохранить байты. Вам необходимо изменить код потока в методе: - (void) handleReadFromStream: (CFReadStreamRef) aStreameventType: (CFStreamEventType) eventType
Прокрутите этот метод вниз, пока не найдете:
@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;
}
ПРАВО после длины = строки, добавьте следующий код:
//
// if recording, save the raw data to a file
//
if(self.isRecording && length != 0){
//
// write the data to a file
//
NSInteger bytesWritten;
NSInteger bytesWrittenSoFar;
bytesWrittenSoFar = 0;
do {
bytesWritten = [self.fileStream write:&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);
}
Вот декларации .h:
Добавлен интерфейс для AudioStreamer.h
// for recording and saving a stream
NSString* filePath;
NSOutputStream* fileStream;
BOOL isRecording;
BOOL isPlayingFile;
В вашем контроллере представления вам понадобится:
@property(nonatomic, assign) IBOutlet UIButton* recordButtonImage;
@property(nonatomic, assign) BOOL isRecording;
@property (nonatomic, copy) NSString* recordFilePath;
Надеюсь, это кому-нибудь поможет. Дайте мне знать, если вопросы, и всегда рады услышать кого-то, кто может улучшить это.
Кроме того, кто-то спросил о self.model.xxx. Модель - это объект данных, который я создал, чтобы позволить мне легко передавать данные, которые используются более чем одним объектом, а также изменяются более чем одним объектом. Я знаю, что глобальные данные - это плохая форма, но бывают случаи, когда доступ к ним становится проще. Я передаю модель данных каждому новому объекту при вызове. Я сохраняю массив каналов, название песни, имя исполнителя и другие данные, связанные с потоком, внутри модели. Я также помещаю любые данные, которые я хочу сохранить через запуски, например настройки, и записываю эту модель данных в файл каждый раз, когда меняются постоянные данные. В этом примере вы можете хранить данные локально. Если вам нужна помощь в прохождении модели, дайте мне знать.