为什么使用AVAssetReader音频队列时声音来了乱码 [英] why is audio coming up garbled when using AVAssetReader with audio queue

查看:212
本文介绍了为什么使用AVAssetReader音频队列时声音来了乱码的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

..但我使用LPCM格式的输入和输出..你怎么能去错了吗?我得到的结果仅仅是噪音。(白噪声)

based on my research.. people keep on saying that it's based on mismatched/wrong formatting.. but i'm using lPCM formatting for both input and output.. how can you go wrong with that? the result i'm getting is just noise.. (like white noise)

我已经决定只贴上我的整个code ..也许这将有助于:

I've decided to just paste my entire code.. perhaps that would help:

#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 回答下面辉煌解决了这个问题。但是,我想要得到这个(最初级开发人员喜欢下自己,甚至btomw当他第一次开始拍摄,通常与参数黑暗,格式化等等 - 见的这里的例子 - )。

Update: btomw's answer below solved the problem magnificently. But I want to get to the bottom of this (most novice developers like myself and even btomw when he first started usually shoot in the dark with parameters, formatting etc - see here for an example -)..

为什么我提供NUL作为一个参数的原因
     AVURLAsset * songAsset = [AVURLAsset URLAssetWithURL:assetURL选项:audioReadSettings];

the reason why I provided nul as a parameter for AVURLAsset *songAsset = [AVURLAsset URLAssetWithURL:assetURL options:audioReadSettings];

是因为根据本<一href=\"https://developer.apple.com/library/mac/#documentation/AVFoundation/Reference/AVAssetReaderTrackOutput_Class/Reference/Reference.html\"相对=nofollow>文档和反复试验,我意识到,任何格式化,我把其他不是LPCM将被直接拒绝。换句话说,当你使用AVAseetReader或转换甚至是结果的总是的LPCM ..所以我想的默认格式是LPCM反正,所以我把它作为空..但我想我错了。

was because according to the documentation and trial and error, I realized that any formatting I put other than lPCM would be rejected outright. In other words, when you use AVAseetReader or conversion even the result is always lPCM.. so I thought the default format was lPCM anyways and so I left it as null.. but I guess I was wrong.

在这个怪异的部分(请纠正我的人,如果我错了)是我提​​到..假设原始文件是.mp3和我的意图是进行播放(或通过发送数据包网络等)的MP3 ..等等我提供了一个MP3 ABSD ..资产读者会崩溃!所以,如果我想在它原来的形式发送,我只需提供空?与此明显的问题是,就没有办法,我要弄清楚什么ABSD有一次我收到它的另一面..或者可以吗?

The weird part in this (please correct me anyone, if I'm wrong) is that as I mentioned.. supposed the original file was .mp3, and my intention was to play it back (or send the packets over a network etc) as mp3.. and so I provided an mp3 ABSD.. the asset reader will crash! so is that if i wanted to send it in it's original form, i just supply null? the obvious problem with this is that there would be no way for me to figure out what ABSD it has once I receive it on the other side.. or could I?

更新2:您可以从 github上中的code

Update 2:You can download the code from github.

推荐答案

是我认为正在发生的事情,也是我怎么觉得你可以解决它。

So here's what I think is happening and also how I think you can fix it.

你开的是iOS设备上的predefined项目出了iPod(音乐)库。那么你正在使用的资产读者收集它的缓冲区和队列这些缓冲区,如果可能,在AudioQueue。

You're pulling a predefined item out of the ipod (music) library on an iOS device. you are then using an asset reader to collect it's buffers, and queue those buffers, where possible, in an AudioQueue.

你有,我想,现在的问题是,你是音频队列缓冲区的输入格式设置为线性脉冲code调制(LPCM - 希望我说对了,我可能是关闭的首字母缩写)。要传递到资产读者输出的输出设置是零,这意味着你会得到一个输出,很有可能不是LPCM,但不是AIFF或AAC格式或MP3或任何格式的歌曲,因为它在存在iOS版的媒体库。你可以,但是,通过传递不同的输出设置,纠正这种情况。

The problem you are having, I think, is that you are setting the audio queue buffer's input format to Linear Pulse Code Modulation (LPCM - hope I got that right, I might be off on the acronym). The output settings you are passing to the asset reader output are nil, which means that you'll get an output that is most likely NOT LPCM, but is instead aiff or aac or mp3 or whatever the format is of the song as it exists in iOS's media library. You can, however, remedy this situation by passing in different output settings.

尝试更改

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];

这是我的理解(每文档在苹果<一个href=\"https://developer.apple.com/library/mac/#documentation/AVFoundation/Reference/AVAssetReaderTrackOutput_Class/Reference/Reference.html\"相对=nofollow称号=这里> 1 ),传入作为输出设置参数为您提供了相同的文件类型的样本作为原始音频跟踪。即使你有一个文件,是LPCM,其他一些设置可能被关闭,这可能会导致你的问题。最起码,这将正常化所有读者的输出,这将使事情变得更简单麻烦拍摄。

It's my understanding (per the documentation at Apple1) that passing nil as the output settings param gives you samples of the same file type as the original audio track. Even if you have a file that is LPCM, some other settings might be off, which might cause your problems. At the very least, this will normalize all the reader output, which should make things a bit easier to trouble shoot.

希望帮助!

编辑:

为什么我提供NUL作为AVURLAsset * songAsset参数的原因
  = [AVURLAsset URLAssetWithURL:assetURL选项:audioReadSettings];

the reason why I provided nul as a parameter for AVURLAsset *songAsset = [AVURLAsset URLAssetWithURL:assetURL options:audioReadSettings];

是因为根据文档和反复试验,我......

was because according to the documentation and trial and error, I...

AVAssetReaders做两件事;读回一个音频文件,因为它存在于磁盘上(即:MP3,AAC,AIFF),或将音频转换成LPCM

AVAssetReaders do 2 things; read back an audio file as it exists on disk (i.e.: mp3, aac, aiff), or convert the audio into lpcm.

如果您通过作为输出设置,它会读取文件回,因为它的存在,而在这你是正确的。我不提的是,资产读者仅允许零或LPCM道歉。我居然遇到了这个问题我自己(它在文档的某个地方,但需要一点跳水),但并没有选择在这里提到它,因为它是不是在我的脑海的时间。 SOOOOO ......我们对此深感抱歉?

If you pass nil as the output settings, it will read the file back as it exists, and in this you are correct. I apologize for not mentioning that an asset reader will only allow nil or LPCM. I actually ran into that problem myself (it's in the docs somewhere, but requires a bit of diving), but didn't elect to mention it here as it wasn't on my mind at the time. Sooooo... sorry about that?

如果你想知道你你读它之前正在阅读的轨道AudioStreamBasicDescription(ASBD),你可以这样做,得到它:

If you want to know the AudioStreamBasicDescription (ASBD) of the track you are reading before you read it, you can get it by doing this:

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 来的任何格式,你认为合适,转让其在网络上的二进制数据。然后,您应该可以开始通过网络发送音频缓冲区中的数据,并成功地与AudioQueue回放。

You can then convert asbd to binary data in whatever format you see fit and transfer it over the network. You should then be able to start sending audio buffer data over the network and successfully play it back with your AudioQueue.

其实我有这样的工作不是很久以前的系统,但因为我便无法保持连接活着的时候了iOS客户端设备到后台,我是不是能够使用它为我的目的。不过,如果所有的工作让我帮助别人谁可以实际使用的信息,似乎是一个双赢给我。

I actually had a system like this working not that long ago, but since I could't keep the connection alive when the iOS client device went to the background, I wasn't able to use it for my purpose. Still, if all that work lets me help someone else who can actually use the info, seems like a win to me.

这篇关于为什么使用AVAssetReader音频队列时声音来了乱码的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆