FFmpeg學(xué)習(xí)之開發(fā)Mac播放器(六):FFmpeg與Mac編解碼器混合使用

Mac和iOS支持使用VideoToolBox硬件編解碼H264和H265的視頻流痕鳍,這次使用FFmpeg解封裝使用VideoToolBox解碼器解碼赔退,還有從Mac采集的數(shù)據(jù)用FFmpeg編碼封裝氯夷。

FFmpeg解封裝+VideoToolBox解碼
FFmpeg中AVPacket對應(yīng)Mac中的CMBlockBufferRef

//用于解析AVCodecContext->extradata中的sps和pps
static void parseH264SequenceHeader(uint8_t * extra_data, uint8_t ** sps, size_t * sps_size, uint8_t ** pps, size_t * pps_size) {
    int spsSize = (extra_data[6] << 8) + extra_data[7];
    *sps_size = spsSize;
    *sps = &extra_data[8];
    int ppsSize = (extra_data[8 + spsSize + 1] << 8) + extra_data[8 + spsSize + 2];
    *pps = &extra_data[8 + spsSize + 3];
    *pps_size = ppsSize;
}

- (void)main {
    ...
    //初始化VideoToolBox解碼器
    parseH264SequenceHeader(codec_ctx->extradata, &sps, &sps_size, &pps, &pps_size);
    uint8_t * parameterSetPointers[2] = {sps, pps};
    size_t parameterSetSizes[2] = {sps_size, pps_size};
    OSStatus status = CMVideoFormatDescriptionCreateFromH264ParameterSets(kCFAllocatorDefault, 2, (const uint8_t * const *)parameterSetPointers, parameterSetSizes, 4, &formatDescRef);
    if (status != noErr) {
        NSLog(@"Create video description failed...");
    }
    VTDecompressionOutputCallbackRecord callback;
    callback.decompressionOutputCallback = didDecompress;
    callback.decompressionOutputRefCon = (__bridge void *)self;
    NSDictionary * destinationImageBufferAttributes = @{(id)kCVPixelBufferPixelFormatTypeKey: @(kCVPixelFormatType_420YpCbCr8Planar)};
    status = VTDecompressionSessionCreate(kCFAllocatorDefault, formatDescRef, NULL, (__bridge CFDictionaryRef)destinationImageBufferAttributes, &callback, &sessionRef);
    if (status != noErr) {
        NSLog(@"Create decompression session failed status = %@", @(status));
    }
    ...
    AVPacket * pkt = av_packet_alloc();
    while (av_read_frame(format_ctx, pkt) >= 0) {
        if (pkt->stream_index == video_index) {
            CMBlockBufferRef blockBuffer = NULL;
            //使用AVPacket中的數(shù)據(jù)直接創(chuàng)建BlockBuffer纵朋,AVPacket中的nalu數(shù)據(jù)需要AVCC格式雷客,如果是Annex B格式要轉(zhuǎn)換成AVCC格式
            OSStatus status = CMBlockBufferCreateWithMemoryBlock(kCFAllocatorDefault, pkt->data, pkt->size, kCFAllocatorNull, NULL, 0, pkt->size, 0, &blockBuffer);
            if (status != kCMBlockBufferNoErr) {
                NSLog(@"Create BlockBuffer failed status = %@", @(status));
            }
            const size_t sampleSize = pkt->size;
            CMSampleBufferRef sampleBuffer = NULL;
            status = CMSampleBufferCreate(kCFAllocatorDefault, blockBuffer, true, NULL, NULL, formatDescRef, 1, 0, NULL, 1, &sampleSize, &sampleBuffer);
            if (status != noErr) {
                NSLog(@"SampleBuffer create failed");
            }
            
            VTDecodeFrameFlags flags = kVTDecodeFrame_EnableAsynchronousDecompression;
            VTDecodeInfoFlags flagOut;
            status = VTDecompressionSessionDecodeFrame(sessionRef, sampleBuffer, flags, &sampleBuffer, &flagOut);
            if (status == noErr) {
                VTDecompressionSessionWaitForAsynchronousFrames(sessionRef);
            }
            CFRelease(blockBuffer);
            CFRelease(sampleBuffer);
        }
    }
}
//解碼回調(diào)方法
void didDecompress( void *decompressionOutputRefCon, void *sourceFrameRefCon, OSStatus status, VTDecodeInfoFlags infoFlags, CVImageBufferRef imageBuffer, CMTime presentationTimeStamp, CMTime presentationDuration ) {
  if (status == noError && imageBuffer) {
    for (int i = 0; i < CVPixelBufferGetPlaneCount(imageBuffer); i++) {
      void * baseAddress = CVPixelBufferGetBaseAddressOfPlane(imageBuffer, i);
      size_t bytesPerRow = CVPixelBufferGetBytesPerRowOfPlane(imageBuffer, i);
      size_t height = CVPixelBufferGetHeightOfPlane(imageBuffer, i);
      fwrite(baseAddress, bytesPerRow * height, 1, file);
      //這里直接寫入yuv文件會出現(xiàn)問題裙秋,VideoToolBox解碼得到的數(shù)據(jù)是按照DTS順序容燕,需要按照PTS排序然后再寫入yuv文件
    }
  }
}
7D30E197-D064-4763-9D20-F734B7BBBD37.png
Mac采集+FFmpeg編碼

- (void)initEncoder {
  format_ctx = avformat_alloc_context();
  if (avformat_alloc_output_context2(&format_ctx, NULL, NULL, outputString.UTF8String) < 0) {
    NSLog(@"Open output path failed");
  }
  codec = avcodec_find_encoder(AV_CODEC_ID_H264);
  codec_ctx = avcodec_alloc_context3(codec);
  codec_ctx->bit_rate = 5000000;
  codec_ctx->width = 1280;  //使用AVFoundation設(shè)置攝像頭采集視頻的寬和高
  codec_ctx->height = 720;
  codec_ctx->time_base = (AVRational){1, 24};  //視頻為24幀
  codec_ctx->framerate = (AVRational){24, 1};
  codec_ctx->gop_size = 10;
  codec_ctx->max_b_frames = 1;
  codec_ctx->pix_fmt = AV_PIX_FMT_NV12;  //Mac和iPhone攝像頭采集的pixel format為NV12
  codec_ctx->color_range = AVCOL_RANGE_JPEG;
  av_opt_set(codec_ctx->priv_data, "present", "slow", 0);

  if (format_ctx->oformat->flags & AVFMT_GLOBALHEADER) {
    codec_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; //填充codec_ctx中extradata
  }

  AVStream * stream = avformat_new_stream(format_ctx, NULL); //創(chuàng)建視頻流
  ret = avcodec_parameters_from_context(stream->codecpar, codec_ctx);
  if (ret < 0) {
    NSLog(@"Failed to copy encoder parameters to output stream 0");
  }
  stream->time_base = codec_ctx->time_base;

  av_dump_format(format_ctx, 0, outputString.UTF8String, 1);
  if (!(format_ctx->oformat->flags & AVFMT_NOFILE)) {
    ret = avio_open(&format_ctx->pb, outputString.UTF8String, AVIO_FLAG_WRITE);
    if (ret < 0) {
      NSLog(@"Could not open output file");
    }
  }
    
  ret = avformat_write_header(format_ctx, NULL);
  if (ret < 0) {
    NSLog(@"Error occurred when opening output file");
  }
}
//攝像頭采集回調(diào)方法
- (void)captureOutput:(AVCaptureOutput *)output didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection {
  CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
  CMTime duration = CMSampleBufferGetDuration(sampleBuffer);
  
  AVFrame * frame = av_frame_alloc(); //創(chuàng)建AVFrame存儲像素數(shù)據(jù)
  frame->height = (int)CVPixelBufferGetHeight(imageBuffer);
  frame->width = (int)CVPixelBufferGetWidth(imageBuffer);
  frame->format = AV_PIX_FMT_NV12;
  frame->color_range = AVCOL_RANGE_JPEG;
  av_frame_get_buffer(frame, 0); //為frame中存儲像素數(shù)據(jù)的data分配空間梁呈,調(diào)用這個方法之前要設(shè)置pixel format(視頻)或者sample format(音頻),視頻的寬高蘸秘,音頻的nb_samples和channel_layout

  CVPixelBufferLockBaseAddress(imageBuffer, 0);
  void * baseAddress = CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 0);
  size_t bytesPerRow = CVPixelBufferGetBytesPerRowOfPlane(imageBuffer, 0);
  size_t height = CVPixelBufferGetHeightOfPlane(imageBuffer, 0);
  frame->linesize[0] = (int)bytesPerRow;
  memcpy(frame->data[0], baseAddress, bytesPerRow * height);
    
  baseAddress = CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 1);
  bytesPerRow = CVPixelBufferGetBytesPerRowOfPlane(imageBuffer, 1);
  height = CVPixelBufferGetHeightOfPlane(imageBuffer, 1);
  frame->linesize[1] = (int)bytesPerRow;
  memcpy(frame->data[1], baseAddress, bytesPerRow * height);
  CVPixelBufferUnlockBaseAddress(imageBuffer, 0);

  int ret = avcodec_send_frame(codec_ctx, frame);
  AVPacket * pkt = av_packet_alloc();
  while (ret >= 0) {
    ret = avcodec_receive_packet(codec_ctx, pkt);
    if (ret == AVERROR(EAGAIN)) {
      NSLog(@"Output is not available in the current state");
      break;
    } else if (ret == AVERROR_EOF) {
      NSLog(@"The encoder has been fully flushed, and there will be no more output packets");
      break;
    } else if (ret < 0) {
      NSLog(@"Error during encoding");
      break;
    }
        
    pkt->stream_index = 0;
    pkt->pts = _pts;
    pkt->dts = pkt->pts;
    pkt->duration = duration.value;
        
    _pts++;
        
    av_packet_rescale_ts(pkt, codec_ctx->time_base, format_ctx->streams[0]->time_base);
    if (av_write_frame(format_ctx, pkt) >= 0) {
      NSLog(@"Write success");
    }
  }
  av_packet_free(&pkt);
  av_frame_free(&frame);
}

- (void)EndRecord {
  [_captureSession stopRunning];

  int ret = avcodec_send_frame(codec_ctx, NULL); //flush data
  AVPacket * pkt = av_packet_alloc();
  while (ret >= 0) {
    ret = avcodec_receive_packet(codec_ctx, pkt);
    if (ret == AVERROR(EAGAIN)) {
      NSLog(@"Output is not available in the current state");
      break;
    } else if (ret == AVERROR_EOF) {
      NSLog(@"The encoder has been fully flushed, and there will be no more output packets");
      break;
    } else if (ret < 0) {
      NSLog(@"Error during encoding");
      break;
    }
    pkt->stream_index = 0;
    pkt->pts = _pts;
    pkt->dts = pkt->pts;
    pkt->duration = duration.value;
    av_packet_rescale_ts(pkt, codec_ctx->time_base, format_ctx->streams[0]->time_base);
    if (av_write_frame(format_ctx, pkt) >= 0) {
      NSLog(@"Write success");
    }
    _pts++;
  }
  avcodec_close(codec_ctx);
  av_write_trailer(format_ctx);
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末官卡,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子醋虏,更是在濱河造成了極大的恐慌寻咒,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,509評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件颈嚼,死亡現(xiàn)場離奇詭異毛秘,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)阻课,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評論 3 394
  • 文/潘曉璐 我一進(jìn)店門叫挟,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人限煞,你說我怎么就攤上這事抹恳。” “怎么了晰骑?”我有些...
    開封第一講書人閱讀 163,875評論 0 354
  • 文/不壞的土叔 我叫張陵适秩,是天一觀的道長绊序。 經(jīng)常有香客問我硕舆,道長,這世上最難降的妖魔是什么骤公? 我笑而不...
    開封第一講書人閱讀 58,441評論 1 293
  • 正文 為了忘掉前任抚官,我火速辦了婚禮,結(jié)果婚禮上阶捆,老公的妹妹穿的比我還像新娘凌节。我一直安慰自己,他們只是感情好洒试,可當(dāng)我...
    茶點故事閱讀 67,488評論 6 392
  • 文/花漫 我一把揭開白布倍奢。 她就那樣靜靜地躺著,像睡著了一般垒棋。 火紅的嫁衣襯著肌膚如雪卒煞。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,365評論 1 302
  • 那天叼架,我揣著相機(jī)與錄音畔裕,去河邊找鬼衣撬。 笑死,一個胖子當(dāng)著我的面吹牛扮饶,可吹牛的內(nèi)容都是我干的具练。 我是一名探鬼主播,決...
    沈念sama閱讀 40,190評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼甜无,長吁一口氣:“原來是場噩夢啊……” “哼扛点!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起毫蚓,我...
    開封第一講書人閱讀 39,062評論 0 276
  • 序言:老撾萬榮一對情侶失蹤占键,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后元潘,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體畔乙,經(jīng)...
    沈念sama閱讀 45,500評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,706評論 3 335
  • 正文 我和宋清朗相戀三年翩概,在試婚紗的時候發(fā)現(xiàn)自己被綠了牲距。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,834評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡钥庇,死狀恐怖牍鞠,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情评姨,我是刑警寧澤难述,帶...
    沈念sama閱讀 35,559評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站吐句,受9級特大地震影響胁后,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜嗦枢,卻給世界環(huán)境...
    茶點故事閱讀 41,167評論 3 328
  • 文/蒙蒙 一攀芯、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧文虏,春花似錦侣诺、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至丸相,卻和暖如春搔确,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評論 1 269
  • 我被黑心中介騙來泰國打工妥箕, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留滥酥,地道東北人。 一個月前我還...
    沈念sama閱讀 47,958評論 2 370
  • 正文 我出身青樓畦幢,卻偏偏與公主長得像坎吻,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子宇葱,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,779評論 2 354