iOS 画音频波形曲线 根据音频数据版

效果图

iOS   画音频波形曲线 根据音频数据版

DrawView.h

  1. #import <UIKit/UIKit.h>
  2. @interface DrawView : UIView
  3. @property shortshort *drawBuffer;
  4. @property int  dataLen;
  5. @property floatfloat *outRel;
  6. @property floatfloat *outImg;
  7. @property int bias;
  8. @property int wSize;
  9. - (void)genKernel;
  10. @end

DrawView.m

  1. #import "DrawView.h"
  2. @implementation DrawView
  3. #define KSIZE 20
  4. #define BIAS 10000
  5. static double fk[KSIZE] = {0};
  6. static double _filterData[2048];
  7. - (void)genKernel
  8. {
  9. double fc = .05;
  10. for (int i = 0; i < KSIZE; i++)
  11. {
  12. if ((i - KSIZE/2) == 0)fk[i] = 22 * M_PI *fc;
  13. if ((i - KSIZE/2) != 0 )fk[i] = sin(22 * M_PI * fc * (i - KSIZE/2))/(i - KSIZE/2);
  14. fk[i] = fk[i] * (0.54 - 0.46 * cos(22 * M_PI * i / KSIZE ));
  15. }
  16. double sum = 0;
  17. for (int m = 0; m < KSIZE; m++)
  18. sum+=fk[m];
  19. for (int n = 0; n < KSIZE; n++)
  20. fk[n]/=sum;
  21. }
  22. - (void)improveSpectrum
  23. {
  24. memset(_filterData, 0x0, sizeof(double) * 1024);
  25. short transData[(int)self.wSize];
  26. memcpy(transData, self.drawBuffer+_bias, _wSize * sizeof(short));
  27. for (int i = 0; i < _wSize; i++)
  28. {
  29. for (int j = 0; j < KSIZE; j++)
  30. {
  31. _filterData[i] = _filterData[i] + transData[ i - j] * fk[j];
  32. }
  33. }
  34. }
  35. - (id)initWithFrame:(CGRect)frame
  36. {
  37. self = [super initWithFrame:frame];
  38. if (self) {
  39. }
  40. return self;
  41. }
  42. - (void)drawRect:(CGRect)rect
  43. {
  44. float delta = 320. / _wSize;
  45. [self improveSpectrum];
  46. [[UIColor grayColor] set];
  47. UIRectFill ([self bounds]);
  48. CGContextRef currentContext = UIGraphicsGetCurrentContext();
  49. CGContextBeginPath(currentContext);
  50. CGContextMoveToPoint(currentContext, 0., 230.);
  51. CGContextAddLineToPoint(currentContext, 320., 230.);
  52. [[UIColor blueColor] setStroke];
  53. CGContextStrokePath(currentContext);
  54. CGContextBeginPath(currentContext);
  55. CGContextMoveToPoint(currentContext, 0., 230.);
  56. for (int i = 0; i < _wSize; i++)
  57. {
  58. CGFloat x = i * delta;
  59. CGFloat y = _filterData[i] / 150.0 + 230.0;
  60. CGContextAddLineToPoint(currentContext, x, y);
  61. }
  62. [[UIColor redColor] setStroke];
  63. CGContextStrokePath(currentContext);
  64. }
  65. @end

ViewController.h

  1. #import <UIKit/UIKit.h>
  2. @interface ViewController : UIViewController
  3. @end

ViewController.m

  1. #import "ViewController.h"
  2. #import "DrawView.h"
  3. struct WavInfo
  4. {
  5. int   size;
  6. char  *data;
  7. short channels;
  8. short block_align;
  9. short bits_per_sample;
  10. unsigned long sample_rate;
  11. unsigned long format_length;
  12. unsigned long format_tag;
  13. unsigned long avg_bytes_sec;
  14. };
  15. @interface ViewController ()
  16. @end
  17. void decodeWaveInfo(const charchar *fname, struct WavInfo *info)
  18. {
  19. FILEFILE *fp;
  20. fp = fopen(fname, "rb");
  21. if(fp)
  22. {
  23. char id[5];
  24. unsigned long dataSize,size;
  25. fread(id, sizeof(char), 4, fp);
  26. id[4]='\0';
  27. if (!strcmp(id, "RIFF"))
  28. {
  29. fread(&size, sizeof(unsigned long), 1, fp);//read file size
  30. fread(id, sizeof(char), 4, fp);//read wave
  31. id[4]='\0';
  32. if (!strcmp(id, "WAVE"))
  33. {
  34. fread(id, sizeof(char), 4, fp);
  35. fread(&info->format_length, sizeof(unsigned long), 1, fp);
  36. fread(&info->format_tag, sizeof(short), 1, fp);
  37. fread(&info->channels, sizeof(short), 1, fp);
  38. fread(&info->sample_rate, sizeof(unsigned long), 1, fp);
  39. fread(&info->avg_bytes_sec, sizeof(unsigned long), 1, fp);
  40. fread(&info->block_align, sizeof(short), 1, fp);
  41. fread(&info->bits_per_sample, sizeof(short), 1, fp);
  42. fread(id, sizeof(char), 4, fp);
  43. fread(&dataSize, sizeof(unsigned long), 1, fp);
  44. info->size = dataSize;
  45. info->data = ( charchar *)malloc(sizeof(char)*dataSize);
  46. fread(info->data, sizeof(char), dataSize, fp);
  47. }
  48. else
  49. {
  50. printf("Error\n");
  51. }
  52. }
  53. else
  54. {
  55. printf("Error\n");
  56. }
  57. fclose(fp);
  58. }
  59. }
  60. @implementation ViewController
  61. - (void)viewDidLoad
  62. {
  63. [super viewDidLoad];
  64. NSString *path = [[NSBundle mainBundle] pathForResource:@"effect1" ofType:@"wav"];
  65. struct WavInfo wavInfo;
  66. decodeWaveInfo([path UTF8String], &wavInfo);
  67. DrawView *d = (DrawView *)self.view;
  68. d.drawBuffer = (shortshort *)malloc(sizeof(short) * wavInfo.size / 2 );
  69. [d genKernel];
  70. d.dataLen = wavInfo.size / 2;
  71. d.wSize = 256;
  72. d.bias = 0;
  73. int n = 0;
  74. for (int m = 0; m < wavInfo.size / 2; m++)
  75. {
  76. d.drawBuffer[n++] = wavInfo.data[m * 2 + 1] << 8 | wavInfo.data[m * 2];
  77. }
  78. UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(dragView:)];
  79. [self.view addGestureRecognizer:pan];
  80. UIPinchGestureRecognizer *pinch = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(pinchView:)];
  81. [self.view addGestureRecognizer:pinch];
  82. }
  83. #pragma mark -
  84. #pragma mark Gesture Recognizer callback
  85. - (void)dragView:(UIPanGestureRecognizer *)recognizer
  86. {
  87. DrawView *d = (DrawView *)self.view;
  88. CGPoint p = [recognizer translationInView:self.view];
  89. NSLog(@"translate point is : %@",NSStringFromCGPoint(p));
  90. d.bias -= p.x;
  91. [self.view setNeedsDisplay];
  92. }
  93. - (void)pinchView:(UIPinchGestureRecognizer *)recognizer
  94. {
  95. DrawView *d = (DrawView *)self.view;
  96. if (recognizer.scale > 1.0)
  97. {
  98. if (d.wSize > 128)
  99. {
  100. d.wSize *= 0.95;
  101. }
  102. }
  103. else
  104. {
  105. if (d.wSize < 1024)
  106. {
  107. d.wSize *= 1.05;
  108. }
  109. }
  110. [self.view setNeedsDisplay];
  111. }
  112. - (void)didReceiveMemoryWarning
  113. {
  114. [super didReceiveMemoryWarning];
  115. // Dispose of any resources that can be recreated.
  116. }
  117. @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 的程序示例。

上一篇:Loj #3044. 「ZJOI2019」Minimax 搜索


下一篇:Loj #3045. 「ZJOI2019」开关