流媒体播放-在线音乐(1)

本文介绍了如何使用AudioStream和AudioQueue在iOS设备上播放本地及在线音视频文件。包括初始化播放器、设置播放参数以及处理播放过程中的各种情况。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

流播放文件即用AudioStream AudioQueue 来播放文件。好处是可以快速的开始播放,减少读文件的过程,适合大文件特别是背景音乐的播放。坏处是一次只能播放一个文件,如果要换播放文件,中间需要一定的时间。但是因为iPhone的文件读取时间只有10秒,对于资源较大的文件,只能考虑这个方式了。下面我将分享一下我在这方面的一点经验:

1. 单个文件播放2. 在线文件播放

1. 单个文件播放 BOOL isPlaying;


AudioFileID audioFile;

AudioStreamBasicDescription dataFormat;

AudioStreamPacketDescription *packetDescs;


UInt64 packetIndex;

UInt32 numPacketsToRead;


BOOL repeat;

BOOL trackClosed;



BOOL trackEnded;


AudioQueueRef queue;

AudioQueueBufferRef buffers[NUM_QUEUE_BUFFERS];

以上是需要定义的为单独文件播放的所需要的元素。可以定义在类里面。



2. 在线文件播放

NSURL *url;


AudioFileStreamID audioFileStream; // the audio file stream parser

AudioStreamPacketDescription packetDescsQueue[kAQMaxPacketDescs]; // packet descriptions for enqueuing audio


CFReadStreamRef stream;


unsigned int fillBufferIndex; // the index of the audioQueueBuffer that is being filled

size_t bytesFilled; // how many bytes have been filled

size_t packetsFilled; // how many packets have been filled


bool inuse[kNumAQBufs]; // flags to indicate that a buffer is still in use

bool started; // flag to indicate that the queue has been started

bool failed; // flag to indicate an error occurred

bool discontinuous; // flag to trigger bug-avoidance


pthread_mutex_t mutex; // a mutex to protect the inuse flags

pthread_cond_t cond; // a condition varable for handling the inuse flags

pthread_mutex_t mutex2; // a mutex to protect the AudioQueue buffer

BOOL trackEnded;


AudioQueueRef queue;

AudioQueueBufferRef buffers[NUM_QUEUE_BUFFERS];



利用http1.1协议播放在线文件。以上是在线文件播放所需要的参数。



#define NUM_QUEUE_BUFFERS 3

#define kNumAQBufs 6 // number of audio queue buffers we allocate

#define kAQBufSize 32 * 1024 // number of bytes in each audio queue buffer

#define kAQMaxPacketDescs 512 // number of packet descriptions in our array



这里是定义的一些参数,NUM_QUEUE_BUFFERS 用于播放本地文件,而 kNumAQBufs 用于播放在线文件。



3. 本地文件初始化

- (id)initWithPath:(NSString*)path

{

    UInt32 size, maxPacketSize;

    char *cookie;

    int i;

    

    if (kxxxTrackActive)

    {

        NSLog(@"Other music is playing.");

        return nil;

    }

    

    if (path == nil) return nil;

    if(!(self = [super init])) return nil;

    

    // try to open up the file using the specified path

    if (noErr != AudioFileOpenURL((CFURLRef)[NSURL fileURLWithPath:path], 0x01, 0, &audioFile))

    {

        NSLog(@"File can not be opened!");

        return nil;

    }

    

    // get the data format of the file

    size = sizeof(dataFormat);

    AudioFileGetProperty(audioFile, kAudioFilePropertyDataFormat, &size, &dataFormat);

    

    // create a new playback queue using the specified data format and buffer callback

    AudioQueueNewOutput(&dataFormat, BufferCallback, self, nil, nil, 0, &queue);

    

    // calculate number of packets to read and allocate space for packet descriptions if needed

    if (dataFormat.mBytesPerPacket == 0 || dataFormat.mFramesPerPacket == 0)

    {

        // Ask Core Audio to give us a conservative estimate of the largest packet

        size = sizeof(maxPacketSize);

        AudioFileGetProperty(audioFile, kAudioFilePropertyPacketSizeUpperBound, &size, &maxPacketSize);

        if (maxPacketSize > kxxxBufferSizeBytes)

        {

            

            maxPacketSize = kxxxBufferSizeBytes;

            NSLog(@"Size out of bounds!");

        }

        // calculate how many packs to read

        numPacketsToRead = kxxxBufferSizeBytes / maxPacketSize;

        

        // will need a packet description for each packet to allocate space accordingly

        packetDescs = malloc(sizeof(AudioStreamPacketDescription) * numPacketsToRead);

    }

    else

    {

        // constant bitrate

        numPacketsToRead = kxxxBufferSizeBytes / dataFormat.mBytesPerPacket;

        

        // don't need packet descriptions for CBR data

        packetDescs = nil;

    }

    

    // see if file uses a magic cookie (a magic cookie is meta data which some formats use)

    AudioFileGetPropertyInfo(audioFile, kAudioFilePropertyMagicCookieData, &size, nil);

    if (size > 0)

    {

        // copy the cookie data from the file into the audio queue

        cookie = malloc(sizeof(char) * size);

        AudioFileGetProperty(audioFile, kAudioFilePropertyMagicCookieData, &size, cookie);

        AudioQueueSetProperty(queue, kAudioQueueProperty_MagicCookie, cookie, size);

        free(cookie);

    }

    

    // we want to know when the playing state changes so we can properly dispose of the audio queue when it's done

    AudioQueueAddPropertyListener(queue, kAudioQueueProperty_IsRunning, propertyListenerCallback, self);

    

    // allocate and prime buffers with some data

    packetIndex = 0;

    for (i = 0; i < NUM_QUEUE_BUFFERS; i++)

    {

        AudioQueueAllocateBuffer(queue, kxxxBufferSizeBytes, &buffers);

        if ([self readPacketsIntoBuffer:buffers] == 0)

        {

            // this might happen if the file was so short that it needed less buffers than we planned on using

            break;

        }

    }

    repeat = NO;

    trackClosed = NO;

    trackEnded = NO;

    kxxxTrackActive = YES;

    return self;

}




4. 在线文件初始化



- (id)initWithURL:(NSURL*)newUrl

{

    self = [super init];

    if (self != nil)

    {

        url = [newUrl retain];

    }

    return self;

}



算了,废话不多说了,直接上代码,等以后有时间了再逐一解释。


.h文件



#ifdef TARGET_OS_IPHONE

#import <UIKit/UIKit.h>

#else

#import <Cocoa/Cocoa.h>

#endif TARGET_OS_IPHONE



#import <AudioToolbox/AudioQueue.h>

#import <AudioToolbox/AudioFile.h>

#include <pthread.h>

#include <AudioToolbox/AudioToolbox.h>



#define NUM_QUEUE_BUFFERS 3

#define kNumAQBufs 6 // number of audio queue buffers we allocate

#define kAQBufSize 32 * 1024 // number of bytes in each audio queue buffer

#define kAQMaxPacketDescs 512 // number of packet descriptions in our array



@interface xxxxx : NSObject {

    

    NSURL *url;

    BOOL isPlaying;

    

@public

    AudioFileStreamID audioFileStream; // the audio file stream parser

    AudioStreamPacketDescription packetDescsQueue[kAQMaxPacketDescs]; // packet descriptions for enqueuing audio

    

    CFReadStreamRef stream;

    

    unsigned int fillBufferIndex; // the index of the audioQueueBuffer that is being filled

    size_t bytesFilled; // how many bytes have been filled

    size_t packetsFilled; // how many packets have been filled

    

    bool inuse[kNumAQBufs]; // flags to indicate that a buffer is still in use

    bool started; // flag to indicate that the queue has been started

    bool failed; // flag to indicate an error occurred

    bool discontinuous; // flag to trigger bug-avoidance

    

    pthread_mutex_t mutex; // a mutex to protect the inuse flags

    pthread_cond_t cond; // a condition varable for handling the inuse flags

    pthread_mutex_t mutex2; // a mutex to protect the AudioQueue buffer

    

    

    AudioFileID audioFile;

    AudioStreamBasicDescription dataFormat;

    AudioStreamPacketDescription *packetDescs;

    

    UInt64 packetIndex;

    UInt32 numPacketsToRead;

    

    BOOL repeat;

    BOOL trackClosed;

    

    

    BOOL trackEnded;

    

    AudioQueueRef queue;

    AudioQueueBufferRef buffers[NUM_QUEUE_BUFFERS];

}



@property BOOL isPlaying;

@property BOOL trackClosed;



- (id) initWithURL:(NSURL*) newURL;

- (id) initWithPath:(NSString*) path;



- (void) setGain:(Float32)gain;

- (void) setRepeat:(BOOL)yn;

- (void) setPlayingWhenAutoLock;



- (void) play;

- (void) playURL;



- (void) pause;

- (void) stopURL;



- (void) close;



extern NSString *xxxTrackFinishedPlayingNotification;



@end




.m文件



#import "xxxxx.h"

#import <CFNetwork/CFNetwork.h>



static UInt32 kxxxBufferSizeBytes = 0x10000; // 64k

static BOOL kxxxTrackActive = NO;

NSString *xxxTrackFinishedPlayingNotification = @"xxxTrackFinishedPlayingNotification";



#pragma mark -

#pragma mark CFReadStream Callback Function Prototypes



void ReadStreamCallBack(CFReadStreamRef stream, CFStreamEventType eventType, void* dataIn);



#pragma mark -

#pragma mark Audio Callback Function Prototypes



void MyAudioQueueOutputCallback(void* inClientData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer);

void MyAudioQueueIsRunningCallback(void *inUserData, AudioQueueRef inAQ, AudioQueuePropertyID inID);

void MyPropertyListenerProc(void *inClientData, AudioFileStreamID inAudioFileStream, AudioFileStreamPropertyID inPropertyID, UInt32 *ioFlags);

void MyPacketsProc(void *inClientData, UInt32 inNumberBytes, UInt32 inNumberPackets, const void *inInputData, AudioStreamPacketDescription *inPacketDescriptions);

OSStatus MyEnqueueBuffer(xxxxx* myData);



#ifdef TARGET_OS_IPHONE

void MyAudioSessionInterruptionListener(void *inClientData, UInt32 inInterruptionState);

#endif



#pragma mark -

#pragma mark Audio Callback Function Implementations



//

// MyPropertyListenerProc

//

// Receives notification when the AudioFileStream has audio packets to be

// played. In response, this function creates the AudioQueue, getting it

// ready to begin playback (playback won't begin until audio packets are

// sent to the queue in MyEnqueueBuffer).

//

// This function is adapted from Apple's example in AudioFileStreamExample with

// kAudioQueueProperty_IsRunning listening added.

//

void MyPropertyListenerProc(void *inClientData, AudioFileStreamID inAudioFileStream, AudioFileStreamPropertyID inPropertyID, UInt32 *ioFlags)

{

    // this is called by audio file stream when it finds property values

    xxxxx* myData = (xxxxx*)inClientData;

    OSStatus err = noErr;

    

    switch (inPropertyID) {

        case kAudioFileStreamProperty_ReadyToProducePackets :

        {

            myData->discontinuous = true;

            

            // the file stream parser is now ready to produce audio packets.

            // get the stream format.

            AudioStreamBasicDescription asbd;

            UInt32 asbdSize = sizeof(asbd);

            err = AudioFileStreamGetProperty(inAudioFileStream, kAudioFileStreamProperty_DataFormat, &asbdSize, &asbd);

            if (err) { NSLog(@"get kAudioFileStreamProperty_DataFormat"); myData->failed = true; break; }

            

            // create the audio queue

            err = AudioQueueNewOutput(&asbd, MyAudioQueueOutputCallback, myData, NULL, NULL, 0, &myData->queue);

            if (err) { NSLog(@"AudioQueueNewOutput"); myData->failed = true; break; }

            

            // listen to the "isRunning" property

            err = AudioQueueAddPropertyListener(myData->queue, kAudioQueueProperty_IsRunning, MyAudioQueueIsRunningCallback, myData);

            if (err) { NSLog(@"AudioQueueAddPropertyListener"); myData->failed = true; break; }

            

            // allocate audio queue buffers

            for (unsigned int i = 0; i < kNumAQBufs; ++i) {

                err = AudioQueueAllocateBuffer(myData->queue, kAQBufSize, &myData->buffers);

                if (err) { NSLog(@"AudioQueueAllocateBuffer"); myData->failed = true; break; }

            }

            

            // get the cookie size

            UInt32 cookieSize;

            Boolean writable;

            err = AudioFileStreamGetPropertyInfo(inAudioFileStream, kAudioFileStreamProperty_MagicCookieData, &cookieSize, &writable);

            if (err) { NSLog(@"info kAudioFileStreamProperty_MagicCookieData"); break; }

            

            // get the cookie data

            void* cookieData = calloc(1, cookieSize);

            err = AudioFileStreamGetProperty(inAudioFileStream, kAudioFileStreamProperty_MagicCookieData, &cookieSize, cookieData);

            if (err) { NSLog(@"get kAudioFileStreamProperty_MagicCookieData"); free(cookieData); break; }

            

            // set the cookie on the queue.

            err = AudioQueueSetProperty(myData->queue, kAudioQueueProperty_MagicCookie, cookieData, cookieSize);

            free(cookieData);

            if (err) { NSLog(@"set kAudioQueueProperty_MagicCookie"); break; }

            break;

        }

    }

}



//

// MyPacketsProc

//

// When the AudioStream has packets to be played, this function gets an

// idle audio buffer and copies the audio packets into it. The calls to

// MyEnqueueBuffer won't return until there are buffers available (or the

// playback has been stopped).

//

// This function is adapted from Apple's example in AudioFileStreamExample with

// CBR functionality added.

//

void MyPacketsProc(void *inClientData, UInt32 inNumberBytes, UInt32 inNumberPackets, const void *inInputData, AudioStreamPacketDescription *inPacketDescriptions)

{

    // this is called by audio file stream when it finds packets of audio

    xxxxx* myData = (xxxxx*)inClientData;

    

    // we have successfully read the first packests from the audio stream, so

    // clear the "discontinuous" flag

    myData->discontinuous = false;

    

    // the following code assumes we're streaming VBR data. for CBR data, the second branch is used.

    if (inPacketDescriptions)

    {

        for (int i = 0; i < inNumberPackets; ++i) {

            SInt64 packetOffset = inPacketDescriptions.mStartOffset;

            SInt64 packetSize   = inPacketDescriptions.mDataByteSize;

            

            // If the audio was terminated before this point, then

            // exit.

            if (myData->trackEnded)

            {

                return;

            }

            

            // if the space remaining in the buffer is not enough for this packet, then enqueue the buffer.

            size_t bufSpaceRemaining = kAQBufSize - myData->bytesFilled;

            if (bufSpaceRemaining < packetSize) {

                MyEnqueueBuffer(myData);

            }

            

            pthread_mutex_lock(&myData->mutex2);

            

            // If the audio was terminated while waiting for a buffer, then

            // exit.

            if (myData->trackEnded)

            {

                pthread_mutex_unlock(&myData->mutex2);

                return;

            }

            

            // copy data to the audio queue buffer

            AudioQueueBufferRef fillBuf = myData->buffers[myData->fillBufferIndex];

            memcpy((char*)fillBuf->mAudioData + myData->bytesFilled, (const char*)inInputData + packetOffset, packetSize);

            

            pthread_mutex_unlock(&myData->mutex2);

            

            // fill out packet description

            myData->packetDescsQueue[myData->packetsFilled] = inPacketDescriptions;

            myData->packetDescsQueue[myData->packetsFilled].mStartOffset = myData->bytesFilled;

            // keep track of bytes filled and packets filled

            myData->bytesFilled += packetSize;

            myData->packetsFilled += 1;

            

            // if that was the last free packet description, then enqueue the buffer.

            size_t packetsDescsRemaining = kAQMaxPacketDescs - myData->packetsFilled;

            if (packetsDescsRemaining == 0) {

                MyEnqueueBuffer(myData);

            }

        }

    }

    else

    {

        size_t offset = 0;

        while (inNumberBytes)

        {

            // if the space remaining in the buffer is not enough for this packet, then enqueue the buffer.

            size_t bufSpaceRemaining = kAQBufSize - myData->bytesFilled;

            if (bufSpaceRemaining < inNumberBytes) {

                MyEnqueueBuffer(myData);

            }

            

            pthread_mutex_lock(&myData->mutex2);

            

            // If the audio was terminated while waiting for a buffer, then

            // exit.

            if (myData->trackEnded)

            {

                pthread_mutex_unlock(&myData->mutex2);

                return;

            }

            

            // copy data to the audio queue buffer

            AudioQueueBufferRef fillBuf = myData->buffers[myData->fillBufferIndex];

            bufSpaceRemaining = kAQBufSize - myData->bytesFilled;

            size_t copySize;

            if (bufSpaceRemaining < inNumberBytes)

            {

                copySize = bufSpaceRemaining;

            }

            else

            {

                copySize = inNumberBytes;

            }

            memcpy((char*)fillBuf->mAudioData + myData->bytesFilled, (const char*)(inInputData + offset), copySize);

            

            pthread_mutex_unlock(&myData->mutex2);

            

            // keep track of bytes filled and packets filled

            myData->bytesFilled += copySize;

            myData->packetsFilled = 0;

            inNumberBytes -= copySize;

            offset += copySize;

        }

    }

}



//

// MyEnqueueBuffer

//

// Called from MyPacketsProc and connectionDidFinishLoading to pass filled audio

// bufffers (filled by MyPacketsProc) to the AudioQueue for playback. This

// function does not return until a buffer is idle for further filling or

// the AudioQueue is stopped.

//

// This function is adapted from Apple's example in AudioFileStreamExample with

// CBR functionality added.

//

OSStatus MyEnqueueBuffer(xxxxx* myData)

{

    OSStatus err = noErr;

    myData->inuse[myData->fillBufferIndex] = true; // set in use flag

    

    // enqueue buffer

    AudioQueueBufferRef fillBuf = myData->buffers[myData->fillBufferIndex];

    fillBuf->mAudioDataByteSize = myData->bytesFilled;

    

    if (myData->packetsFilled)

    {

        err = AudioQueueEnqueueBuffer(myData->queue, fillBuf, myData->packetsFilled, myData->packetDescsQueue);

    }

    else

    {

        err = AudioQueueEnqueueBuffer(myData->queue, fillBuf, 0, NULL);

    }

    

    if (err) { NSLog(@"AudioQueueEnqueueBuffer"); myData->failed = true; return err; } 

    

    if (!myData->started) { // start the queue if it has not been started already

        err = AudioQueueStart(myData->queue, NULL);

        if (err) { NSLog(@"AudioQueueStart"); myData->failed = true; return err; } 

        myData->started = true;

    }

    

    // go to next buffer

    if (++myData->fillBufferIndex >= kNumAQBufs) myData->fillBufferIndex = 0;

    myData->bytesFilled = 0; // reset bytes filled

    myData->packetsFilled = 0; // reset packets filled

    

    // wait until next buffer is not in use

    pthread_mutex_lock(&myData->mutex); 

    while (myData->inuse[myData->fillBufferIndex] && !myData->trackEnded)

    {

        pthread_cond_wait(&myData->cond, &myData->mutex);

    }

    pthread_mutex_unlock(&myData->mutex);

    

    return err;

}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值