iOS直播推流實(shí)現(xiàn)-采集

將最近學(xué)習(xí)的直播推流技術(shù)做個(gè)筆記盯蝴。
iOS推流的主要流程如下:

  1. 視頻音頻采集
  2. 視頻美顏濾鏡和貼紙
  3. 視頻編碼和音頻編碼
  4. 推流到流服務(wù)器

采集

iOS一般使用AVFundation進(jìn)行采集,涉及到的類如下:

  • AVCaptureSession 采集的會話對象听怕,它一頭連接輸入對象捧挺,一頭連接輸出對象向app提供采集好的原始音視頻數(shù)據(jù),通過它管理采集的開始與結(jié)束

  • AVCaptureDevice 采集用的設(shè)備尿瞭,比如麥克風(fēng)還是攝像頭闽烙,是前置攝像頭還是后置攝像頭。所以初始化時(shí)一般指定mediaType:AVMediaTypeAudio(音頻) or AVMediaTypeVideo(視頻)

  • AVCaptureDeviceInput 采集輸入對象声搁,通過AVCaptureDevice對象進(jìn)行初始化

  • AVCaptureVideoDataOutput 黑竞、AVCaptureAudioDataOutput 視頻數(shù)據(jù)和音頻數(shù)據(jù)采集后的輸出對象,與輸入對象對應(yīng)疏旨。廢話不多說了看代碼吧很魂!
    // 先創(chuàng)建一個(gè)采集類,并進(jìn)行必要屬性定義吧

@interface Capture()<AVCaptureVideoDataOutputSampleBufferDelegate, AVCaptureAudioDataOutputSampleBufferDelegate> {
    // 采集相關(guān)對象
    AVCaptureVideoDataOutput *videoOutput;
    AVCaptureAudioDataOutput *audioOutput;
    
    AVCaptureConnection *videoConnection;
    AVCaptureConnection *audioConnection;
        
    dispatch_queue_t acaptureQueue;
    dispatch_queue_t vcaptureQueue;
    dispatch_semaphore_t samaphore;
    
    AVCaptureDeviceInput *_deviceInput;
    int sampleCount;
 }
@property (nonatomic, assign) AVCaptureDevicePosition devicePosition;
@property (nonatomic, assign) AVCaptureVideoOrientation orientation;
@property (nonatomic, assign) AVCaptureSessionPreset preset;
@property (nonatomic, assign) BOOL isMirrored;
@end
  1. 初始化
// 1.創(chuàng)建一個(gè)采集類會話 AVCaptureSession
_session = [[AVCaptureSession alloc] init];
// 設(shè)置視頻采集的寬高參數(shù)
 _session.sessionPreset = _preset; // @sea AVCaptureSessionPreset1280x720
// 2.設(shè)置音頻采集
AVCaptureDevice *micro = [AVCaptureDevice defaultDeviceWithDeviceType:AVCaptureDeviceTypeBuiltInMicrophone mediaType:AVMediaTypeAudio position:AVCaptureDevicePositionUnspecified];
    AVCaptureDeviceInput *audioInput = [[AVCaptureDeviceInput alloc] initWithDevice:micro error:nil];
    if (![_session canAddInput:audioInput]) {
        NSLog(@"can not add audioInput");
        return  NO;
    }
    // 將采集輸入源添加到session
    [_session addInput:audioInput];
    // 3. 采集物理設(shè)備對象檐涝,選擇后置攝像頭
    AVCaptureDevice *camera = [self videoDeviceWitchPosition:_devicePosition];
    // 通過視頻物理設(shè)備對象創(chuàng)建視頻輸入對象
    AVCaptureDeviceInput *videoInput = [[AVCaptureDeviceInput alloc] initWithDevice:camera error:nil];
    if (![_session canAddInput:videoInput]) {
        NSLog(@"can not add video input");
        return NO;
    }
    _deviceInput = videoInput;
    [_session addInput:videoInput];
    /* 如果調(diào)用了[session startRunning]之后要想改變音視頻輸出對象配置參數(shù)遏匆,則必須調(diào)用[session beginConfiguration];和 [session commitConfiguration];才能生效。
如果沒有調(diào)用[session startRunning]則這兩句代碼可以不寫 */
    [_session beginConfiguration];
    // 4. 創(chuàng)建視頻輸出對象
    videoOutput = [[AVCaptureVideoDataOutput alloc] init];
    // 設(shè)置視頻輸出參數(shù)谁榜,這里設(shè)置輸出格式為YUV420YpCbCr8
    NSDictionary *videoSettings = @{(id)kCVPixelBufferPixelFormatTypeKey: @(kCVPixelFormatType_420YpCbCr8BiPlanarFullRange)};
    videoOutput.videoSettings = videoSettings;
    // 當(dāng)采集速度過快而處理速度跟不上時(shí)的丟棄策略幅聘,默認(rèn)丟棄最新采集的視頻。這里設(shè)置為NO窃植,表示不丟棄緩存起來
    videoOutput.alwaysDiscardsLateVideoFrames = YES;
   // vcaptureQueue 輸出執(zhí)行的視頻隊(duì)列帝蒿,它是一個(gè)串行隊(duì)列  
   // 視頻輸出代理
    [videoOutput setSampleBufferDelegate:self queue:vcaptureQueue];
   // 添加到輸出
    [_session addOutput:videoOutput];
    
    // 5. 創(chuàng)建音頻輸出對象
    audioOutput = [[AVCaptureAudioDataOutput alloc] init];
   // acaptureQueue 輸出執(zhí)行的音頻隊(duì)列,它是一個(gè)串行隊(duì)列  
   // 音頻輸出代理
    [audioOutput setSampleBufferDelegate:self queue:acaptureQueue];
   // 添加到輸出
    [_session addOutput:audioOutput];
    // 6. 分別創(chuàng)建音頻和視頻AVCaptureConnection
    /*AVCaptureConnection表示avcaptureputport或端口之間的連接巷怜, 可用AVCaptureVideoPreviewLayer呈現(xiàn)采集    
      的內(nèi)容陵叽。*/
    videoConnection = [videoOutput connectionWithMediaType:AVMediaTypeVideo];
    audioConnection = [audioOutput connectionWithMediaType:AVMediaTypeAudio];
    // 視頻鏡像采集
    videoConnection.videoMirrored = _isMirrored;
    // 設(shè)置采集的方向狞尔,如果不設(shè)置采集到的視頻旋轉(zhuǎn)了90度。
    if([videoConnection isVideoOrientationSupported]) {
        videoConnection.videoOrientation = _orientation;
    }
    // 提交配置
    [_session commitConfiguration];

  1. 以上是初始化的過程巩掺,開始采集和結(jié)束采集還需要調(diào)用:
///  開始采集
- (void)startRunning {
    [_session startRunning];
}
/// 停止采集
- (void)stopRunning {
    [_session stopRunning];
}
  1. 設(shè)置代理
// 這里的sampleBuffer就是采集到的數(shù)據(jù)了,根據(jù)connection來判斷偏序,是Video還是Audio的數(shù)據(jù)
- (void)captureOutput:(AVCaptureOutput *)output didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection
{
    // 這里的sampleBuffer就是采集到的數(shù)據(jù)了,根據(jù)connection來判斷,是Video還是Audio的數(shù)據(jù)
    if (connection == videoConnection) {
        NSLog(@"這里獲的 video sampleBuffer胖替,做進(jìn)一步處理(編碼H.264)%i", ++sampleCount);
        if (self.delegate) {
            [self.delegate capture:self videoBuffer:sampleBuffer];
        }
    } else if (connection == audioConnection) {
        NSLog(@"這里獲得 audio sampleBuffer研儒,做進(jìn)一步處理(編碼AAC)");
        if (self.delegate) {
            [self.delegate capture:self audioBuffer:sampleBuffer];
        }
    }
}
  1. 顯示,使用AVCaptureVideoPreviewLayer可以顯示采集的視頻独令,自定義一個(gè)UIView, 命名CapturePreviewView端朵,設(shè)置layerClass為[AVCaptureVideoPreviewLayer class],設(shè)置layer基本參數(shù):
        self.previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill; 
        self.previewLayer.connection.videoOrientation = AVCaptureVideoOrientationPortrait;

在viewDidLoad里調(diào)用

- (void)showPreview {
    self.preview = [[CapturePreviewView alloc] initWithFrame:self.view.bounds];
    _preview.previewLayer.session = self.capture.session;
    _preview.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
    [self.view insertSubview:_preview atIndex:0];
}
  1. 我們可以對采集到的數(shù)據(jù)進(jìn)行預(yù)覽播放燃箭,使用AVSampleBufferDisplayLayer播放 CMSampleBufferRef格式數(shù)據(jù)
-(void)showSampleLayer {
    _displayLayer = [[AVSampleBufferDisplayLayer alloc] init];
    _displayLayer.frame = CGRectMake(0, 0, _videoEncoder.config->width / 5.0, _videoEncoder.config->height / 5.0);
    _displayLayer.videoGravity = AVLayerVideoGravityResizeAspect;
    [self.preview.layer insertSublayer:_displayLayer above:_preview.previewLayer];
}

// 在采集到視頻數(shù)據(jù)的回調(diào)里播放buffer
-(void)capture:(Capture *)capture videoBuffer:(CMSampleBufferRef _Nullable)buffer
{
    // 顯示播放
     [_displayLayer enqueueSampleBuffer:buffer];
    // [_videoEncoder encode:buffer timeStamp:CACurrentMediaTime()*1000];
}

6.修改采樣參數(shù)冲呢,如采樣率,幀率等等

/// 更新幀率
- (void)updateFps:(int32_t)fps {
 
    AVCaptureDevice *vDevice = [self videoDeviceWitchPosition:_devicePosition];
    //獲取當(dāng)前支持的最大fps
    float maxRate = [(AVFrameRateRange *)[vDevice.activeFormat.videoSupportedFrameRateRanges objectAtIndex:0] maxFrameRate];
    //如果想要設(shè)置的fps小于或等于做大fps招狸,就進(jìn)行修改
    if (maxRate >= fps) {
        //實(shí)際修改fps的代碼
        if ([vDevice lockForConfiguration:NULL]) {
            vDevice.activeVideoMinFrameDuration = CMTimeMake(10, (int)(fps * 10));
            vDevice.activeVideoMaxFrameDuration = vDevice.activeVideoMinFrameDuration;
            [vDevice unlockForConfiguration];
        }
    }
}
/// 切換攝像頭(前置或后置)
- (void)changeCamaraPosition {
    dispatch_async(vcaptureQueue, ^{
        if (self.devicePosition == AVCaptureDevicePositionFront) {
            self.devicePosition = AVCaptureDevicePositionBack;
        } else {
            self.devicePosition = AVCaptureDevicePositionFront;
        }
        AVCaptureDevice *camera = [self videoDeviceWitchPosition:self.devicePosition];
        [self.session beginConfiguration];
        [self.session removeInput:self->_deviceInput];
        
        AVCaptureDeviceInput *videoInput = [[AVCaptureDeviceInput alloc] initWithDevice:camera error:nil];
        if (!videoInput) {
            NSLog(@"can not init video input");
            return;
        }
        if (![self.session canAddInput:videoInput]) {
            NSLog(@"can not add video input");
            return;
        }
        self->_deviceInput = videoInput;
        [self.session addInput:videoInput];

        [self.session commitConfiguration];
        
     });
 
}
/// 設(shè)置視頻采集方向
- (void)setVideoOrientation:(AVCaptureVideoOrientation)orientation
{
    _orientation = orientation;
    dispatch_async(vcaptureQueue, ^{
        self->videoConnection.videoOrientation = orientation;
    });
}
/// 設(shè)置是否鏡像
- (void)setVideoMirrored:(BOOL)isMirrored
{
    _isMirrored = isMirrored;
    dispatch_async(vcaptureQueue, ^{
        self->videoConnection.videoMirrored = isMirrored;
    });
}
/// 設(shè)置采集分辨率
- (void)setVideoDimension:(AVCaptureSessionPreset)preset
{
    _preset = preset;
    dispatch_async(vcaptureQueue, ^{
        [self.session beginConfiguration];
        if ([self.session canSetSessionPreset:preset]) {
            [self.session setSessionPreset:preset];
        };
        [self.session commitConfiguration];
    });
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末敬拓,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子裙戏,更是在濱河造成了極大的恐慌乘凸,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,948評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件累榜,死亡現(xiàn)場離奇詭異营勤,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)壹罚,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,371評論 3 385
  • 文/潘曉璐 我一進(jìn)店門葛作,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人猖凛,你說我怎么就攤上這事进鸠。” “怎么了形病?”我有些...
    開封第一講書人閱讀 157,490評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長霞幅。 經(jīng)常有香客問我漠吻,道長,這世上最難降的妖魔是什么司恳? 我笑而不...
    開封第一講書人閱讀 56,521評論 1 284
  • 正文 為了忘掉前任途乃,我火速辦了婚禮,結(jié)果婚禮上扔傅,老公的妹妹穿的比我還像新娘耍共。我一直安慰自己烫饼,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,627評論 6 386
  • 文/花漫 我一把揭開白布试读。 她就那樣靜靜地躺著杠纵,像睡著了一般。 火紅的嫁衣襯著肌膚如雪钩骇。 梳的紋絲不亂的頭發(fā)上比藻,一...
    開封第一講書人閱讀 49,842評論 1 290
  • 那天,我揣著相機(jī)與錄音倘屹,去河邊找鬼银亲。 笑死,一個(gè)胖子當(dāng)著我的面吹牛纽匙,可吹牛的內(nèi)容都是我干的务蝠。 我是一名探鬼主播,決...
    沈念sama閱讀 38,997評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼烛缔,長吁一口氣:“原來是場噩夢啊……” “哼馏段!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起力穗,我...
    開封第一講書人閱讀 37,741評論 0 268
  • 序言:老撾萬榮一對情侶失蹤毅弧,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后当窗,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體够坐,經(jīng)...
    沈念sama閱讀 44,203評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,534評論 2 327
  • 正文 我和宋清朗相戀三年崖面,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了元咙。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,673評論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡巫员,死狀恐怖庶香,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情简识,我是刑警寧澤赶掖,帶...
    沈念sama閱讀 34,339評論 4 330
  • 正文 年R本政府宣布,位于F島的核電站七扰,受9級特大地震影響奢赂,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜颈走,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,955評論 3 313
  • 文/蒙蒙 一膳灶、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦轧钓、人聲如沸序厉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,770評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽弛房。三九已至,卻和暖如春霉晕,著一層夾襖步出監(jiān)牢的瞬間庭再,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,000評論 1 266
  • 我被黑心中介騙來泰國打工牺堰, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留拄轻,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,394評論 2 360
  • 正文 我出身青樓伟葫,卻偏偏與公主長得像恨搓,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子筏养,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,562評論 2 349

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

  • 推流斧抱,就是將采集到的音頻,視頻數(shù)據(jù)通過流媒體協(xié)議發(fā)送到流媒體服務(wù)器渐溶。 推流前的工作:采集辉浦,處理,編碼壓縮 推流中做...
    小緈福閱讀 2,988評論 1 19
  • 推流茎辐,就是將采集到的音頻宪郊,視頻數(shù)據(jù)通過流媒體協(xié)議發(fā)送到流媒體服務(wù)器。 推流前的工作:采集拖陆,處理弛槐,編碼壓縮 推流中做...
    木馬不在轉(zhuǎn)閱讀 7,382評論 13 30
  • 需求 眾所周知,原始的音視頻數(shù)據(jù)無法直接在網(wǎng)絡(luò)上傳輸,推流需要編碼后的音視頻數(shù)據(jù)以合成的視頻流,如flv, mov...
    小東邪啊閱讀 5,764評論 3 21
  • 一直在忙, 也沒寫過幾次博客! 但一直熱衷于直播開發(fā)技術(shù), 公司又不是直播方向的, 所以就年前忙里偷閑研究了一下直...
    叫我豐叔閱讀 17,319評論 24 132
  • #直播總結(jié) ##1.概述 關(guān)于直播的技術(shù)文章不少,成體系的不多依啰。我們將用這篇文章乎串,更系統(tǒng)化地介紹當(dāng)下大熱的視頻直播...
    蓋世英雄_ix4n04閱讀 1,360評論 0 2