效果圖
DrawView.h
- #import <UIKit/UIKit.h>
- @interface DrawView : UIView
- @property shortshort *drawBuffer;
- @property int dataLen;
- @property floatfloat *outRel;
- @property floatfloat *outImg;
- @property int bias;
- @property int wSize;
- - (void)genKernel;
- @end
DrawView.m
- #import "DrawView.h"
- @implementation DrawView
- #define KSIZE 20
- #define BIAS 10000
- static double fk[KSIZE] = {0};
- static double _filterData[2048];
- - (void)genKernel
- {
- double fc = .05;
- for (int i = 0; i < KSIZE; i++)
- {
- if ((i - KSIZE/2) == 0)fk[i] = 22 * M_PI *fc;
- if ((i - KSIZE/2) != 0 )fk[i] = sin(22 * M_PI * fc * (i - KSIZE/2))/(i - KSIZE/2);
- fk[i] = fk[i] * (0.54 - 0.46 * cos(22 * M_PI * i / KSIZE ));
- }
- double sum = 0;
- for (int m = 0; m < KSIZE; m++)
- sum+=fk[m];
- for (int n = 0; n < KSIZE; n++)
- fk[n]/=sum;
- }
- - (void)improveSpectrum
- {
- memset(_filterData, 0x0, sizeof(double) * 1024);
- short transData[(int)self.wSize];
- memcpy(transData, self.drawBuffer+_bias, _wSize * sizeof(short));
- for (int i = 0; i < _wSize; i++)
- {
- for (int j = 0; j < KSIZE; j++)
- {
- _filterData[i] = _filterData[i] + transData[ i - j] * fk[j];
- }
- }
- }
- - (id)initWithFrame:(CGRect)frame
- {
- self = [super initWithFrame:frame];
- if (self) {
- }
- return self;
- }
- - (void)drawRect:(CGRect)rect
- {
- float delta = 320. / _wSize;
- [self improveSpectrum];
- [[UIColor grayColor] set];
- UIRectFill ([self bounds]);
- CGContextRef currentContext = UIGraphicsGetCurrentContext();
- CGContextBeginPath(currentContext);
- CGContextMoveToPoint(currentContext, 0., 230.);
- CGContextAddLineToPoint(currentContext, 320., 230.);
- [[UIColor blueColor] setStroke];
- CGContextStrokePath(currentContext);
- CGContextBeginPath(currentContext);
- CGContextMoveToPoint(currentContext, 0., 230.);
- for (int i = 0; i < _wSize; i++)
- {
- CGFloat x = i * delta;
- CGFloat y = _filterData[i] / 150.0 + 230.0;
- CGContextAddLineToPoint(currentContext, x, y);
- }
- [[UIColor redColor] setStroke];
- CGContextStrokePath(currentContext);
- }
- @end
ViewController.h
- #import <UIKit/UIKit.h>
- @interface ViewController : UIViewController
- @end
ViewController.m
- #import "ViewController.h"
- #import "DrawView.h"
- struct WavInfo
- {
- int size;
- char *data;
- short channels;
- short block_align;
- short bits_per_sample;
- unsigned long sample_rate;
- unsigned long format_length;
- unsigned long format_tag;
- unsigned long avg_bytes_sec;
- };
- @interface ViewController ()
- @end
- void decodeWaveInfo(const charchar *fname, struct WavInfo *info)
- {
- FILEFILE *fp;
- fp = fopen(fname, "rb");
- if(fp)
- {
- char id[5];
- unsigned long dataSize,size;
- fread(id, sizeof(char), 4, fp);
- id[4]='\0';
- if (!strcmp(id, "RIFF"))
- {
- fread(&size, sizeof(unsigned long), 1, fp);//read file size
- fread(id, sizeof(char), 4, fp);//read wave
- id[4]='\0';
- if (!strcmp(id, "WAVE"))
- {
- fread(id, sizeof(char), 4, fp);
- fread(&info->format_length, sizeof(unsigned long), 1, fp);
- fread(&info->format_tag, sizeof(short), 1, fp);
- fread(&info->channels, sizeof(short), 1, fp);
- fread(&info->sample_rate, sizeof(unsigned long), 1, fp);
- fread(&info->avg_bytes_sec, sizeof(unsigned long), 1, fp);
- fread(&info->block_align, sizeof(short), 1, fp);
- fread(&info->bits_per_sample, sizeof(short), 1, fp);
- fread(id, sizeof(char), 4, fp);
- fread(&dataSize, sizeof(unsigned long), 1, fp);
- info->size = dataSize;
- info->data = ( charchar *)malloc(sizeof(char)*dataSize);
- fread(info->data, sizeof(char), dataSize, fp);
- }
- else
- {
- printf("Error\n");
- }
- }
- else
- {
- printf("Error\n");
- }
- fclose(fp);
- }
- }
- @implementation ViewController
- - (void)viewDidLoad
- {
- [super viewDidLoad];
- NSString *path = [[NSBundle mainBundle] pathForResource:@"effect1" ofType:@"wav"];
- struct WavInfo wavInfo;
- decodeWaveInfo([path UTF8String], &wavInfo);
- DrawView *d = (DrawView *)self.view;
- d.drawBuffer = (shortshort *)malloc(sizeof(short) * wavInfo.size / 2 );
- [d genKernel];
- d.dataLen = wavInfo.size / 2;
- d.wSize = 256;
- d.bias = 0;
- int n = 0;
- for (int m = 0; m < wavInfo.size / 2; m++)
- {
- d.drawBuffer[n++] = wavInfo.data[m * 2 + 1] << 8 | wavInfo.data[m * 2];
- }
- UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(dragView:)];
- [self.view addGestureRecognizer:pan];
- UIPinchGestureRecognizer *pinch = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(pinchView:)];
- [self.view addGestureRecognizer:pinch];
- }
- #pragma mark -
- #pragma mark Gesture Recognizer callback
- - (void)dragView:(UIPanGestureRecognizer *)recognizer
- {
- DrawView *d = (DrawView *)self.view;
- CGPoint p = [recognizer translationInView:self.view];
- NSLog(@"translate point is : %@",NSStringFromCGPoint(p));
- d.bias -= p.x;
- [self.view setNeedsDisplay];
- }
- - (void)pinchView:(UIPinchGestureRecognizer *)recognizer
- {
- DrawView *d = (DrawView *)self.view;
- if (recognizer.scale > 1.0)
- {
- if (d.wSize > 128)
- {
- d.wSize *= 0.95;
- }
- }
- else
- {
- if (d.wSize < 1024)
- {
- d.wSize *= 1.05;
- }
- }
- [self.view setNeedsDisplay];
- }
- - (void)didReceiveMemoryWarning
- {
- [super didReceiveMemoryWarning];
- // Dispose of any resources that can be recreated.
- }
- @end
代碼下載:http://pan.baidu.com/s/1ACWXT
參考:
在 iPhone 應用或者是游戲的開發過程中,對聲音的支持是必不可少的。在我做過的幾個應用中,每個都涉及到音效,所以在這里做個簡單的歸納,很多都是引用自《iPhone Application Programming Guide》(需要有 Apple ID 才能打開鏈接),加了一些實際使用的經驗。
iPhone OS 主要提供以下了幾種播放音頻的方法:
System Sound Services
AVAudioPlayer 類
Audio Queue Services
OpenAL
1. System Sound Services
System Sound Services 是最底層也是最簡單的聲音播放服務,調用 AudioServicesPlaySystemSound 這個方法就可以播放一些簡單的音頻文件,使用此方法只適合播放一些很小的提示或者警告音,因為它有很多限制:
■ 聲音長度要小於 30 秒
■ In linear PCM 或者 IMA4 (IMA/ADPCM) 格式的
■ 打包成 .caf, .aif, 或者 .wav 的文件
■ 不能控制播放的進度
■ 調用方法后立即播放聲音
■ 沒有循環播放和立體聲控制
另外,它還可以調用系統的震動功能,方法也很簡單。具體的代碼可以參考官方的示例 SysSound
,但是官方的示例只有一些簡單的用法,從文檔中我們發現可以通過 AudioServicesAddSystemSoundCompletion 方法為音頻播放添加 CallBack 函數,有了 CallBack 函數我們可以解決不少問題,比如可以克服 System Sound Services 本身不支持循環播放的問題。以下代碼可以實現一個在程序中循環播放的背景音樂:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
static
void
completionCallback (SystemSoundID mySSID) {
// Play again after sound play completion
AudioServicesPlaySystemSound(mySSID);
}
- (
void
) playSound {
// Get the main bundle for the app
CFBundleRef mainBundle;
SystemSoundID soundFileObject;
mainBundle = CFBundleGetMainBundle ();
// Get the URL to the sound file to play
CFURLRef soundFileURLRef = CFBundleCopyResourceURL (
mainBundle,
CFSTR (
"background"
),
CFSTR (
"wav"
),
NULL
);
// Create a system sound object representing the sound file
AudioServicesCreateSystemSoundID (
soundFileURLRef,
&soundFileObject
);
// Add sound completion callback
AudioServicesAddSystemSoundCompletion (soundFileObject, NULL, NULL,
completionCallback,
(
void
*) self);
// Play the audio
AudioServicesPlaySystemSound(soundFileObject);
}
|
2. AVAudioPlayer 類
AVAudioPlayer 是 AVFoundation.framework 中定義的一個類,所以使用要先在工程中引入 AVFoundation.framework。我們可以把 AVAudioPlayer 看作是一個高級的播放器,它支持廣泛的音頻格式,主要是以下這些格式:
■ AAC
■ AMR(AdaptiveMulti-Rate, aformatforspeech)
■ ALAC(AppleLossless)
■ iLBC(internetLowBitrateCodec, anotherformatforspeech)
■ IMA4(IMA/ADPCM)
■ linearPCM(uncompressed)
■ μ-lawanda-law
■ MP3(MPEG-1audiolayer3
AVAudioPlayer 可以播放任意長度的音頻文件、支持循環播放、可以同步播放多個音頻文件、控制播放進度以及從音頻文件的任意一點開始播放等,更高級的功能可以參考 AVAudioPlayer 的文檔。要使用 AVAudioPlayer 的對象播放文件,你只需為其指定一個音頻文件並設定一個實現了 AVAudioPlayerDelegate 協議的 delegate 對象。這里舉一個簡單的例子,和上一個例子一樣,實現一直循環播放的背景音樂:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
- (
void
) playBackgroundSoundEffect {
NSString *soundFilePath =
[[NSBundle mainBundle] pathForResource: @
"background"
ofType: @
"wav"
];
NSURL *fileURL = [[NSURL alloc] initFileURLWithPath: soundFilePath];
AVAudioPlayer *newPlayer =
[[AVAudioPlayer alloc] initWithContentsOfURL: fileURL
error: nil];
[fileURL release];
self.player = newPlayer;
[newPlayer release];
[self.player prepareToPlay];
[self.player setDelegate: self];
self.player.numberOfLoops = -1;
// Loop playback until invoke stop method
[self.player play];
}
|
可以看到,只要將 AVAudioPlayer 的 numberOfLoops 屬性設為負數,音頻文件就會一直循環播放直到調用 stop 方法。
AVAudioPlayer 同樣支持 Callback,這是 AVAudioPlayerDelegate 的一個可選 delegate 方法:
- (void) audioPlayerDidFinishPlaying: (AVAudioPlayer *) player successfully: (BOOL) flag {
if (player == self.player && flag == YES) {
NSLog(@"Playback finish.");
}
}
另外,你可以隨時控制 AVAudioPlayer 對象的播放、暫停以及停止,通過判斷對象的狀態,分別調用 play、pause 和 stop 方法即可:
- (IBAction) playOrPause: (id) sender {
// if playing, pause
if (self.player.playing) {
[self.player pause];
// if stopped or paused, start playing
} else {
[self.player play];
}
雖然 AVAudioPlayer 可以播放很多格式,但是我們在實際開發過程中還是最好使用一些沒有壓縮的格式,比如 WAVE 文件,這樣可以減少系統處理單元的資源占用,以便更好的完成程序的其他功能。另外,在使用 AVAudioPlayer 連續播放 mp3 這類經過壓縮的音頻文件時,在連接處可能出現一定的間隔時間。
3. Audio Queue Services
如果以上兩種音頻播放的解決方案都無法滿足你的需求,那么我想你肯定需要使用 Audio Queue Services。使用 Audio Queue Services 對音頻進行播放,你可以完全實現對聲音的控制。例如,你可以在聲音數據從文件讀到內存緩沖區后對聲音進行一定處理再進行播放,從而實現對音頻的快速/慢速播放的功能。
因為 Audio Queue Services 相對復雜很多,Apple 官方已經把它整理為一本書了,具體可以參考 Audio Queue Services Programming Guide 和 SpeakHere 的程序示例。
4. OpenAL
OpenAL 是一套跨平台的開源的音頻處理接口,與圖形處理的 OpenGL 類似,它為音頻播放提供了一套更加優化的方案。它最適合開發游戲的音效,用法也與其他平台下相同。
iPhone 支持 OpenAL 1.1,我沒有在實際開發中使用過,具體的文檔可以參考 OpenAL 的網站 http://openal.org和 oalTouch 的程序示例。