HTTP-FLV實(shí)現(xiàn)局域網(wǎng)點(diǎn)對(duì)點(diǎn)直播

使用HTTP-FLV把iPhone攝像頭的畫面進(jìn)行直播,局域網(wǎng)內(nèi)的設(shè)備可以通過VLC進(jìn)行觀看展鸡,不通過服務(wù)器姨丈,實(shí)現(xiàn)局域網(wǎng)點(diǎn)對(duì)點(diǎn)直播畅卓。
實(shí)現(xiàn)步驟
1、采集iPhone攝像頭畫面
2蟋恬、采集到的數(shù)據(jù)硬編碼成H264數(shù)據(jù)
3翁潘、把編碼的數(shù)據(jù)通過FFmpeg封裝成FLV tag
4、搭建HTTP服務(wù)器監(jiān)聽HTTP連接筋现,連接成功之后發(fā)送數(shù)據(jù)

代碼實(shí)現(xiàn)

1唐础、采集iPhone攝像頭畫面

    _captureSession = [[AVCaptureSession alloc] init];

    _captureSession.sessionPreset = AVCaptureSessionPreset1280x720;
    _captureDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
    NSError * error = nil;
    _captureDeviceInput = [AVCaptureDeviceInput deviceInputWithDevice:_captureDevice error:&error];
    if (_captureDeviceInput) {
        [_captureSession addInput:_captureDeviceInput];
    }
    _captureVideoDataOutput = [[AVCaptureVideoDataOutput alloc] init];
    [_captureVideoDataOutput setAlwaysDiscardsLateVideoFrames:YES];

    NSDictionary * settings = [[NSDictionary alloc] initWithObjectsAndKeys:@(kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange), kCVPixelBufferPixelFormatTypeKey, nil];
    _captureVideoDataOutput.videoSettings = settings;

    dispatch_queue_t queue = dispatch_queue_create("CaptureQueue", NULL);

    [_captureVideoDataOutput setSampleBufferDelegate:self queue:queue];
    [_captureSession addOutput:_captureVideoDataOutput];

    _previewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:_captureSession];
    _previewLayer.frame = CGRectMake(0, 100, self.view.bounds.size.width, self.view.bounds.size.height - 100);
    [self.view.layer addSublayer:_previewLayer];
    [_captureSession startRunning];

2、采集到的數(shù)據(jù)硬編碼成H264數(shù)據(jù)

    //初始化硬編碼器
    OSStatus status = VTCompressionSessionCreate(NULL, 1280, 720, kCMVideoCodecType_H264, NULL, NULL, NULL, didCompressH264, (__bridge void *)self, &_compressionSession);
    if (status != noErr) {
        NSLog(@"Create compressionSession error");
        return;
    }

    VTSessionSetProperty(_compressionSession, kVTCompressionPropertyKey_RealTime, kCFBooleanTrue);
    VTSessionSetProperty(_compressionSession, kVTCompressionPropertyKey_ProfileLevel, kVTProfileLevel_H264_High_AutoLevel);
    VTSessionSetProperty(_compressionSession, kVTCompressionPropertyKey_AllowFrameReordering, kCFBooleanTrue);
    VTSessionSetProperty(_compressionSession, kVTCompressionPropertyKey_MaxKeyFrameInterval, (__bridge CFTypeRef)(@(30)));
    VTSessionSetProperty(_compressionSession, kVTCompressionPropertyKey_ExpectedFrameRate, (__bridge CFTypeRef)(@(30)));
    VTSessionSetProperty(_compressionSession, kVTCompressionPropertyKey_AverageBitRate, (__bridge CFTypeRef)(@(800 * 1024)));
    status = VTCompressionSessionPrepareToEncodeFrames(_compressionSession);
    status = VTCompressionSessionCompleteFrames(_compressionSession, kCMTimeInvalid);
    if (status != noErr) {
        NSLog(@"Prepare error");
    }
//編碼采集到的數(shù)據(jù)
- (void)captureOutput:(AVCaptureOutput *)output didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection {
    CVImageBufferRef imageBuffer = (CVImageBufferRef)CMSampleBufferGetImageBuffer(sampleBuffer);
    CMTime pts = CMSampleBufferGetPresentationTimeStamp(sampleBuffer);
    CMTime dur = CMSampleBufferGetDuration(sampleBuffer);
    VTEncodeInfoFlags flags;
    OSStatus status = VTCompressionSessionEncodeFrame(_compressionSession, imageBuffer, pts, dur, NULL, NULL, &flags);
    if (status != noErr) {
        NSLog(@"Encode fail");
    } 
    //此處編碼也可以使用FFmpeg進(jìn)行軟編碼CVImageBufferRef是采集出的像素?cái)?shù)據(jù)矾飞,和CVPixelBufferRef一樣一膨,可以取出yuv數(shù)據(jù)傳入FFmpeg中進(jìn)行編碼
}

3、把編碼的數(shù)據(jù)通過FFmpeg封裝成FLV tag

int ret = avformat_alloc_output_context2(&ofmt_ctx, NULL, "flv", NULL);
if (ret < 0) {
  NSLog(@"Could not allocate output format context!");
}
//這里我們通過write_buffer方法把數(shù)據(jù)寫入內(nèi)存通過HTTP發(fā)送出去洒沦,而不是寫入文件或服務(wù)器地址豹绪,需要?jiǎng)?chuàng)建AVIOContext然后賦值給AVFormatContext
//這里申請(qǐng)的AVIOContext要通過avio_context_free()釋放
unsigned char * outBuffer = (unsigned char *)av_malloc(32768);
AVIOContext * avio_out = avio_alloc_context(outBuffer, 32768, 1, NULL, NULL, write_buffer, NULL);
ofmt_ctx->pb = avio_out;
ofmt_ctx->flags = AVFMT_FLAG_CUSTOM_IO;

AVCodec * codec = avcodec_find_encoder(AV_CODEC_ID_H264);
out_stream = avformat_new_stream(ofmt_ctx, codec);
codec_ctx = avcodec_alloc_context3(codec);

AVRational dst_fps = {30, 1};
codec_ctx->codec_tag = 0;
codec_ctx->codec_id = AV_CODEC_ID_H264;
codec_ctx->codec_type = AVMEDIA_TYPE_VIDEO;
codec_ctx->width = 1280;
codec_ctx->height = 720;
codec_ctx->gop_size = 12;
codec_ctx->pix_fmt = AV_PIX_FMT_NV12;
codec_ctx->framerate = dst_fps;
codec_ctx->time_base = av_inv_q(dst_fps);
if(ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER) {
  codec_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
}
ret = avcodec_parameters_from_context(out_stream->codecpar, codec_ctx);
if (ret < 0) {
  NSLog(@"Could not initialize stream codec parameters!");
}
AVDictionary * codec_options = NULL;
av_dict_set(&codec_options, "profile", "high", 0);
av_dict_set(&codec_options, "preset", "superfast", 0);
av_dict_set(&codec_options, "tune", "zerolatency", 0);

ret = avcodec_open2(codec_ctx, codec, &codec_options);
if (ret < 0) {
  NSLog(@"Could not open video encoder!");
}

out_stream->codecpar->extradata = codec_ctx->extradata;
out_stream->codecpar->extradata_size = codec_ctx->extradata_size;

ret = avformat_write_header(ofmt_ctx, NULL);
if (ret < 0) {
  NSLog(@"Could not write header!");
}
static int write_buffer(void * opaque, uint8_t * buf, int buf_size) {
  //在avformat_write_header的時(shí)候這里得到的數(shù)據(jù)是FLV文件的頭部,在av_write_frame的時(shí)候這里得到的是FLV tag數(shù)據(jù),可以通過HTTP發(fā)送出去瞒津,我使用的GCDWebServer庫(kù)
  //這里buf_size上限是我們創(chuàng)建時(shí)的32768蝉衣,如果每個(gè)tag數(shù)據(jù)小于32768就會(huì)得到完整的tag數(shù)據(jù),如果tag數(shù)據(jù)大于32768就會(huì)得到部分tag數(shù)據(jù)巷蚪,要自行處理
  return 0;
}
static void didCompressH264(void *outputCallbackRefCon, void *sourceFrameRefCon, OSStatus status, VTEncodeInfoFlags infoFlags, CMSampleBufferRef sampleBuffer ) {
  //iOS硬編碼的H264數(shù)據(jù)是AVCC格式的病毡,每個(gè)NALU的前4個(gè)字節(jié)是數(shù)據(jù)長(zhǎng)度,AVCC格式的數(shù)據(jù)直接寫入文件是不能播放的屁柏,需要轉(zhuǎn)換成0x00000001開始碼開頭的Annex B格式
  if (status != noErr) {
    NSLog(@"Compress H264 failed");
    return;
  }
  if (!CMSampleBufferDataIsReady(sampleBuffer)) {
    return;
  }
  CMBlockBufferRef dataBuffer = CMSampleBufferGetDataBuffer(sampleBuffer);
  size_t length, totalLength;
  char * dataPointer;
  const char bytesHeader[] = "\x00\x00\x00\x01";
  OSStatus statusCodeRet = CMBlockBufferGetDataPointer(dataBuffer, 0, &length, &totalLength, &dataPointer);
  bool keyFrame = !CFDictionaryContainsKey((CFDictionaryRef)(CFArrayGetValueAtIndex(CMSampleBufferGetSampleAttachmentsArray(sampleBuffer, true), 0)), (const void)kCMSampleAttachmentKey_NotSync);
  NSMutableData * pktData = [NSMutableData data];
  if (keyFrame) {
    CMFormatDescriptionRef format = CMSampleBufferGetFormatDescription(sample);
    size_t sparameterSetSize, sparameterSetCount;
    const uint8_t * sparameterSet;
    status = CMVideoFormatDescriptionGetH264ParameterSetAtIndex(format, 0, &sparameterSet, &sparameterSetSize, &sparameterSetCount, 0);
    if (status == noErr) {
      size_t pparameterSetSize, pparameterSetCount;
      const uint8_t * pparameterSet;
      status = CMVideoFormatDescriptionGetH264ParameterSetAtIndex(format, 1, &pparameterSet, &pparameterSetSize, &pparameterSetCount, 0);
      if (status == noErr) {
        size_t headerLength = 4;
        size length = 2 * headerLength + sparameterSetSize + pparameterSetSize;
        unsigned char * buffer = (unsigned char *)malloc(sizeof(unsigned char) * length);
        memcpy(buffer, bytesHeader, headerLength);
        memcpy(buffer + headerLength, sparameterSet, sparameterSetSize);
        memcpy(buffer + headerLength + sparameterSetSize, bytesHeader, headerLength);
        memcpy(buffer + headerLength + sparameterSetSize + headerLength, pparameterSet, pparameterSetSize);
        [pktData appendBytes:buffer length:length];
      }
    }
  }
  size_t bufferOffset = 0;
  int AVCCHeaderLength = 4;
  while(bufferOffset < totalLength - AVCCHeaderLength) {
    uint32_t NALUintLength = 0;
    memcpy(&NALUnitLength, dataPointer + bufferOffset, AVCCHeaderLength);
    NALUnitLength = CFSwapInt32BigToHost(NALUnitLength);
    unsigned char * buffer = (unsigned char *)malloc(sizeof(unsigned char) * (NALUnitLength + AVCCHeaderLength));
    memcpy(buffer, bytesHeader, AVCCHeaderLength);
    memcpy(buffer + AVCCHeaderLength, dataPointer + bufferOffset + AVCCHeaderLength, NALUnitLength);
    [pktData appendBytes:buffer length:NALUnitLength + AVCCHeaderLength];
    bufferOffset += AVCCHeaderLength + NALUnitLength;
  }
  AVPacket pkt = {0};
  av_init_packet(&pkt);
  pkt.data = (uint8_t *)[pktData bytes];
  pkt.size = (int)[pktData length];
  //pkt_pts從0開始遞增
  pkt.pts = pkt_pts;
  pkt.dts = pkt.pts;
  if (keyFrame) {
    pkt.flags = AV_PKT_FLAG_KEY;
  } else {
    pkt.flags = 0;
  }
  pkt.stream_index = 0;
  av_packet_rescale_ts(&pkt, codec_ctx->time_base, out_stream->time_base);
  av_write_frame(ofmt_ctx, &pkt);
  pkt_pts++;
}

通過VLC可以觀看直播啦膜,通過ffplay播放黑屏,原因還未發(fā)現(xiàn)淌喻。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末僧家,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子裸删,更是在濱河造成了極大的恐慌八拱,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,496評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件涯塔,死亡現(xiàn)場(chǎng)離奇詭異肌稻,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)匕荸,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門灯萍,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人每聪,你說我怎么就攤上這事〕莘纾” “怎么了药薯?”我有些...
    開封第一講書人閱讀 162,632評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)救斑。 經(jīng)常有香客問我童本,道長(zhǎng),這世上最難降的妖魔是什么脸候? 我笑而不...
    開封第一講書人閱讀 58,180評(píng)論 1 292
  • 正文 為了忘掉前任穷娱,我火速辦了婚禮,結(jié)果婚禮上运沦,老公的妹妹穿的比我還像新娘泵额。我一直安慰自己,他們只是感情好携添,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,198評(píng)論 6 388
  • 文/花漫 我一把揭開白布嫁盲。 她就那樣靜靜地躺著,像睡著了一般烈掠。 火紅的嫁衣襯著肌膚如雪羞秤。 梳的紋絲不亂的頭發(fā)上缸托,一...
    開封第一講書人閱讀 51,165評(píng)論 1 299
  • 那天,我揣著相機(jī)與錄音瘾蛋,去河邊找鬼俐镐。 笑死,一個(gè)胖子當(dāng)著我的面吹牛哺哼,可吹牛的內(nèi)容都是我干的佩抹。 我是一名探鬼主播,決...
    沈念sama閱讀 40,052評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼幸斥,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼匹摇!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起甲葬,我...
    開封第一講書人閱讀 38,910評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤廊勃,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后经窖,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體坡垫,經(jīng)...
    沈念sama閱讀 45,324評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,542評(píng)論 2 332
  • 正文 我和宋清朗相戀三年画侣,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了冰悠。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,711評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡配乱,死狀恐怖溉卓,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情搬泥,我是刑警寧澤桑寨,帶...
    沈念sama閱讀 35,424評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站忿檩,受9級(jí)特大地震影響尉尾,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜燥透,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,017評(píng)論 3 326
  • 文/蒙蒙 一沙咏、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧班套,春花似錦肢藐、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春瞻讽,著一層夾襖步出監(jiān)牢的瞬間鸳吸,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工速勇, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留晌砾,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,722評(píng)論 2 368
  • 正文 我出身青樓烦磁,卻偏偏與公主長(zhǎng)得像养匈,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子都伪,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,611評(píng)論 2 353

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

  • ### YUV顏色空間 視頻是由一幀一幀的數(shù)據(jù)連接而成呕乎,而一幀視頻數(shù)據(jù)其實(shí)就是一張圖片。 yuv是一種圖片儲(chǔ)存格式...
    天使君閱讀 3,282評(píng)論 0 4
  • 0 概述 FFmpeg是一套領(lǐng)先的音視頻多媒體處理開源框架陨晶,采用LGPL或GPL許可證猬仁。它提供了對(duì)音視頻的采集、編...
    但行耕者閱讀 6,812評(píng)論 0 19
  • 前言 如此強(qiáng)大的FFmpeg先誉,能夠?qū)崿F(xiàn)視頻采集湿刽、視頻格式轉(zhuǎn)化、視頻截圖褐耳、視頻添加水印诈闺、視頻切片造寝、視頻錄制沦偎、視頻推流...
    騷之哈塞給閱讀 25,753評(píng)論 6 39
  • 前言 如此強(qiáng)大的FFmpeg,能夠?qū)崿F(xiàn)視頻采集捺信、視頻格式轉(zhuǎn)化刃滓、視頻截圖漓穿、視頻添加水印、視頻切片注盈、視頻錄制、視頻推流...
    sillen閱讀 5,384評(píng)論 2 45
  • 聽老師說叙赚,今天洋洋在學(xué)校里運(yùn)動(dòng)量比較大老客,中午早早就睡著了。 晚上震叮,我七點(diǎn)多回到家胧砰,他見到我的第一個(gè)要求...
    Sam爸很忙閱讀 291評(píng)論 0 1