前语

其实运用原生FFmpeg的软解和硬解很简单,硬解以iOS为例只需要编译的库里边含有videotoolbox模块。
注意:如果是运用ijk编译的库是直接调用硬解是不会成功的

软解流程

1.注册一切解码器

avcodec_register_all();

2.找到h264编解码器信息

AVCodec * codec_dec = avcodec_find_decoder(AV_CODEC_ID_H264);

3.通过编解码器信息创建上下文

AVCodecContext * c_dec = avcodec_alloc_context3(codec_dec);

4.通过装备参数初始化详细的解码器(第三个参数为AVDictionary能够装备解码器的详细参数)

        if (avcodec_open2(c_dec, codec_dec,NULL) < 0)
        {
            av_free(c_dec);
            return NULL;
        }

5.初始化AVFrame用于接收解码后的数据

AVFrame * pic_tmp = av_frame_alloc();
    if(pic_tmp == NULL)
    {
        avcodec_close(c_dec);
        av_free(c_dec);
        return ;
    }

6.Socket收到数据包后在CADisplayLink中调用解码

  • FramePacket:是自定义的传输数据包
  • XYQCodecCtx:便是AVCodecContext
  • XYQFrame:是AVFrame
  • 最终运用YUV420p烘托出来
  • _pframeList缓存数据列表
- (UIImage *)decodeAndDisplay:(FramePacket *)fp{
    AVPacket       packet;
    av_new_packet(&packet, fp->len);
    memcpy(packet.data, fp->data, fp->len);
    int ret=0;
    ret = avcodec_send_packet(XYQCodecCtx, &packet);
    if (ret != 0) {
        NSLog(@"----6");
        av_packet_unref(&packet);
        [self->_pframeList DPListPopFront];
        return nil;
    }
    ret = avcodec_receive_frame(XYQCodecCtx, XYQFrame);
    if (ret == 0){
        NSLog(@"----7");
        dispatch_async(dispatch_get_main_queue(), ^{
            [self aVFrameToYUV420pToDisplay:self->XYQFrame];
        });
    }
    av_packet_unref(&packet);
    [self->_pframeList DPListPopFront];
    return nil;
}

硬解流程

和软解流程差不多只需要在第四步avcodec_open2前增加硬解的参数装备

1.找到videotoolbox硬解

    const char *codecName = av_hwdevice_get_type_name(AV_HWDEVICE_TYPE_VIDEOTOOLBOX);
    enum AVHWDeviceType type = av_hwdevice_find_type_by_name(codecName);
    if (type != AV_HWDEVICE_TYPE_VIDEOTOOLBOX) {
        return NULL;
    }

2.初始化硬解,将硬解加入到上下文中

AVBufferRef *hw_device_ctx = NULL;
static int InitHardwareDecoder(AVCodecContext *ctx, const enum AVHWDeviceType type) {
    int err = av_hwdevice_ctx_create(&hw_device_ctx, type, NULL, NULL, 0);
    if (err < 0) {
        log4cplus_error("XDXParseParse", "Failed to create specified HW device.\n");
        return err;
    }
    ctx->hw_device_ctx = av_buffer_ref(hw_device_ctx);
    return err;
}

最终有一点需要注意,在硬解中我们是直接从AVFrame能拿到CVPixelBufferRef数据的,他是在data的第四个数据,而在软解中运用的是前三个数据

CVPixelBufferRef pixelBuffer = (CVPixelBufferRef)videoFrame->data[3];

拿到之后就能运用opengl烘托了,这儿就不多赘述了

扩展

前面是已知h264的情况下进行的解码,如果是未知的情况下能够用过读取流信息来赋予他解码信息

  1. 装备解码信息
  • formatContext:AVFormatContext
   int ret = av_find_best_stream(formatContext, AVMEDIA_TYPE_VIDEO, -1, -1, &codec, 0);
   if (ret < 0) {
        log4cplus_error(kModuleName, "av_find_best_stream faliture");
        return NULL;
   }

2.装备解码参数

    ret = avcodec_parameters_to_context(codecContext, formatContext->streams[videoStreamIndex]->codecpar);
    if (ret < 0){
        log4cplus_error(kModuleName, "avcodec_parameters_to_context faliture");
        return NULL;
    }

翻开文件流的代码(创建AVFormatContext)

- (AVFormatContext *)createFormatContextbyFilePath:(NSString *)filePath {
    if (filePath == nil) {
        log4cplus_error(kModuleName, "%s: file path is NULL",__func__);
        return NULL;
    }
    AVFormatContext  *formatContext = NULL;
    AVDictionary     *opts          = NULL;
    av_dict_set(&opts, "timeout", "1000000", 0);//设置超时1秒
    formatContext = avformat_alloc_context();
    BOOL isSuccess = avformat_open_input(&formatContext, [filePath cStringUsingEncoding:NSUTF8StringEncoding], NULL, &opts) < 0 ? NO : YES;
    av_dict_free(&opts);
    if (!isSuccess) {
        if (formatContext) {
            avformat_free_context(formatContext);
        }
        return NULL;
    }
    if (avformat_find_stream_info(formatContext, NULL) < 0) {
        avformat_close_input(&formatContext);
        return NULL;
    }
    return formatContext;
}