iOS自定義音視頻采集問(wèn)題匯總

公司的直播項(xiàng)目一直采用的網(wǎng)易云的直播SDK听隐,后來(lái)產(chǎn)品需求需要我們使用手機(jī)端作為音視頻采集設(shè)備沪么,使用電視端作為傳輸與顯示媒介,把視頻數(shù)據(jù)展示在電視和Windows端,音頻只展示W(wǎng)indows端,這樣一個(gè)需求,這樣我這邊需要做到的:
1.音視頻采集已經(jīng)考慮的傳輸數(shù)據(jù)大小問(wèn)題需要對(duì)音視頻進(jìn)行壓縮編碼
2.數(shù)據(jù)傳輸惜索,因?yàn)殡娨暫褪謾C(jī)在同一個(gè)局域網(wǎng)所以,我們使用socket進(jìn)行數(shù)據(jù)傳輸
以上兩點(diǎn)看似簡(jiǎn)單其實(shí)里面內(nèi)容挺多堤如,各種問(wèn)題的出現(xiàn)讓我在這個(gè)項(xiàng)目中跌了很多跟頭榔至,對(duì)于遇到的問(wèn)題做以下記錄

音視頻采集

iOS這邊處理音視頻采集并不難彩匕,網(wǎng)上教程一大把,特別是視頻采集很簡(jiǎn)單宙攻,只需有一點(diǎn)要注意就是像素的大小設(shè)置溢陪,蘋果有兩種方案:一種是你自定義畫布大小邓馒,第二個(gè)你使用硬件支持的大小

自定義大小
pixW = 320;pixH = 240; //畫布寬高自定義
self.captureSession.sessionPreset = AVCaptureSessionPresetHigh; //畫質(zhì) 采用最高 數(shù)據(jù)也比較大
使用硬件支持

這個(gè)需要你使用系統(tǒng)像素采集的寬高

if ([self.captureSession canSetSessionPreset:AVCaptureSessionPreset1280x720]) { //判斷當(dāng)前采集的設(shè)備 是否支持 這個(gè)像素
 self.captureSession.sessionPreset = AVCaptureSessionPreset1280x720;

音頻這邊的就有多個(gè)選擇, 比如:AVCaptureDevice, AudioQueue以及Audio Unit。其中 Audio Unit是最底層的接口胆萧,它的優(yōu)點(diǎn)是功能強(qiáng)大羹唠,延遲低; 而缺點(diǎn)是學(xué)習(xí)成本高,難度大萌焰。對(duì)于一般的iOS應(yīng)用程序,AVCaptureDevice和AudioQueue完全夠用了掌猛。但對(duì)于音視頻直播辣往,最好還是使用 Audio Unit 進(jìn)行處理许起,這樣可以達(dá)到最佳的效果猛频。
因?yàn)槲疫@邊做的是直播的所以為了追求低延遲,最好的效果越锈,就采用了Audio Unit進(jìn)行處理,因?yàn)槎际荂語(yǔ)言的代碼而且還要和OC進(jìn)行結(jié)合所以學(xué)習(xí)起來(lái)確實(shí)很費(fèi)勁 所以對(duì)里面的關(guān)鍵代碼做了注釋

 // AVAudioSession的接口比較簡(jiǎn)單膘滨。APP啟動(dòng)的時(shí)候會(huì)自動(dòng)幫激活A(yù)VAudioSession甘凭,我們可以手動(dòng) 設(shè)置 開(kāi)始的時(shí)候b可以播放也可以錄音
AVAudioSession* session = [AVAudioSession sharedInstance];
[session setCategory:AVAudioSessionCategoryPlayAndRecord withOptions:AVAudioSessionCategoryOptionDefaultToSpeaker error:&error];
[session setActive:YES error:nil];   
//啟用錄音功能
UInt32 inputEnableFlag = 1;
CheckError(AudioUnitSetProperty(_remoteIOUnit,
                                kAudioOutputUnitProperty_EnableIO,
                                kAudioUnitScope_Input,
                                1,
                                &inputEnableFlag,
                                sizeof(inputEnableFlag)),
           "Open input of bus 1 failed");
//    Open output of bus 0(output speaker)//禁用播放功能
UInt32 outputEnableFlag = 1;
CheckError(AudioUnitSetProperty(_remoteIOUnit,
                                kAudioOutputUnitProperty_EnableIO,
                                kAudioUnitScope_Output,
                                0,
                                &outputEnableFlag,
                                sizeof(outputEnableFlag)),
           "Open output of bus 0 failed");
//Set up stream format for input and output
/**
 AudioStreamBasicDescription:
 mSampleRate;       采樣率, eg. 44100
 mFormatID;         格式, eg. kAudioFormatLinearPCM
 mFormatFlags;      標(biāo)簽格式, eg. kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked
 mBytesPerPacket;   每個(gè)Packet的Bytes數(shù)量, eg. 2
 mFramesPerPacket;  每個(gè)Packet的幀數(shù)量, eg. 1
 mBytesPerFrame;    (mBitsPerChannel / 8 * mChannelsPerFrame) 每幀的Byte數(shù), eg. 2
 mChannelsPerFrame; 1:單聲道;2:立體聲, eg. 1
 mBitsPerChannel;   語(yǔ)音每采樣點(diǎn)占用位數(shù)[8/16/24/32], eg. 16
 mReserved;         保留
 */
_streamFormat.mFormatID = kAudioFormatLinearPCM;
_streamFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;
_streamFormat.mSampleRate = kRate;
_streamFormat.mFramesPerPacket = 1;
_streamFormat.mBytesPerFrame = 2;
_streamFormat.mBytesPerPacket = 2;
_streamFormat.mBitsPerChannel = kBits;
_streamFormat.mChannelsPerFrame = kChannels;

CheckError(AudioUnitSetProperty(_remoteIOUnit,
                                kAudioUnitProperty_StreamFormat,
                                kAudioUnitScope_Input,
                                0,
                                &_streamFormat,
                                sizeof(_streamFormat)),
           "kAudioUnitProperty_StreamFormat of bus 0 failed");
CheckError(AudioUnitSetProperty(_remoteIOUnit,
                                kAudioUnitProperty_StreamFormat,
                                kAudioUnitScope_Output,
                                1,
                                &_streamFormat,
                                sizeof(_streamFormat)),
           "kAudioUnitProperty_StreamFormat of bus 1 failed");

//音頻采集結(jié)果回調(diào)
AURenderCallbackStruct recordCallback;
recordCallback.inputProc = recordCallback_xb;
recordCallback.inputProcRefCon = (__bridge void *)(self);
CheckError(AudioUnitSetProperty(_remoteIOUnit,
                            kAudioOutputUnitProperty_SetInputCallback,
                                kAudioUnitScope_Output,
                                1,
                                &recordCallback,
                                sizeof(recordCallback)),
           "couldnt set remote i/o render callback for output");

音視頻編碼

視頻我采用的是常用的H264編碼火邓,之前說(shuō)了視頻采集很簡(jiǎn)單丹弱,但視頻264編碼也是C語(yǔ)言寫里面的設(shè)置還是有很多需要注意的

OSStatus status = VTCompressionSessionCreate(NULL,//分配器,如果使用NULL的話,就使用默認(rèn)的. CFAllocatorRef  _Nullable allocator
                                                 width,//視頻幀的象素寬 int32_t width
                                                 height,//視頻幀的象素高 int32_t height
                                                 kCMVideoCodecType_H264,//編碼器的類型 CMVideoCodecType codecType
                                                 NULL,//如果用指定的視頻編碼器,就要設(shè)置這個(gè).使用NULL就是videoToolbox自己選擇一個(gè) CFDictionaryRef  _Nullable encoderSpecification
                                                 NULL,//元象素緩存,如果你不要videoToolbox給你創(chuàng)建,就傳NULL.使用非VTB分配的緩存,可以讓你有機(jī)會(huì)拷貝圖片 CFDictionaryRef  _Nullable sourceImageBufferAttributes,
                                                 NULL,//壓縮數(shù)據(jù)分配器.傳NULL可以使用默認(rèn)的. CFAllocatorRef  _Nullable compressedDataAllocator
                                                 didCompressH264,//回調(diào),這個(gè)方法會(huì)在另一個(gè)線程上被異步的VTCompressionSessionEncodeFrame調(diào)用.只有在你要使VTCompressionSessionEncodeFrameWithOutputHandler去編碼幀時(shí),才可以設(shè)置為NULL. VTCompressionOutputCallback  _Nullable outputCallback
                                                 (__bridge void *)(self),//回調(diào)方法所在的實(shí)例,回調(diào)方法是全局的可以設(shè)置為NULL void * _Nullable outputCallbackRefCon
                                                 &EncodingSession);//用來(lái)接收新的compression session VTCompressionSessionRef  _Nullable * _Nonnull compressionSessionOut
    NSLog(@"H264: VTCompressionSessionCreate %d", (int)status);
// Set the properties
    VTSessionSetProperty(EncodingSession, kVTCompressionPropertyKey_RealTime, kCFBooleanTrue);//實(shí)時(shí)運(yùn)行
    VTSessionSetProperty(EncodingSession, kVTCompressionPropertyKey_AllowFrameReordering, kCFBooleanFalse);
    
    //設(shè)置碼率德撬,均值,單位是byte
    SInt32 bitRate = width*height*3 * 4 * 8 * 2;  //越高效果越屌  幀數(shù)據(jù)越大
    CFNumberRef ref = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &bitRate);
    VTSessionSetProperty(EncodingSession, kVTCompressionPropertyKey_AverageBitRate, ref);
    CFRelease(ref);
    //設(shè)置碼率上限躲胳,均值蜓洪,單位是byte
    int bitRateLimit = width * height * 3 * 4 * 2;
    CFNumberRef bitRateLimitRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &bitRateLimit);
    VTSessionSetProperty(EncodingSession, kVTCompressionPropertyKey_DataRateLimits, bitRateLimitRef);
    
    int frameInterval = 10; //關(guān)鍵幀間隔 越低效果越屌 幀數(shù)據(jù)越大
    CFNumberRef  frameIntervalRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &frameInterval);
    VTSessionSetProperty(EncodingSession, kVTCompressionPropertyKey_MaxKeyFrameInterval,frameIntervalRef);
    CFRelease(frameIntervalRef);

設(shè)置碼率這個(gè)一定要 把寬高數(shù)據(jù)放大100倍左右不然圖像會(huì)很模糊,當(dāng)時(shí)因?yàn)檫@個(gè)問(wèn)題困擾了我3天坯苹,其他倒是沒(méi)有太大問(wèn)題的

Socket 數(shù)據(jù)傳輸

socket我采用的是GCDAsyncSocket 這個(gè)框架,使用方法也很簡(jiǎn)單網(wǎng)上教程很多隆檀,在這不再詳述,只是記錄一下注意的問(wèn)題
比如數(shù)據(jù)傳輸 常量數(shù)據(jù)進(jìn)行大小端轉(zhuǎn)換問(wèn)題 粹湃,還有因?yàn)閿?shù)據(jù)都是采用二級(jí)制進(jìn)行發(fā)送所以要對(duì)數(shù)據(jù)進(jìn)行組合恐仑,但是音視頻發(fā)送數(shù)據(jù)要單獨(dú)寫一個(gè)方法,不然NSMutableData 對(duì)象會(huì)造成內(nèi)存泄露無(wú)法釋放为鳄,原因是視頻采集的代理方法執(zhí)行速度太快導(dǎo)致此對(duì)象釋放不及時(shí)

char sign[8] = "KOCLACMD";
uint16_t  data_type  = 0x02;
uint16_t  media_type = 0x06;
uint32_t  width  =  pixW;
uint32_t  height =  pixH;
uint32_t  len =  (uint32_t)data.length;
// 字節(jié)序轉(zhuǎn)換 小端模式轉(zhuǎn)換大端 匹配網(wǎng)絡(luò)字節(jié)序
HTONS(data_type);
HTONS(media_type);
HTONL(len);
HTONL(width);
HTONL(height);

NSMutableData *muta = [NSMutableData data];
[muta appendBytes:&sign length:8];
[muta appendBytes:&data_type length:2];
[muta appendBytes:&media_type length:2];
[muta appendBytes:&len length:4];
[muta appendBytes:&width length:4];
[muta appendBytes:&height length:4];
[muta appendData:data];
//    NSLog(@"Video data (%lu): %@", (unsigned long)muta.length, muta.description);
[_clientSocket writeData:muta withTimeout:-1 tag:0];

因?yàn)槟銈鬏數(shù)狡渌吮热绾笈_(tái)所以涉及到常量類型的數(shù)據(jù)要進(jìn)行大小端轉(zhuǎn)換裳仆,而且還要注意 short 類型轉(zhuǎn)換和 long類型轉(zhuǎn)換的方法不同
其他也沒(méi)有什么了

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市孤钦,隨后出現(xiàn)的幾起案子歧斟,更是在濱河造成了極大的恐慌,老刑警劉巖司训,帶你破解...
    沈念sama閱讀 219,188評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件构捡,死亡現(xiàn)場(chǎng)離奇詭異液南,居然都是意外死亡壳猜,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門滑凉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)统扳,“玉大人,你說(shuō)我怎么就攤上這事畅姊≈渲樱” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 165,562評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵若未,是天一觀的道長(zhǎng)朱嘴。 經(jīng)常有香客問(wèn)我,道長(zhǎng)粗合,這世上最難降的妖魔是什么萍嬉? 我笑而不...
    開(kāi)封第一講書人閱讀 58,893評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮隙疚,結(jié)果婚禮上壤追,老公的妹妹穿的比我還像新娘。我一直安慰自己供屉,他們只是感情好行冰,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,917評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布溺蕉。 她就那樣靜靜地躺著,像睡著了一般悼做。 火紅的嫁衣襯著肌膚如雪疯特。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 51,708評(píng)論 1 305
  • 那天肛走,我揣著相機(jī)與錄音辙芍,去河邊找鬼。 笑死羹与,一個(gè)胖子當(dāng)著我的面吹牛故硅,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播纵搁,決...
    沈念sama閱讀 40,430評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼吃衅,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了腾誉?” 一聲冷哼從身側(cè)響起徘层,我...
    開(kāi)封第一講書人閱讀 39,342評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎利职,沒(méi)想到半個(gè)月后趣效,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,801評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡猪贪,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,976評(píng)論 3 337
  • 正文 我和宋清朗相戀三年跷敬,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片热押。...
    茶點(diǎn)故事閱讀 40,115評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡西傀,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出桶癣,到底是詐尸還是另有隱情拥褂,我是刑警寧澤,帶...
    沈念sama閱讀 35,804評(píng)論 5 346
  • 正文 年R本政府宣布牙寞,位于F島的核電站饺鹃,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏间雀。R本人自食惡果不足惜悔详,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,458評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望雷蹂。 院中可真熱鬧伟端,春花似錦、人聲如沸匪煌。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 32,008評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至霜医,卻和暖如春齿拂,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背肴敛。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,135評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工署海, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人医男。 一個(gè)月前我還...
    沈念sama閱讀 48,365評(píng)論 3 373
  • 正文 我出身青樓砸狞,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親镀梭。 傳聞我的和親對(duì)象是個(gè)殘疾皇子刀森,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,055評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • 本例需求:將Mic采集的PCM轉(zhuǎn)成AAC透罢,可得到兩種不同數(shù)據(jù),本例采用AudioQueue/AudioUnit兩種...
    小東邪啊閱讀 18,873評(píng)論 67 54
  • ### YUV顏色空間 視頻是由一幀一幀的數(shù)據(jù)連接而成榜晦,而一幀視頻數(shù)據(jù)其實(shí)就是一張圖片。 yuv是一種圖片儲(chǔ)存格式...
    天使君閱讀 3,293評(píng)論 0 4
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒(méi)有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對(duì)...
    cosWriter閱讀 11,103評(píng)論 1 32
  • 我只是一片普普通通的小葉子 即使在萬(wàn)千相似的模樣中 也不是那么突兀 只是坐著自己 默默奉獻(xiàn)一點(diǎn)點(diǎn)呼吸 也許微不足道...
    萋萋月下閱讀 164評(píng)論 0 0
  • 篤信什么 要把一日的性命送給一棵酸棗樹(shù) 換它堅(jiān)硬的核羽圃,蜜糖的想象 或要把一日的性命送給別的乾胶? 歸鴻的暮色,長(zhǎng)亭古道...
    尋槳閱讀 420評(píng)論 2 7