- 在前面兩個篇章中悯搔,已經(jīng)把解碼和播放兩個重要的模塊講完了吃谣,剩下的只有一些處理中間邏輯的代碼了,看來這一篇是又要水了鲫惶,但是也是重要要的處理部分蜈首。
- 音頻解碼邏輯和之前的視頻解碼邏輯一樣樣的,下面我們可能只會列舉出一些不相同的部分欠母。
基于iOS平臺的最簡單的FFmpeg音頻播放器(一)
基于iOS平臺的最簡單的FFmpeg音頻播放器(二)
基于iOS平臺的最簡單的FFmpeg音頻播放器(三)
正式開始
1. 初始化音頻播放器
AieAudioManager * audioManager = [AieAudioManager audioManager];
[audioManager activateAudioSession];
- 音頻播放器理論上應(yīng)該先初始化欢策,獲取和設(shè)置硬件參數(shù)。
2.初始化解碼庫
- (void)start
{
_path = [[NSBundle mainBundle] pathForResource:@"薛之謙 - 摩天大樓" ofType:@"m4a"];
__weak AudioPlayController * weakSelf = self;
AieDecoder * decoder = [[AieDecoder alloc] init];
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSError * error = nil;
[decoder openFile:_path error:&error];
__strong AudioPlayController * strongSelf = weakSelf;
if (strongSelf)
{
dispatch_async(dispatch_get_main_queue(), ^{
[strongSelf setMovieDecoder:decoder];
});
}
});
}
- 初始化解碼庫赏淌,然后使用
FFmpeg
相關(guān)的函數(shù)去打開文件踩寇,解析音頻文件中的流信息等。
3.開始解碼六水,并打開音頻
- (void)play
{
// 解碼音頻 并把音頻存儲到_audioFrames
[self asyncDecodeFrames];
// 打開音頻
[self enableAudio:YES];
}
- (void)enableAudio: (BOOL) on
{
AieAudioManager * audioManager = [AieAudioManager audioManager];
if (on) {
audioManager.outputBlock = ^(float *outData, UInt32 numFrames, UInt32 numChannels) {
[self audioCallbackFillData: outData numFrames:numFrames numChannels:numChannels];
};
[audioManager play];
}
}
-
asyncDecodeFrames
這個函數(shù)就就不說了俺孙,和視頻解碼的時候一毛一樣的辣卒。 -
AieAudioManager
是一個單例類,所以重復(fù)獲取都是同一個對象睛榄,相比Kxmovie
的音頻播放類荣茫,我把這個類簡化了許多,畢竟是最簡單的音頻播放器场靴。 -
audioManager.outputBlock
這個回調(diào)就是用來接收即將解碼的數(shù)據(jù)的啡莉。
4.解碼成功,并返回音頻數(shù)據(jù)
- (void)asyncDecodeFrames
{
__weak AudioPlayController * weakSelf = self;
__weak AieDecoder * weakDecoder = _decoder;
dispatch_async(_dispatchQueue, ^{
// 當(dāng)已經(jīng)解碼的視頻總時間大于_maxBufferedDuration 停止解碼
BOOL good = YES;
while (good) {
good = NO;
@autoreleasepool {
__strong AieDecoder * strongDecoder = weakDecoder;
if (strongDecoder) {
NSArray * frames = [strongDecoder decodeFrames:0.1];
if (frames.count) {
__strong AudioPlayController * strongSelf = weakSelf;
if (strongSelf) {
good = [strongSelf addFrames:frames];
}
}
}
}
}
});
}
- 這一部分是和視頻解碼的流程是一樣的旨剥,還是不做解釋了
5.緩存解碼后的音頻數(shù)據(jù)
- (BOOL) addFrames:(NSArray *)frames
{
@synchronized (_audioFrames)
{
for (AieFrame * frame in frames)
{
if (frame.type == AieFrameTypeAudio)
{
[_audioFrames addObject:frame];
_bufferedDuration += frame.duration;
}
}
}
return _bufferedDuration < _maxBufferedDuration;
}
- 緩存解碼后的音頻數(shù)據(jù)咧欣,并計算出緩存中音頻數(shù)據(jù)的總時間,用來合理的控制解碼速度轨帜。
6.填充將要解碼的數(shù)據(jù)
- (void) audioCallbackFillData: (float *) outData
numFrames: (UInt32) numFrames
numChannels: (UInt32) numChannels
- 這個函數(shù)是本文的重點(diǎn)魄咕,是一個從音頻播放器回調(diào)出來的函數(shù),然后又使用block回調(diào)到這個類蚌父。
-numFrames
是音頻數(shù)據(jù)采樣幀的數(shù)量哮兰,這個之前有說過。 -
numChannels
是音頻的通道數(shù)梢什。
6.1 獲取音頻數(shù)據(jù)
NSUInteger count = _audioFrames.count;
if (count > 0) {
AieAudioFrame *frame = _audioFrames[0];
[_audioFrames removeObjectAtIndex:0];
_moviePosition = frame.position;
_bufferedDuration -= frame.duration;
_currentAudioFramePos = 0;
_currentAudioFrame = frame.samples;
}
if (!count || !(_bufferedDuration > _minBufferedDuration)) {
[self asyncDecodeFrames];
}
- 以上代碼的主要作用是從
_audioFrames
緩存區(qū)中逐個取出數(shù)據(jù)奠蹬,然后記錄當(dāng)前的播放位置和時間朝聋,當(dāng)緩存區(qū)沒有數(shù)據(jù)或者是緩沖時間少于最小的時間的時候嗡午,就繼續(xù)解碼。
6.1 填充數(shù)據(jù)
if (_currentAudioFrame) {
const void *bytes = (Byte *)_currentAudioFrame.bytes + _currentAudioFramePos;
const NSUInteger bytesLeft = (_currentAudioFrame.length - _currentAudioFramePos);
const NSUInteger frameSizeOf = numChannels * sizeof(float);
const NSUInteger bytesToCopy = MIN(numFrames * frameSizeOf, bytesLeft);
const NSUInteger framesToCopy = bytesToCopy / frameSizeOf;
memcpy(outData, bytes, bytesToCopy);
numFrames -= framesToCopy;
outData += framesToCopy * numChannels;
if (bytesToCopy < bytesLeft)
_currentAudioFramePos += bytesToCopy;
else
_currentAudioFrame = nil;
} else {
memset(outData, 0, numFrames * numChannels * sizeof(float));
//LoggerStream(1, @"silence audio");
break;
}
- 如果
_currentAudioFrame
中存在數(shù)據(jù)冀痕,那就一幀一幀數(shù)據(jù)的存進(jìn)outData
中荔睹,如果_currentAudioFrame
中的音頻數(shù)據(jù)大于一幀,那就分為多次存言蛇。 - 從這個
bytesToCopy
的計算方式可以看出僻他,numFrames * numChannels * sizeof(float)
就是一幀的采樣數(shù)、通道數(shù)和一次采樣點(diǎn)大小的乘積就是實(shí)際上總的一幀音頻數(shù)據(jù)的大小腊尚。 - 如果
_currentAudioFrame
中不存在數(shù)據(jù)吨拗,那就直接存0
。
結(jié)尾
- 到這里我們關(guān)于最簡單的音頻播放器的內(nèi)容就全部結(jié)束了婿斥。
- 由于放了FFmpeg庫劝篷,所以Demo會很大,下載的時候比較費(fèi)時民宿。
- 謝謝閱讀