Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

WaveForm on IOS

I'm looking for how to draw the sound amplitude.

I found http://supermegaultragroovy.com/2009/10/06/drawing-waveforms/ but i have some problems. How get a list of floating-point values representing the audio?

like image 444
Igor Shubin Avatar asked Nov 28 '11 16:11

Igor Shubin


People also ask

What is a song waveform?

A waveform is a song's fingerprint. It is this waveform, or convolution of acoustic (sound) waves, that contains all of a song's content. From it, a rich array of features can be revealed.


2 Answers

Thank all.

I found this example here: Drawing waveform with AVAssetReader , changed it and developed a new class based on.

This class returns UIImageView.

//.h file #import <UIKit/UIKit.h>  @interface WaveformImageVew : UIImageView{  } -(id)initWithUrl:(NSURL*)url; - (NSData *) renderPNGAudioPictogramLogForAssett:(AVURLAsset *)songAsset; @end   //.m file #import "WaveformImageVew.h"  #define absX(x) (x<0?0-x:x) #define minMaxX(x,mn,mx) (x<=mn?mn:(x>=mx?mx:x)) #define noiseFloor (-50.0) #define decibel(amplitude) (20.0 * log10(absX(amplitude)/32767.0)) #define imgExt @"png" #define imageToData(x) UIImagePNGRepresentation(x)  @implementation WaveformImageVew  -(id)initWithUrl:(NSURL*)url{     if(self = [super init]){         AVURLAsset * urlA = [AVURLAsset URLAssetWithURL:url options:nil];         [self setImage:[UIImage imageWithData:[self renderPNGAudioPictogramLogForAssett:urlA]]];     }     return self; }  -(UIImage *) audioImageLogGraph:(Float32 *) samples                    normalizeMax:(Float32) normalizeMax                     sampleCount:(NSInteger) sampleCount                     channelCount:(NSInteger) channelCount                     imageHeight:(float) imageHeight {      CGSize imageSize = CGSizeMake(sampleCount, imageHeight);     UIGraphicsBeginImageContext(imageSize);     CGContextRef context = UIGraphicsGetCurrentContext();      CGContextSetFillColorWithColor(context, [UIColor blackColor].CGColor);     CGContextSetAlpha(context,1.0);     CGRect rect;     rect.size = imageSize;     rect.origin.x = 0;     rect.origin.y = 0;      CGColorRef leftcolor = [[UIColor whiteColor] CGColor];     CGColorRef rightcolor = [[UIColor redColor] CGColor];      CGContextFillRect(context, rect);      CGContextSetLineWidth(context, 1.0);      float halfGraphHeight = (imageHeight / 2) / (float) channelCount ;     float centerLeft = halfGraphHeight;     float centerRight = (halfGraphHeight*3) ;      float sampleAdjustmentFactor = (imageHeight/ (float) channelCount) / (normalizeMax - noiseFloor) / 2;      for (NSInteger intSample = 0 ; intSample < sampleCount ; intSample ++ ) {         Float32 left = *samples++;         float pixels = (left - noiseFloor) * sampleAdjustmentFactor;         CGContextMoveToPoint(context, intSample, centerLeft-pixels);         CGContextAddLineToPoint(context, intSample, centerLeft+pixels);         CGContextSetStrokeColorWithColor(context, leftcolor);         CGContextStrokePath(context);          if (channelCount==2) {             Float32 right = *samples++;             float pixels = (right - noiseFloor) * sampleAdjustmentFactor;             CGContextMoveToPoint(context, intSample, centerRight - pixels);             CGContextAddLineToPoint(context, intSample, centerRight + pixels);             CGContextSetStrokeColorWithColor(context, rightcolor);             CGContextStrokePath(context);          }     }      // Create new image     UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();      // Tidy up     UIGraphicsEndImageContext();         return newImage; }    - (NSData *) renderPNGAudioPictogramLogForAssett:(AVURLAsset *)songAsset {      NSError * error = nil;          AVAssetReader * reader = [[AVAssetReader alloc] initWithAsset:songAsset error:&error];      AVAssetTrack * songTrack = [songAsset.tracks objectAtIndex:0];      NSDictionary* outputSettingsDict = [[NSDictionary alloc] initWithObjectsAndKeys:                                          [NSNumber numberWithInt:kAudioFormatLinearPCM],AVFormatIDKey,                                         //     [NSNumber numberWithInt:44100.0],AVSampleRateKey, /*Not Supported*/                                         //     [NSNumber numberWithInt: 2],AVNumberOfChannelsKey,    /*Not Supported*/                                          [NSNumber numberWithInt:16],AVLinearPCMBitDepthKey,                                         [NSNumber numberWithBool:NO],AVLinearPCMIsBigEndianKey,                                         [NSNumber numberWithBool:NO],AVLinearPCMIsFloatKey,                                         [NSNumber numberWithBool:NO],AVLinearPCMIsNonInterleaved,                                          nil];       AVAssetReaderTrackOutput* output = [[AVAssetReaderTrackOutput alloc] initWithTrack:songTrack outputSettings:outputSettingsDict];      [reader addOutput:output];     [output release];      UInt32 sampleRate,channelCount;      NSArray* formatDesc = songTrack.formatDescriptions;     for(unsigned int i = 0; i < [formatDesc count]; ++i) {         CMAudioFormatDescriptionRef item = (CMAudioFormatDescriptionRef)[formatDesc objectAtIndex:i];         const AudioStreamBasicDescription* fmtDesc = CMAudioFormatDescriptionGetStreamBasicDescription (item);         if(fmtDesc ) {              sampleRate = fmtDesc->mSampleRate;             channelCount = fmtDesc->mChannelsPerFrame;              //    NSLog(@"channels:%u, bytes/packet: %u, sampleRate %f",fmtDesc->mChannelsPerFrame, fmtDesc->mBytesPerPacket,fmtDesc->mSampleRate);         }     }      UInt32 bytesPerSample = 2 * channelCount;     Float32 normalizeMax = noiseFloor;     NSLog(@"normalizeMax = %f",normalizeMax);     NSMutableData * fullSongData = [[NSMutableData alloc] init];     [reader startReading];          UInt64 totalBytes = 0;       Float64 totalLeft = 0;     Float64 totalRight = 0;     Float32 sampleTally = 0;      NSInteger samplesPerPixel = sampleRate / 50;          while (reader.status == AVAssetReaderStatusReading){          AVAssetReaderTrackOutput * trackOutput = (AVAssetReaderTrackOutput *)[reader.outputs objectAtIndex:0];         CMSampleBufferRef sampleBufferRef = [trackOutput copyNextSampleBuffer];          if (sampleBufferRef){             CMBlockBufferRef blockBufferRef = CMSampleBufferGetDataBuffer(sampleBufferRef);              size_t length = CMBlockBufferGetDataLength(blockBufferRef);             totalBytes += length;               NSAutoreleasePool *wader = [[NSAutoreleasePool alloc] init];              NSMutableData * data = [NSMutableData dataWithLength:length];             CMBlockBufferCopyDataBytes(blockBufferRef, 0, length, data.mutableBytes);               SInt16 * samples = (SInt16 *) data.mutableBytes;             int sampleCount = length / bytesPerSample;             for (int i = 0; i < sampleCount ; i ++) {                  Float32 left = (Float32) *samples++;                 left = decibel(left);                 left = minMaxX(left,noiseFloor,0);                  totalLeft  += left;                    Float32 right;                 if (channelCount==2) {                     right = (Float32) *samples++;                     right = decibel(right);                     right = minMaxX(right,noiseFloor,0);                      totalRight += right;                 }                  sampleTally++;                  if (sampleTally > samplesPerPixel) {                      left  = totalLeft / sampleTally;                      if (left > normalizeMax) {                         normalizeMax = left;                     }                                         // NSLog(@"left average = %f, normalizeMax = %f",left,normalizeMax);                                          [fullSongData appendBytes:&left length:sizeof(left)];                      if (channelCount==2) {                         right = totalRight / sampleTally;                            if (right > normalizeMax) {                             normalizeMax = right;                         }                                                  [fullSongData appendBytes:&right length:sizeof(right)];                     }                      totalLeft   = 0;                     totalRight  = 0;                     sampleTally = 0;                  }             }                                      [wader drain];                          CMSampleBufferInvalidate(sampleBufferRef);              CFRelease(sampleBufferRef);         }     }          NSData * finalData = nil;      if (reader.status == AVAssetReaderStatusFailed || reader.status == AVAssetReaderStatusUnknown){         // Something went wrong. Handle it.     }      if (reader.status == AVAssetReaderStatusCompleted){         // You're done. It worked.          NSLog(@"rendering output graphics using normalizeMax %f",normalizeMax);          UIImage *test = [self audioImageLogGraph:(Float32 *) fullSongData.bytes                                      normalizeMax:normalizeMax                                       sampleCount:fullSongData.length / (sizeof(Float32) * 2)                                      channelCount:2                                      imageHeight:100];          finalData = imageToData(test);     }              [fullSongData release];     [reader release];      return finalData; }  @end 
like image 186
Igor Shubin Avatar answered Sep 20 '22 04:09

Igor Shubin


Been reading your question and created a control for this. Looks like this:

enter image description here

Code here:

https://github.com/fulldecent/FDWaveformView

Discussion here:

https://www.cocoacontrols.com/controls/fdwaveformview

UPDATE 2015-01-29: This project is going strong and making consistent releases. Thanks for SO for all the exposure!

like image 38
William Entriken Avatar answered Sep 23 '22 04:09

William Entriken