AudioQueue如何找出排队数据的播放长度

Posted

技术标签:

【中文标题】AudioQueue如何找出排队数据的播放长度【英文标题】:AudioQueue how to find out playback length of queued data 【发布时间】:2011-09-10 23:17:16 【问题描述】:

我正在使用 AudioQueue 流式传输一些歌曲,我的问题是如何判断已排队缓冲区的播放长度?我想一次传输两秒的数据,我遇到的问题是我怎么知道有多少字节实际上对应于两秒的音乐(所以我总是可以领先两秒)。

谢谢

丹尼尔

【问题讨论】:

未压缩或未压缩的歌曲?如果压缩,是 CBR 还是 VBR? 我猜它会被 mp3 或 m4a 压缩 【参考方案1】:

这是一个使用音频文件服务获取比特率/数据包/帧数据的类,以从音乐文件中获取对应于 x 秒的字节数,该示例已使用 mp3 和 m4a 文件进行测试

标题

#import <Foundation/Foundation.h>
#import <AudioToolbox/AudioToolbox.h>
@interface MusicChunker : NSObject

    AudioFileID audioFile;
    int _sampleRate;
    int _totalFrames;
    UInt64 _framesPerPacket;
    UInt64 _totalPackets;
    UInt64 fileDataSize;
    AudioFilePacketTableInfo _packetInfo;
    int _fileLength;
    AudiostreamBasicDescription _fileDataFormat;
    NSFileHandle * _fileHandle;
    int _packetOffset;
    int _totalReadBytes;
    int _maxPacketSize;
    BOOL firstTime;
    BOOL _ism4a;

-(id)initWithURL:(NSURL*)url andFileType:(NSString*)ext;
//gets next chunk that corresponds to seconds of audio
-(NSData*)getNextDataChunk:(int)seconds;
@end

实施

#import "MusicChunker.h"
void ReportAudioError(OSStatus statusCode);
@implementation MusicChunker

- (id)init

    self = [super init];
    if (self) 
        // Initialization code here.
    

    return self;

void ReportAudioError(OSStatus statusCode) 
    switch (statusCode) 
        case noErr:
            break;
        case kAudioFileUnspecifiedError:
            [NSException raise:@"AudioFileUnspecifiedError" format:@"An unspecified error occured."];
            break;
        case kAudioFileUnsupportedDataFormatError:
            [NSException raise:@"AudioFileUnsupportedDataFormatError" format:@"The data format is not supported by the output file type."];
            break;
        case kAudioFileUnsupportedFileTypeError:
            [NSException raise:@"AudioFileUnsupportedFileTypeError" format:@"The file type is not supported."];
            break;
        case kAudioFileUnsupportedPropertyError:
            [NSException raise:@"AudioFileUnsupportedPropertyError" format:@"A file property is not supported."];
            break;
        case kAudioFilePermissionsError:
            [NSException raise:@"AudioFilePermissionsError" format:@"The operation violated the file permissions. For example, an attempt was made to write to a file opened with the kAudioFileReadPermission constant."];
            break;
        case kAudioFileNotOptimizedError:
            [NSException raise:@"AudioFileNotOptimizedError" format:@"The chunks following the audio data chunk are preventing the extension of the audio data chunk. To write more data, you must optimize the file."];
            break;
        case kAudioFileInvalidChunkError:
            [NSException raise:@"AudioFileInvalidChunkError" format:@"Either the chunk does not exist in the file or it is not supported by the file."];
            break;
        case kAudioFileDoesNotAllow64BitDataSizeError:
            [NSException raise:@"AudioFileDoesNotAllow64BitDataSizeError" format:@"The file offset was too large for the file type. The AIFF and WAVE file format types have 32-bit file size limits."];
            break;
        case kAudioFileInvalidPacketOffsetError:
            [NSException raise:@"AudioFileInvalidPacketOffsetError" format:@"A packet offset was past the end of the file, or not at the end of the file when a VBR format was written, or a corrupt packet size was read when the packet table was built."];
            break;
        case kAudioFileInvalidFileError:
            [NSException raise:@"AudioFileInvalidFileError" format:@"The file is malformed, or otherwise not a valid instance of an audio file of its type."];
            break;
        case kAudioFileOperationNotSupportedError:
            [NSException raise:@"AudioFileOperationNotSupportedError" format:@"The operation cannot be performed. For example, setting the kAudioFilePropertyAudioDataByteCount constant to increase the size of the audio data in a file is not a supported operation. Write the data instead."];
            break;
        case -50:
            [NSException raise:@"AudioFileBadParameter" format:@"An invalid parameter was passed, possibly the current packet and/or the inNumberOfPackets."];
            break;
        default:
            [NSException raise:@"AudioFileUknownError" format:@"An unknown error type %@ occured. [%s]", [NSNumber numberWithInteger:statusCode], (char*)&statusCode];
            break;
    


+ (AudioFileTypeID)hintForFileExtension:(NSString *)fileExtension

    AudioFileTypeID fileTypeHint = kAudioFileAAC_ADTSType;
    if ([fileExtension isEqual:@"mp3"])
    
        fileTypeHint = kAudioFileMP3Type;
    
    else if ([fileExtension isEqual:@"wav"])
    
        fileTypeHint = kAudioFileWAVEType;
    
    else if ([fileExtension isEqual:@"aifc"])
    
        fileTypeHint = kAudioFileAIFCType;
    
    else if ([fileExtension isEqual:@"aiff"])
    
        fileTypeHint = kAudioFileAIFFType;
    
    else if ([fileExtension isEqual:@"m4a"])
    
        fileTypeHint = kAudioFileM4AType;
    
    else if ([fileExtension isEqual:@"mp4"])
    
        fileTypeHint = kAudioFileMPEG4Type;
    
    else if ([fileExtension isEqual:@"caf"])
    
        fileTypeHint = kAudioFileCAFType;
    
    else if ([fileExtension isEqual:@"aac"])
    
        fileTypeHint = kAudioFileAAC_ADTSType;
    
    return fileTypeHint;


-(id)initWithURL:(NSURL*)url andFileType:(NSString*)ext

    self = [super init];
    if (self) 
        // Initialization code here.
        //OSStatus theErr = noErr;
        if([ext isEqualToString:@"mp3"])
        
            _ism4a=FALSE;
        
        else
            _ism4a=TRUE;
        firstTime=TRUE;
        _packetOffset=0;
        AudioFileTypeID hint=[MusicChunker hintForFileExtension:ext];
        OSStatus theErr = AudioFileOpenURL((CFURLRef)url, kAudioFileReadPermission, hint, &audioFile);
        if(theErr)
        
            ReportAudioError(theErr);

        

        UInt32 thePropertySize;// = sizeof(theFileFormat);

        thePropertySize = sizeof(fileDataSize);
        theErr = AudioFileGetProperty(audioFile, kAudioFilePropertyAudioDataByteCount, &thePropertySize, &fileDataSize);
        if(theErr)
        
            ReportAudioError(theErr);

        

        theErr = AudioFileGetProperty(audioFile,    kAudioFilePropertyAudioDataPacketCount, &thePropertySize, &_totalPackets);
        if(theErr)
        
            ReportAudioError(theErr);

        
        /*
        UInt32 size;

        size= sizeof(_packetInfo);
        theErr= AudioFileGetProperty(audioFile, kAudioFilePropertyPacketTableInfo, &size, &_packetInfo);
  g(@"Key %@", key );
         

        if(theErr)
        
            ReportAudioError(theErr);
        
        */
        UInt32 size;
        size=sizeof(_maxPacketSize);
        theErr=AudioFileGetProperty(audioFile,  kAudioFilePropertyMaximumPacketSize , &size, &_maxPacketSize);

        size = sizeof( _fileDataFormat );
        theErr=AudioFileGetProperty( audioFile, kAudioFilePropertyDataFormat, &size, &_fileDataFormat );
        _framesPerPacket=_fileDataFormat.mFramesPerPacket;
        _totalFrames=_fileDataFormat.mFramesPerPacket*_totalPackets;

        _fileHandle=[[NSFileHandle fileHandleForReadingFromURL:url error:nil] retain];    
        _fileLength=[_fileHandle seekToEndOfFile];
        _sampleRate=_fileDataFormat.mSampleRate;
        _totalReadBytes=0;
        /*
         AudioFramePacketTranslation tran;//= .mFrame = 0, .mPacket = packetCount - 1, .mFrameOffsetInPacket = 0 ;
         tran.mFrame=0;
         tran.mFrameOffsetInPacket=0;
         tran.mPacket=1;
         UInt32 size=sizeof(tran);
         theErr=AudioFileGetProperty(audioFile, kAudioFilePropertyPacketToFrame, &size, &tran);
         */
        /*
         AudioBytePacketTranslation bt;
         bt.mPacket=4;
         bt.mByteOffsetInPacket=0;
         size=sizeof(bt);
         theErr=AudioFileGetProperty(audioFile, kAudioFilePropertyPacketToByte, &size, &bt);
         */


    

    return self;

//gets next chunk that corresponds to seconds of audio
-(NSData*)getNextDataChunk:(int)seconds


    //NSLog(@"%d, total packets",_totalPackets);

    if(_packetOffset>=_totalPackets)
        return nil;

    //sampleRate * seconds = number of wanted frames
    int framesWanted= _sampleRate*seconds;
    NSData *header=nil;
    int wantedPackets=  framesWanted/_framesPerPacket;
    if(firstTime && _ism4a)
    
        firstTime=false;
        //when we have a header that was stripped off, we grab it from the original file
        int totallen= [_fileHandle seekToEndOfFile];
        int dif=totallen-fileDataSize;
        [_fileHandle seekToFileOffset:0];
        header= [_fileHandle readDataOfLength:dif];
     



    int packetOffset=_packetOffset+wantedPackets;

    //bound condition
    if(packetOffset>_totalPackets)
    
        packetOffset=_totalPackets;
    


    UInt32 outBytes;

    UInt32 packetCount = wantedPackets;
    int x=packetCount * _maxPacketSize;
    void *data = (void *)malloc(x);

    OSStatus theErr=AudioFileReadPackets(audioFile, false, &outBytes, NULL, _packetOffset, &packetCount, data);

    if(theErr)
    
        ReportAudioError(theErr);
    
    //calculate bytes to read

    int bytesRead=outBytes;

    //update read bytes
    _totalReadBytes+=bytesRead;
   // NSLog(@"total bytes read %d", _totalReadBytes);
    _packetOffset=packetOffset;



    NSData *subdata=[[NSData dataWithBytes:data length:outBytes] retain];    

    free(data);

    if(header)
    
        NSMutableData *data=[[NSMutableData alloc]init];
        [data appendData:header];
        [data appendData:subdata];
        [subdata release];
        return [data autorelease];
    

    return [subdata autorelease];


@end

【讨论】:

【参考方案2】:

如果歌曲采用任意压缩格式,并且您想要精确的 2 秒剪辑,您可能必须先将歌曲转换为原始 PCM 样本或 WAV 数据(AVAssetReader 等)。然后,您可以以已知的采样率对样本进行计数。例如44.1k 采样率下的 88200 帧相当于 2 秒。

【讨论】:

所以如果我知道采样率并且有帧,那么我应该能够得到时间吗?转换我的 porpouses 需要太长时间......我总是知道采样率 如果不转换音频,可能不知道缓冲区中有多少个 PCM 采样帧,具体取决于压缩格式。 试图找到一种不必转换的方法,必须有其他方法,AQ 可以做的一切...... @Daniel - 不要假设一些简单的方法是可能的,除非你可以在文档化的 API 中找到它。 我想我找到了一种通过音频文件服务的方法,仍在努力,但我得到它后会在这里发回

以上是关于AudioQueue如何找出排队数据的播放长度的主要内容,如果未能解决你的问题,请参考以下文章

iOS音频播放之AudioQueue:播放本地音乐

iOS AudioQueue播放AAC数据总是延迟2-3秒

如何在不冻结 GUI 的情况下让 AudioQueue 播放?

AudioQueue 内存播放示例

ios音频:使用audioqueue改变播放进度

使用 AudioQueue 播放断断续续的音频