Re: Streaming using coreaudio
Re: Streaming using coreaudio
- Subject: Re: Streaming using coreaudio
- From: "Hamish Allan" <email@hidden>
- Date: Tue, 13 Jan 2009 22:57:52 +0000
On Tue, Jan 13, 2009 at 10:22 PM, John Michael Zorko <email@hidden> wrote:
> If NSURLConnection is reading as fast as it can (and caching all that it's
> read until I consume it), and I can only consume it at a certain rate (say,
> for a 128K MP3 stream), NSURLConnection's ease of use ends up costing more
> than one might expect (and for low-memory platforms like the iPhone, quite
> possibly more than it's worth -- the iPhone OS kept sending my app memory
> warnings when I tried to stream a long-ish piece of audio).
Ah, sorry, I should have been clearer: the OP mentioned shoutcast, so
I assumed we were talking about a stream of indeterminate length
(expectedContentLength == -1), in which production and consumption
rates are necessarily equal.
For streaming finite length audio, it might be easier to download the
data into a file but start playing the file before the download is
finished. An unfinished and unpolished piece of code I wrote for this
purpose is included below; feel free to use this under a BSD license.
Best wishes,
Hamish
//
// FiniteStreamAudioPlayer.h
// FiniteStreamAudioPlayer
//
// Created by Hamish on 18/12/2008.
// Copyright 2008 Hamish Allan. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <AVFoundation/AVFoundation.h>
@class FiniteStreamAudioPlayer;
@protocol FiniteStreamAudioPlayerDelegate
- (void)finiteStreamAudioPlayerDidFinishPlaying:(FiniteStreamAudioPlayer
*)player successfully:(BOOL)flag;
- (void)finiteStreamAudioPlayerDecodeErrorDidOccur:(FiniteStreamAudioPlayer
*)player error:(NSError *)error;
- (void)finiteStreamAudioPlayerBeginInterruption:(FiniteStreamAudioPlayer
*)player;
- (void)finiteStreamAudioPlayerEndInterruption:(FiniteStreamAudioPlayer
*)player;
@end
@interface FiniteStreamAudioPlayer : NSObject <AVAudioPlayerDelegate>
{
NSURL *myUrl;
id myDelegate;
NSURLConnection *myConnection;
NSString *myTemporaryFilePath;
NSFileHandle *myTemporaryFileHandle;
long long myReceivedContentLength;
long long myExpectedContentLength;
NSTimeInterval myAvailableDuration;
BOOL myAutostart;
AVAudioPlayer *myAudioPlayer;
}
@property(assign) id<FiniteStreamAudioPlayerDelegate> delegate;
@property(readonly, getter=isPlaying) BOOL playing;
@property NSTimeInterval currentTime;
@property(readonly) NSTimeInterval minimumAvailableDurationForAutostart;
@property(readonly) NSTimeInterval availableDuration;
@property(readonly) NSTimeInterval duration;
@property BOOL autostart;
@property(readonly) NSURL *url;
- (id)initWithContentsOfURL:(NSURL *)url error:(NSError **)errorPtr;
- (BOOL)prepareToPlay;
- (BOOL)play;
- (void)pause;
- (void)stop;
@end
@interface NSString (SHA256Additions)
- (NSString *)sha256Hash;
@end
//
// FiniteStreamAudioPlayer.m
// FiniteStreamAudioPlayer
//
// Created by Hamish on 18/12/2008.
// Copyright 2008 Hamish Allan. All rights reserved.
//
#import "FiniteStreamAudioPlayer.h"
#import <CommonCrypto/CommonDigest.h>
#import <AVFoundation/AVFoundation.h>
@implementation FiniteStreamAudioPlayer
@synthesize autostart = myAutostart;
- (id)initWithContentsOfURL:(NSURL *)url error:(NSError **)errorPtr
{
self = [super init];
if (self)
{
NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url];
myConnection = [[NSURLConnection alloc]
initWithRequest:urlRequest delegate:self startImmediately:YES];
if (!myConnection)
{
[self release];
return nil;
}
myUrl = [url retain];
self.autostart = YES;
}
return self;
}
- (void)removeTemporaryFile
{
if (myTemporaryFilePath)
[[NSFileManager defaultManager]
removeItemAtPath:myTemporaryFilePath error:NULL];
}
- (void)dealloc
{
[myConnection cancel];
[myConnection autorelease];
[self removeTemporaryFile];
[myUrl release];
[myTemporaryFilePath release];
[myTemporaryFileHandle release];
[myAudioPlayer release];
[super dealloc];
}
- (void)cancelWithError:(NSError *)error
{
[myConnection cancel];
[myConnection release];
myConnection = nil;
[self.delegate finiteStreamAudioPlayerDecodeErrorDidOccur:self error:error];
}
- (void)connection:(NSURLConnection *)connection
didReceiveResponse:(NSURLResponse *)response
{
NSString *urlString = [[response URL] absoluteString];
myExpectedContentLength = [response expectedContentLength];
if (myExpectedContentLength < 0)
{
NSString *errorDescription = NSLocalizedString(@"Stream not
finite", @"FiniteStreamAudioPlayer.m");
NSString *errorFormat = NSLocalizedString(@"The stream at %@
is of indeterminate length", @"FiniteStreamAudioPlayer.m");
NSString *errorReason = [NSString
stringWithFormat:errorFormat, urlString];
NSError *error = [NSError errorWithDomain:NSURLErrorDomain
code:NSURLErrorUnsupportedURL userInfo:
[NSDictionary dictionaryWithObjectsAndKeys:
errorDescription, NSLocalizedDescriptionKey,
errorReason, NSLocalizedFailureReasonErrorKey,
urlString, NSErrorFailingURLStringKey, nil]];
[self cancelWithError:error];
return;
}
NSString *tmpName = [NSString stringWithFormat:@"%@.%@",
[urlString sha256Hash], [urlString pathExtension]];
myTemporaryFilePath = [[NSTemporaryDirectory()
stringByAppendingPathComponent:tmpName] retain];
NSLog(@"myTemporaryFilePath %@", myTemporaryFilePath);
if (![[NSFileManager defaultManager]
createFileAtPath:myTemporaryFilePath contents:[NSData data]
attributes:nil])
{
NSString *errorDescription = NSLocalizedString(@"Unable to
create file", @"FiniteStreamAudioPlayer.m");
NSString *errorReason = NSLocalizedString(@"The audio cache
file cannot be created", @"FiniteStreamAudioPlayer.m");
NSError *error = [NSError errorWithDomain:NSURLErrorDomain
code:NSURLErrorCannotCreateFile userInfo:
[NSDictionary dictionaryWithObjectsAndKeys:
errorDescription, NSLocalizedDescriptionKey,
errorReason, NSLocalizedFailureReasonErrorKey,
urlString, NSErrorFailingURLStringKey, nil]];
[self cancelWithError:error];
return;
}
myTemporaryFileHandle = [[NSFileHandle
fileHandleForWritingAtPath:myTemporaryFilePath] retain];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(applicationWillTerminate:)
name:UIApplicationWillTerminateNotification object:nil];
[myTemporaryFileHandle truncateFileAtOffset:myExpectedContentLength];
[myTemporaryFileHandle seekToFileOffset:0];
}
- (void)applicationWillTerminate:(NSNotification *)notification
{
[self removeTemporaryFile];
}
- (void)connection:(NSURLConnection*)connection didFailWithError:(NSError*)error
{
NSLog(@"didFailWithError %@", error);
}
- (void)connection:(NSURLConnection*)connection didReceiveData:(NSData*)data
{
NSLog(@"didReceiveData length %d", [data length]);
[myTemporaryFileHandle writeData:data];
myReceivedContentLength += [data length];
if (!myAudioPlayer && myReceivedContentLength > 10000) // TODO:
rationalise this
{
NSError *error = nil;
myAudioPlayer = [(AVAudioPlayer *)[AVAudioPlayer alloc]
initWithContentsOfURL:[NSURL fileURLWithPath:myTemporaryFilePath]
error:&error];
if (error)
[self cancelWithError:error];
myAudioPlayer.delegate = self;
}
[self willChangeValueForKey:@"availableDuration"];
myAvailableDuration = (CGFloat)myReceivedContentLength /
(CGFloat)myExpectedContentLength * myAudioPlayer.duration;
if (myAvailableDuration > self.minimumAvailableDurationForAutostart)
[myAudioPlayer play];
[self didChangeValueForKey:@"availableDuration"];
}
- (void)connectionDidFinishLoading:(NSURLConnection*)connection
{
NSLog(@"didFinishLoading");
[myTemporaryFileHandle closeFile];
}
- (id<FiniteStreamAudioPlayerDelegate>)delegate
{
return myDelegate;
}
- (void)setDelegate:(id<FiniteStreamAudioPlayerDelegate>)delegate
{
myDelegate = delegate;
}
- (BOOL)isPlaying
{
return myAudioPlayer.isPlaying;
}
- (NSTimeInterval)currentTime
{
return myAudioPlayer.currentTime;
}
- (void)setCurrentTime:(NSTimeInterval)currentTime
{
myAudioPlayer.currentTime = currentTime;
}
- (NSTimeInterval)minimumAvailableDurationForAutostart
{
// TODO: make this take into account loading speed
if (self.duration < 5.0)
return self.duration;
else
return 5.0;
}
- (NSTimeInterval)availableDuration
{
return myAvailableDuration;
}
- (NSTimeInterval)duration
{
return myAudioPlayer.duration;
}
- (NSURL *)url
{
return myUrl;
}
- (BOOL)prepareToPlay
{
return [myAudioPlayer prepareToPlay];
}
- (BOOL)play
{
return [myAudioPlayer play];
}
- (void)pause
{
[myAudioPlayer pause];
}
- (void)stop
{
[myAudioPlayer stop];
if (myConnection)
[myConnection cancel];
}
- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player
successfully:(BOOL)flag
{
[myDelegate finiteStreamAudioPlayerDidFinishPlaying:self successfully:flag];
}
- (void)audioPlayerDecodeErrorDidOccur:(AVAudioPlayer *)player
error:(NSError *)error;
{
[myDelegate finiteStreamAudioPlayerDecodeErrorDidOccur:self error:error];
}
- (void)audioPlayerBeginInterruption:(AVAudioPlayer *)player
{
[myDelegate finiteStreamAudioPlayerBeginInterruption:self];
}
- (void)audioPlayerEndInterruption:(AVAudioPlayer *)player
{
[myDelegate finiteStreamAudioPlayerEndInterruption:self];
}
@end
@implementation NSString (SHA256Additions)
- (NSString *)sha256Hash
{
unsigned char hash[CC_SHA256_DIGEST_LENGTH];
CC_SHA256([self UTF8String], [self
lengthOfBytesUsingEncoding:NSUTF8StringEncoding], hash);
NSMutableString *result = [NSMutableString
stringWithCapacity:CC_SHA256_DIGEST_LENGTH];
int i;
for (i = 0; i < CC_SHA256_DIGEST_LENGTH; ++i)
[result appendString:[NSString stringWithFormat:@"x", hash[i]]];
return result;
}
@end
_______________________________________________
Do not post admin requests to the list. They will be ignored.
Coreaudio-api mailing list (email@hidden)
Help/Unsubscribe/Update your Subscription:
This email sent to email@hidden