FFmpeg的软解和硬解(android ffmpeg 硬解码)
前言
其实使用原生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
的情况下进行的解码,如果是未知的情况下可以用过读取流信息来赋予他解码信息
配置解码信息
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; }
作者:幻想无极
链接:https://juejin.cn/post/7054385866365485069