我正在尝试从iPod库中的MP3中提取原始PCM样本,以便我可以播放歌曲并操纵音调,速度和应用声音效果(例如滤镜)。我已经走了AVPlayer和AVAudioPlayer的路线,它们都不能完全控制播放。
以下代码就我所知。我现在处于一个不知道如何处理我的while循环中的CMSampleBufferRef的地步,因为我不知道使用哪个框架来播放音频并应用这些效果。
知道实现这一目标的最佳方法是什么?我已经查看了使用AVAssetWriter转换文件的情况,但这不会为我删除它,因为这个过程非常耗时。当然我可以将PCM样本读入内存进行播放,而不必先将它们写入磁盘?
注意:我知道下面的代码引用了项目中的一个mp3,但我知道这种方法与我从MPMediaPropertyAssetURL中提取NSURL的方法相同
-(IBAction)loadTrack:(id)sender {
NSString *songPath = [[NSBundle mainBundle] pathForResource:@"Smooth_Sub Focus_192" ofType:@"mp3"];
NSURL *assetURL = [[NSURL alloc] initFileURLWithPath:songPath];
AVURLAsset *songAsset = [AVURLAsset URLAssetWithURL:assetURL options:nil];
NSError *assetError = nil;
AVAssetReader *assetReader = [[AVAssetReader assetReaderWithAsset:songAsset
error:&assetError] retain];
if (assetError) {
NSLog (@"Error: %@", assetError);
return;
}
AVAssetReaderOutput *assetReaderOutput = [[AVAssetReaderAudioMixOutput assetReaderAudioMixOutputWithAudioTracks:songAsset.tracks
audioSettings: nil] retain];
if (![assetReader canAddOutput:assetReaderOutput]) {
NSLog (@"Incompatible Asser Reader Output");
return;
}
[assetReader addOutput: assetReaderOutput];
[assetReader startReading];
CMSampleBufferRef nextBuffer;
while (nextBuffer = [assetReaderOutput copyNextSampleBuffer]) {
/* What Do I Do Here? */
}
[assetReader release];
[assetReaderOutput release];
}
答案 0 :(得分:9)
我在自己的代码中做了类似的事情。以下方法为AVURLAsset返回一些NSData:
- (NSData *)extractDataForAsset:(AVURLAsset *)songAsset {
NSError * error = nil;
AVAssetReader * reader = [[AVAssetReader alloc] initWithAsset:songAsset error:&error];
AVAssetTrack * songTrack = [songAsset.tracks objectAtIndex:0];
AVAssetReaderTrackOutput * output = [[AVAssetReaderTrackOutput alloc] initWithTrack:songTrack outputSettings:nil];
[reader addOutput:output];
[output release];
NSMutableData * fullSongData = [[NSMutableData alloc] init];
[reader startReading];
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);
UInt8 buffer[length];
CMBlockBufferCopyDataBytes(blockBufferRef, 0, length, buffer);
NSData * data = [[NSData alloc] initWithBytes:buffer length:length];
[fullSongData appendData:data];
[data release];
CMSampleBufferInvalidate(sampleBufferRef);
CFRelease(sampleBufferRef);
}
}
if (reader.status == AVAssetReaderStatusFailed || reader.status == AVAssetReaderStatusUnknown){
// Something went wrong. Handle it.
}
if (reader.status == AVAssetReaderStatusCompleted){
// You're done. It worked.
}
[reader release];
return [fullSongData autorelease];
}
我建议在后台线程上执行此操作,因为它非常耗时。
这种方法的一个缺点是整首歌被加载到内存中,这当然是有限的。
答案 1 :(得分:5)
除汤姆欧文的答案外,我建议更换
UInt8 buffer[length];
CMBlockBufferCopyDataBytes(blockBufferRef, 0, length, buffer);
NSData * data = [[NSData alloc] initWithBytes:buffer length:length];
与
NSMutableData * data = [[NSMutableData alloc] initWithLength:length];
CMBlockBufferCopyDataBytes(blockBufferRef, 0, length, data.mutableBytes);
避免双重处理样本,并减少内存使用开销。
或者,你可以将自动发布池中的[NSMutableData dataWithLength:length]包装为一个不相关但相似的问题{/ 3}}。
答案 2 :(得分:2)
我认为你想在那里确保它的PCM ......
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];
答案 3 :(得分:0)
对于歌曲的持续时间,我相信你可以简单地查询歌曲资产:
float songTimeInSeconds = CMTimeGetSeconds(songAsset.duration);
int songMinutes = (int)(songTimeInSeconds/60.);
int songSeconds = (int)(songTimeInSeconds - 60.0*songMinutes);