为什么在audio队列中使用AVAssetReader时,audio会出现乱码

基于我的研究..人们不断说它是基于不匹配/错误的格式..但我使用lPCM格式input和输出..你怎么会出错? 我得到的结果只是噪音..(像白噪声)

我已经决定只是粘贴我的整个代码..也许这将有助于:

#import "AppDelegate.h" #import "ViewController.h" @implementation AppDelegate @synthesize window = _window; @synthesize viewController = _viewController; - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; // Override point for customization after application launch. self.viewController = [[ViewController alloc] initWithNibName:@"ViewController" bundle:nil]; self.window.rootViewController = self.viewController; [self.window makeKeyAndVisible]; // Insert code here to initialize your application player = [[Player alloc] init]; [self setupReader]; [self setupQueue]; // initialize reader in a new thread internalThread =[[NSThread alloc] initWithTarget:self selector:@selector(readPackets) object:nil]; [internalThread start]; // start the queue. this function returns immedatly and begins // invoking the callback, as needed, asynchronously. //CheckError(AudioQueueStart(queue, NULL), "AudioQueueStart failed"); // and wait printf("Playing...\n"); do { CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.25, false); } while (!player.isDone /*|| gIsRunning*/); // isDone represents the state of the Audio File enqueuing. This does not mean the // Audio Queue is actually done playing yet. Since we have 3 half-second buffers in-flight // run for continue to run for a short additional time so they can be processed CFRunLoopRunInMode(kCFRunLoopDefaultMode, 2, false); // end playback player.isDone = true; CheckError(AudioQueueStop(queue, TRUE), "AudioQueueStop failed"); cleanup: AudioQueueDispose(queue, TRUE); AudioFileClose(player.playbackFile); return YES; } - (void) setupReader { NSURL *assetURL = [NSURL URLWithString:@"ipod-library://item/item.m4a?id=1053020204400037178"]; // from ilham's ipod AVURLAsset *songAsset = [AVURLAsset URLAssetWithURL:assetURL options:nil]; // from AVAssetReader Class Reference: // AVAssetReader is not intended for use with real-time sources, // and its performance is not guaranteed for real-time operations. NSError * error = nil; AVAssetReader* reader = [[AVAssetReader alloc] initWithAsset:songAsset error:&error]; AVAssetTrack* track = [songAsset.tracks objectAtIndex:0]; readerOutput = [AVAssetReaderTrackOutput assetReaderTrackOutputWithTrack:track outputSettings:nil]; // AVAssetReaderOutput* readerOutput = [[AVAssetReaderAudioMixOutput alloc] initWithAudioTracks:songAsset.tracks audioSettings:nil]; [reader addOutput:readerOutput]; [reader startReading]; } - (void) setupQueue { // get the audio data format from the file // we know that it is PCM.. since it's converted AudioStreamBasicDescription dataFormat; dataFormat.mSampleRate = 44100.0; dataFormat.mFormatID = kAudioFormatLinearPCM; dataFormat.mFormatFlags = kAudioFormatFlagIsBigEndian | kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked; dataFormat.mBytesPerPacket = 4; dataFormat.mFramesPerPacket = 1; dataFormat.mBytesPerFrame = 4; dataFormat.mChannelsPerFrame = 2; dataFormat.mBitsPerChannel = 16; // create a output (playback) queue CheckError(AudioQueueNewOutput(&dataFormat, // ASBD MyAQOutputCallback, // Callback (__bridge void *)self, // user data NULL, // run loop NULL, // run loop mode 0, // flags (always 0) &queue), // output: reference to AudioQueue object "AudioQueueNewOutput failed"); // adjust buffer size to represent about a half second (0.5) of audio based on this format CalculateBytesForTime(dataFormat, 0.5, &bufferByteSize, &player->numPacketsToRead); // check if we are dealing with a VBR file. ASBDs for VBR files always have // mBytesPerPacket and mFramesPerPacket as 0 since they can fluctuate at any time. // If we are dealing with a VBR file, we allocate memory to hold the packet descriptions bool isFormatVBR = (dataFormat.mBytesPerPacket == 0 || dataFormat.mFramesPerPacket == 0); if (isFormatVBR) player.packetDescs = (AudioStreamPacketDescription*)malloc(sizeof(AudioStreamPacketDescription) * player.numPacketsToRead); else player.packetDescs = NULL; // we don't provide packet descriptions for constant bit rate formats (like linear PCM) // get magic cookie from file and set on queue MyCopyEncoderCookieToQueue(player.playbackFile, queue); // allocate the buffers and prime the queue with some data before starting player.isDone = false; player.packetPosition = 0; int i; for (i = 0; i < kNumberPlaybackBuffers; ++i) { CheckError(AudioQueueAllocateBuffer(queue, bufferByteSize, &audioQueueBuffers[i]), "AudioQueueAllocateBuffer failed"); // EOF (the entire file's contents fit in the buffers) if (player.isDone) break; } } -(void)readPackets { // initialize a mutex and condition so that we can block on buffers in use. pthread_mutex_init(&queueBuffersMutex, NULL); pthread_cond_init(&queueBufferReadyCondition, NULL); state = AS_BUFFERING; while ((sample = [readerOutput copyNextSampleBuffer])) { AudioBufferList audioBufferList; CMBlockBufferRef CMBuffer = CMSampleBufferGetDataBuffer( sample ); CheckError(CMSampleBufferGetAudioBufferListWithRetainedBlockBuffer( sample, NULL, &audioBufferList, sizeof(audioBufferList), NULL, NULL, kCMSampleBufferFlag_AudioBufferList_Assure16ByteAlignment, &CMBuffer ), "could not read samples"); AudioBuffer audioBuffer = audioBufferList.mBuffers[0]; UInt32 inNumberBytes = audioBuffer.mDataByteSize; size_t incomingDataOffset = 0; while (inNumberBytes) { size_t bufSpaceRemaining; bufSpaceRemaining = bufferByteSize - bytesFilled; @synchronized(self) { bufSpaceRemaining = bufferByteSize - bytesFilled; size_t copySize; if (bufSpaceRemaining < inNumberBytes) { copySize = bufSpaceRemaining; } else { copySize = inNumberBytes; } // copy data to the audio queue buffer AudioQueueBufferRef fillBuf = audioQueueBuffers[fillBufferIndex]; memcpy((char*)fillBuf->mAudioData + bytesFilled, (const char*)(audioBuffer.mData + incomingDataOffset), copySize); // keep track of bytes filled bytesFilled +=copySize; incomingDataOffset +=copySize; inNumberBytes -=copySize; } // if the space remaining in the buffer is not enough for this packet, then enqueue the buffer. if (bufSpaceRemaining < inNumberBytes + bytesFilled) { [self enqueueBuffer]; } } } } -(void)enqueueBuffer { @synchronized(self) { inuse[fillBufferIndex] = true; // set in use flag buffersUsed++; // enqueue buffer AudioQueueBufferRef fillBuf = audioQueueBuffers[fillBufferIndex]; NSLog(@"we are now enqueing buffer %d",fillBufferIndex); fillBuf->mAudioDataByteSize = bytesFilled; err = AudioQueueEnqueueBuffer(queue, fillBuf, 0, NULL); if (err) { NSLog(@"could not enqueue queue with buffer"); return; } if (state == AS_BUFFERING) { // // Fill all the buffers before starting. This ensures that the // AudioFileStream stays a small amount ahead of the AudioQueue to // avoid an audio glitch playing streaming files on iPhone SDKs < 3.0 // if (buffersUsed == kNumberPlaybackBuffers - 1) { err = AudioQueueStart(queue, NULL); if (err) { NSLog(@"couldn't start queue"); return; } state = AS_PLAYING; } } // go to next buffer if (++fillBufferIndex >= kNumberPlaybackBuffers) fillBufferIndex = 0; bytesFilled = 0; // reset bytes filled } // wait until next buffer is not in use pthread_mutex_lock(&queueBuffersMutex); while (inuse[fillBufferIndex]) { pthread_cond_wait(&queueBufferReadyCondition, &queueBuffersMutex); } pthread_mutex_unlock(&queueBuffersMutex); } #pragma mark - utility functions - // generic error handler - if err is nonzero, prints error message and exits program. static void CheckError(OSStatus error, const char *operation) { if (error == noErr) return; char str[20]; // see if it appears to be a 4-char-code *(UInt32 *)(str + 1) = CFSwapInt32HostToBig(error); if (isprint(str[1]) && isprint(str[2]) && isprint(str[3]) && isprint(str[4])) { str[0] = str[5] = '\''; str[6] = '\0'; } else // no, format it as an integer sprintf(str, "%d", (int)error); fprintf(stderr, "Error: %s (%s)\n", operation, str); exit(1); } // we only use time here as a guideline // we're really trying to get somewhere between 16K and 64K buffers, but not allocate too much if we don't need it/* void CalculateBytesForTime(AudioStreamBasicDescription inDesc, Float64 inSeconds, UInt32 *outBufferSize, UInt32 *outNumPackets) { // we need to calculate how many packets we read at a time, and how big a buffer we need. // we base this on the size of the packets in the file and an approximate duration for each buffer. // // first check to see what the max size of a packet is, if it is bigger than our default // allocation size, that needs to become larger // we don't have access to file packet size, so we just default it to maxBufferSize UInt32 maxPacketSize = 0x10000; static const int maxBufferSize = 0x10000; // limit size to 64K static const int minBufferSize = 0x4000; // limit size to 16K if (inDesc.mFramesPerPacket) { Float64 numPacketsForTime = inDesc.mSampleRate / inDesc.mFramesPerPacket * inSeconds; *outBufferSize = numPacketsForTime * maxPacketSize; } else { // if frames per packet is zero, then the codec has no predictable packet == time // so we can't tailor this (we don't know how many Packets represent a time period // we'll just return a default buffer size *outBufferSize = maxBufferSize > maxPacketSize ? maxBufferSize : maxPacketSize; } // we're going to limit our size to our default if (*outBufferSize > maxBufferSize && *outBufferSize > maxPacketSize) *outBufferSize = maxBufferSize; else { // also make sure we're not too small - we don't want to go the disk for too small chunks if (*outBufferSize < minBufferSize) *outBufferSize = minBufferSize; } *outNumPackets = *outBufferSize / maxPacketSize; } // many encoded formats require a 'magic cookie'. if the file has a cookie we get it // and configure the queue with it static void MyCopyEncoderCookieToQueue(AudioFileID theFile, AudioQueueRef queue ) { UInt32 propertySize; OSStatus result = AudioFileGetPropertyInfo (theFile, kAudioFilePropertyMagicCookieData, &propertySize, NULL); if (result == noErr && propertySize > 0) { Byte* magicCookie = (UInt8*)malloc(sizeof(UInt8) * propertySize); CheckError(AudioFileGetProperty (theFile, kAudioFilePropertyMagicCookieData, &propertySize, magicCookie), "get cookie from file failed"); CheckError(AudioQueueSetProperty(queue, kAudioQueueProperty_MagicCookie, magicCookie, propertySize), "set cookie on queue failed"); free(magicCookie); } } #pragma mark - audio queue - static void MyAQOutputCallback(void *inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inCompleteAQBuffer) { AppDelegate *appDelegate = (__bridge AppDelegate *) inUserData; [appDelegate myCallback:inUserData inAudioQueue:inAQ audioQueueBufferRef:inCompleteAQBuffer]; } - (void)myCallback:(void *)userData inAudioQueue:(AudioQueueRef)inAQ audioQueueBufferRef:(AudioQueueBufferRef)inCompleteAQBuffer { unsigned int bufIndex = -1; for (unsigned int i = 0; i < kNumberPlaybackBuffers; ++i) { if (inCompleteAQBuffer == audioQueueBuffers[i]) { bufIndex = i; break; } } if (bufIndex == -1) { NSLog(@"something went wrong at queue callback"); return; } // signal waiting thread that the buffer is free. pthread_mutex_lock(&queueBuffersMutex); NSLog(@"signalling that buffer %d is free",bufIndex); inuse[bufIndex] = false; buffersUsed--; pthread_cond_signal(&queueBufferReadyCondition); pthread_mutex_unlock(&queueBuffersMutex); } @end 

更新: btomw的答案在下面解决了这个问题。 但是我想深入到底层(像我这样的大多数新手开发者,甚至在开始的时候通常都是在黑暗中用参数进行拍摄,格式化等等 – 参见这里的例子)。

为什么我提供nul作为AVURLAsset * songAsset = [AVURLAsset URLAssetWithURL:assetURL options:audioReadSettings];

是因为根据文档和反复试验,我意识到除了lPCM以外的任何格式都会被直接拒绝。 换句话说,当你使用AVAseetReader或转换,即使结果总是 lPCM ..所以我认为默认格式是lPCM反正,所以我把它留作空…但我想我错了。

在这个奇怪的部分(请纠正我的任何人,如果我错了),正如我所说的..假设原始文件是.mp3,我的意图是播放它(或通过networking发送数据包等)作为MP3 ..所以我提供了一个MP3 ABSD ..资产读者将崩溃! 所以如果我想以原来的forms发送它,我只是提供null? 这个显而易见的问题是,如果我一旦收到另一边的ABSD,就没有办法知道它是什么。或者我可以吗?

更新2:你可以从github下载代码。

所以这就是我认为正在发生的事情,也是我认为你可以修复它。

您正在从iOS设备上的iPod(音乐)库中提取预定义项目。 然后您使用资产阅读​​器来收集缓冲区,并在可能的情况下将这些缓冲区排列在AudioQueue中。

我想,你所遇到的问题是,你正在设置audio队列缓冲区的input格式为线性脉冲编码调制(LPCM – 希望我得到了这个权利,我可能会缩写)。 您传递给资产读取器输出的输出设置为零,这意味着您将得到最有可能不是LPCM的输出,而是aiff或aac或者mp3,或者其它歌曲的格式。 iOS的媒体库。 但是,您可以通过传递不同的输出设置来弥补这种情况。

尝试改变

 readerOutput = [AVAssetReaderTrackOutput assetReaderTrackOutputWithTrack:track outputSettings:nil]; 

至:

 [NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithInt:kAudioFormatLinearPCM], AVFormatIDKey, [NSNumber numberWithFloat:44100.0], AVSampleRateKey, [NSNumber numberWithInt:2], AVNumberOfChannelsKey, [NSData dataWithBytes:&channelLayout length:sizeof(AudioChannelLayout)], AVChannelLayoutKey, [NSNumber numberWithInt:16], AVLinearPCMBitDepthKey, [NSNumber numberWithBool:NO], AVLinearPCMIsNonInterleaved, [NSNumber numberWithBool:NO],AVLinearPCMIsFloatKey, [NSNumber numberWithBool:NO], AVLinearPCMIsBigEndianKey, nil]; output = [AVAssetReaderTrackOutput assetReaderTrackOutputWithTrack:track audioSettings:outputSettings]; 

这是我的理解(根据苹果1的文档),通过nil作为输出设置参数为您提供与原始音轨相同的文件types的示例。 即使你有一个LPCM文件,其他一些设置可能会closures,这可能会导致你的问题。 至less,这将使所有的阅读器输出正常化,这应该使事情变得更容易,以解决问题。

希望有所帮助!

编辑:

为什么我提供nul作为AVURLAsset * songAsset = [AVURLAsset URLAssetWithURL:assetURL options:audioReadSettings];

是因为根据文件和试验和错误,我…

AVAssetReaders做2件事; 读取磁盘上存在的audio文件(即:mp3,aac,aiff)或将audio转换为lpcm。

如果通过nil作为输出设置,它将会读取文件,因为它存在,在这里你是正确的。 我很抱歉没有提到资产读者将只允许零或LPCM。 我自己也碰到过这个问题(这是在某个地方的文档里,但是需要一点点的潜水),但是没有在这里提及,因为那个时候我并不在意。 Sooooo …对不起呢?

如果您想要在读取之前了解您正在阅读的曲目的AudioStreamBasicDescription(ASBD),可以通过以下操作来获取它:

 AVURLAsset* uasset = [[AVURLAsset URLAssetWithURL:<#assetURL#> options:nil]retain]; AVAssetTrack*track = [uasset.tracks objectAtIndex:0]; CMFormatDescriptionRef formDesc = (CMFormatDescriptionRef)[[track formatDescriptions] objectAtIndex:0]; const AudioStreamBasicDescription* asbdPointer = CMAudioFormatDescriptionGetStreamBasicDescription(formDesc); //because this is a pointer and not a struct we need to move the data into a struct so we can use it AudioStreamBasicDescription asbd = {0}; memcpy(&asbd, asbdPointer, sizeof(asbd)); //asbd now contains a basic description for the track 

然后,您可以将asbd转换为您认为合适的任何格式的二进制数据,并通过networking传输。 然后,您应该能够通过networking开始发送audio缓冲区数据,并成功使用您的AudioQueue进行播放。

实际上我不久之前就有这样一个系统的工作,但是因为当iOS客户端设备进入后台时我不能保持连接的活跃,所以我无法将其用于我的目的。 不过,如果所有这些工作都让我帮助其他可以真正使用这些信息的人,那么这对我来说似乎是一场胜利。