首页 » PHP教程 » phpffmpegrtmp技巧_FFmpeg解码的软解及硬解cuda和qsv运用方法

phpffmpegrtmp技巧_FFmpeg解码的软解及硬解cuda和qsv运用方法

访客 2024-11-26 0

扫一扫用手机浏览

文章目录 [+]

int32_t iRet = -1; // 末了一个包解码完成后,须要取完解码器中剩余的缓存帧;// 调用avcodec_send_packet时塞空包进去,;// 解码器就会知道所有包解码完成,再调用avcodec_receive_frame时,将会取出缓存帧;// AVPacket packet;// av_init_packet(&packet);// pkt.data = NULL;// pkt.size = 0;// avcodec_send_packet(ctx, pkt); iRet = avcodec_send_packet(ctx, pkt);if (iRet != 0 && iRet != AVERROR(EAGAIN)) { get_ffmepg_err_str(iRet); if (iRet == AVERROR_EOF) iRet = 0; return iRet;} while (true) { // 每解出来一帧,丢到行列步队中; iRet = avcodec_receive_frame(ctx, frame); if (iRet != 0) { if (iRet == AVERROR(EAGAIN)) { return 0; } else return iRet; } PushRenderBuffer(); // 音频解码后,如果须要重采样,也可以在此处进行resample; }

以前的版本解码办法为:

int32_t iRet = -1;iRet = avcodec_send_packet(ctx, pkt);if (iRet != 0 && iRet != AVERROR(EAGAIN)) { get_ffmepg_err_str(iRet); if (iRet == AVERROR_EOF) iRet = 0; return iRet;} avcodec_decode_video2(pCodecCtx, frame, iGotPicture, pkt);

新旧版本更新时,把稳接口的利用方法,新版本avcodec_send_packet一次,须要循环调用avcodec_receive_frame多次,返回EAGAIN后,结束当前这次的解码,音频解码也是一样

phpffmpegrtmp技巧_FFmpeg解码的软解及硬解cuda和qsv运用方法

C++音视频开拓学习资料:点击领取→音视频开拓(资料文档+视频教程+口试题)(FFmpeg+WebRTC+RTMP+RTSP+HLS+RTP)

phpffmpegrtmp技巧_FFmpeg解码的软解及硬解cuda和qsv运用方法
(图片来自网络侵删)
一、软件解码:

先上一段代码,再做下解释,这是解码视频文件,以AVStream的办法获取文件信息,再创建解码器

int32_t ret = avformat_open_input(&m_pAVFormatIC, pPath, NULL, NULL);if (avformat_find_stream_info(m_pAVFormatIC, NULL) < 0) { return;}for (uint32_t i = 0; i < m_pAVFormatIC->nb_streams; i++) { switch (m_pAVFormatIC->streams[i]->codec->codec_type) { case AVMEDIA_TYPE_VIDEO: // 视频流; AVCodec find_codec = avcodec_find_decoder(m_pAVFormatIC->streams[i]->codec->codec_id); AVCodecContext codec_ctx = avcodec_alloc_context3(find_codec); avcodec_parameters_to_context(codec_ctx, m_pAVFormatIC->streams[i]->codecpar); codec_ctx->opaque = this; codec_ctx->thread_count = 5; codec_ctx->thread_safe_callbacks = 1; // avcodec_receive_frame的frame数据内存交由自己申请,自己开释,减少内存申请及拷贝; codec_ctx->get_buffer2 = DemuxStream::CustomFrameAllocBuffer; avcodec_open2(codec_ctx, find_codec, NULL); continue; case AVMEDIA_TYPE_AUDIO: // 音频流; // 同上; continue; }}

1、m_pAVFormatIC->streams[i]->codec->codec_type 来判断是否包含音频和视频数据,如果是mp3文件,codec_type有Video是表示MP3的封面图片帧;

2、avcodec_find_decoder和avcodec_alloc_context3创建的指针,不须要覆盖stream中的指针,这是缺点的用法,stream只是记录当前文件的流信息,不要用来保存context,创建的context由自己来保管;

3、avcodec_parameters_to_context,一定要做,这是把解析到的留信息设置到context,不须要在自己一个参数一个参数的设置;

4、多线程软解,thread_count不修正的话默认值是1,利用单个线程进行解码,碰着一些大文件,比如4K,30帧以上的视频流时,解码速率是跟不上的,有两种方案:多线程软解或者硬件解码(后面说)

thread_count为0,表示由ffmpeg调用最大线程数来进行软解,我的电脑的6核12线程,被创建了13个线程进行解码,实际上这会资源过剩,可以根据实际利用情形设置指定线程数量进行解码,这里我设置的是5,

牢记!
在设置thread_count大于1的值时,一定要把thread_safe_callbacks设置为1,否则的话必定会产生崩溃;

默认时,thread_safe_callbacks是为0,thread_count是1或者0的时候,都不须要这个标记

5、自行分配avcodec_receive_frame中frame的数据内存

调用avcodec_receive_frame获取到解码完成后的视频帧,如果须要获取视频的数据流,须要将frame中的data拷贝到连续的内存地址中,详细方法:

uint8_t AllocBufferByFrame(const AVFrame frame){ if (!frame) return nullptr; int32_t s = av_image_get_buffer_size((AVPixelFormat)frame->format, frame->width, frame->height, 1); if (s <= 0) return nullptr; return new uint8_t[s]{0};} int32_t FillBufferFromFrame(uint8_t alloced_buffer, const AVFrame frame){ if (!alloced_buffer || !frame) return -1; uint8_t dst_data[4]{}; int dst_linesize[4]{}; int32_t ret = av_image_fill_arrays(dst_data, dst_linesize, alloced_buffer, (AVPixelFormat)frame->format, frame->width, frame->height, 1); if (ret <= 0) return ret; av_image_copy(dst_data, dst_linesize, (const uint8_t )frame->data, frame->linesize, (AVPixelFormat)frame->format, frame->width, frame->height); return ret;} uint8_t buffer = AllocBufferByFrame(frame);FillBufferFromFrame(buffer, frame);// 利用完buffer的地方,开释buffer的内存;

按照这种利用方法,同一帧的数据会申请两份,解码器中申请了一份,拷贝到buffer中一份,增加了内存花费以及数据拷贝,ffmpeg供应理解码时frame的内存由自己管理的回调接口,get_buffer2,这个回调接口在调用avcodec_receive_frame时,如果自定义了函数指针,将会调用自定义的函数接口,在接口内完成frame的data,linesize,buf的内容添补,在回调时,frame中的format,width,height已经被添补,可以直接拿来利用,把稳:在get_buffer2的回调里,flag是AV_GET_BUFFER_FLAG_REF(1),我们申请的内存会被其他frame复用,不能直接开释,该当通过绑定的free接口来开释,我这里申请的是连续内存,回调free的接口每个平面都会调用,只有第一个平面也便是data[0]的时候,才能delete,方法如下:

void DemuxStream::CustomFrameFreeBuffer(void opaque, uint8_t data){ //申请的空间是连续的,只有第一个data,也便是data[0]的时候再删除,否则会崩溃; int i = (int)opaque; if (i == 0) delete data;} int DemuxStream::CustomFrameAllocBuffer(struct AVCodecContext s, AVFrame frame, int flags){ int32_t size = av_image_get_buffer_size((AVPixelFormat)frame->format, frame->width, frame->height, 1); if (size <= 0) return -1; // 这是由自己申请的内存,avcodec_receive_frame利用完后要自己开释; uint8_t buffer = new uint8_t[size]{0}; uint8_t dst_data[AV_NUM_DATA_POINTERS]{}; int dst_linesize[AV_NUM_DATA_POINTERS]{}; int32_t ret = av_image_fill_arrays(dst_data, dst_linesize, buffer, (AVPixelFormat)frame->format, frame->width, frame->height, 1); if (ret < 0) { delete[] buffer; return ret; } for (int i = 0; i < AV_NUM_DATA_POINTERS; i++) { frame->linesize[i] = dst_linesize[i]; frame->data[i] = dst_data[i]; frame->buf[i] = av_buffer_create(frame->data[i], frame->linesize[i] frame->height, DemuxStream::CustomFrameFreeBuffer, (void )i, 0); } return 0;} // 解码时伪代码;avcodec_send_packet(ctx, pkt);while (true) { // 每解出来一帧,丢到行列步队中; iRet = avcodec_receive_frame(ctx, frame); if (iRet != 0) { if (iRet == AVERROR(EAGAIN)) { return 0; } else return iRet; } push_render_buffer(frame); //利用完成后,调用av_frame_unref,内存回收会在CustomFrameFreeBuffer实行; //由于自行申请的buffer,交给ffmpeg后,有可能会被复用,不能直接删除buffer;}

C++音视频开拓学习资料:点击领取→音视频开拓(资料文档+视频教程+口试题)(FFmpeg+WebRTC+RTMP+RTSP+HLS+RTP)

二、硬件解码

硬件解码须要编译的ffmpeg库支持,详细编译方法就不再此赘述,本次用到的硬件解码为英伟达cuda和英特尔的qsv,硬解解码器的创建跟软解有所不同,利用的过程也只是存在一点差异

1、英伟达,cuda

AVPixelFormat DemuxStream::GetHwFormat(AVCodecContext ctx, const AVPixelFormat pix_fmts){ const enum AVPixelFormat p; DemuxStream pThis = (DemuxStream )ctx->opaque; for (p = pix_fmts; p != -1; p++) { if (p == pThis->m_AVStreamInfo.hw_pix_fmt) { return p; } } fprintf(stderr, "Failed to get HW surface format.\n"); return AV_PIX_FMT_NONE;}AVCodecContext DemuxStream::GetCudaDecoder(AVStream stream){ int32_t ret = avformat_open_input(&m_pAVFormatIC, pPath, NULL, NULL); if (ret < 0) return nullptr; if (avformat_find_stream_info(m_pAVFormatIC, NULL) < 0) return nullptr; ret = av_find_best_stream(m_pAVFormatIC, AVMEDIA_TYPE_VIDEO, -1, -1, &find_codec, 0); if (ret < 0) return nullptr; for (int i = 0;; i++) { const AVCodecHWConfig config = avcodec_get_hw_config(find_codec, i); if (!config) { // 没找到cuda解码器,不能利用; return nullptr; } if (config->device_type == AV_HWDEVICE_TYPE_CUDA) { // 找到了cuda解码器,记录对应的AVPixelFormat,后面get_format须要利用; m_AVStreamInfo.hw_pix_fmt = config->pix_fmt; m_AVStreamInfo.device_type = AV_HWDEVICE_TYPE_CUDA; break; } } AVCodecContext decoder_ctx = avcodec_alloc_context3(find_codec); avcodec_parameters_to_context(decoder_ctx, m_pAVFormatIC->stream[video_index]->codecpar); decoder_ctx->opaque = this; decoder_ctx->get_format = DemuxStream::GetHwFormat; if (m_AVStreamInfo.hw_device_ctx) { av_buffer_unref(&m_AVStreamInfo.hw_device_ctx); m_AVStreamInfo.hw_device_ctx = NULL; } if (av_hwdevice_ctx_create(&m_AVStreamInfo.hw_device_ctx, m_AVStreamInfo.device_type, NULL, NULL, 0) < 0) { fprintf(stderr, "Failed to create specified HW device.\n"); // 创建硬解设备context失落败,不能利用; return nullptr; } decoder_ctx->hw_device_ctx = av_buffer_ref(m_AVStreamInfo.hw_device_ctx); avcodec_open2(decoder_ctx, find_codec, NULL);}

avcodec_get_hw_config,从当前硬解的配置中,遍历探求适用于当前AvCodec的cuda配置,找到后记录cuda解码后的AVPixelFormat,后面解码器初始化时会利用到这个pix_fmt;

设置get_format的回调接口,在回调接口里面,会遍历cuda解码器支持的输出格式,匹配记录的pix_fmt才可以利用,在get_format如果没有找到匹配的硬解设置,可以返回指定类型的软解输出格式,解码器会自动切换到软解进行解码

初始化cuda解码器之前,先创建硬解设备context,赋值AVCodecContext->hw_device_ctx为其引用

至此cuda解码器创建完成,接下来是解码获取视频帧,硬解得到的视频帧后续的处理办法与软解是一样的,硬解比软解多了一步av_hwframe_transfer_data,在avcodec_receive_frame实行完成后,得到的frame中的数据时GPU的数据,是不能直接拿出来用的,须要通过av_hwframe_transfer_data转到内存数据,转换完成后记得把frame的属性通过av_frame_copy_props设置给hw_frame,这里的硬解获取视频帧同样适用于qsv硬解;

iRet = avcodec_send_packet(ctx, pkt);if (iRet != 0 && iRet != AVERROR(EAGAIN)) { get_ffmepg_err_str(iRet); if (iRet == AVERROR_EOF) iRet = 0; return iRet;}while (true) { // 每解出来一帧,丢到行列步队中; iRet = avcodec_receive_frame(ctx, frame); if (iRet != 0) { if (iRet == AVERROR(EAGAIN)) { return 0; } else return iRet; } if (m_pStreamInfo->hw_pix_fmt != AV_PIX_FMT_NONE && m_pStreamInfo->device_type != AV_HWDEVICE_TYPE_NONE) { AVFrame hw_frame = av_frame_alloc(); iRet = av_hwframe_transfer_data(hw_frame, frame, 0); if (iRet < 0) { av_frame_unref(hw_frame); return iRet; } av_frame_copy_props(hw_frame, frame); // hw_frame中的data,便是硬解完成后的视频帧数据; }}

2、英特尔硬解,qsv

qsv的解码器创建的办法与cuda不同,解码获取视频帧内容与cuda完备同等,解码的部分看上面,qsv的解码器创建和cuda的差异在于,初始化AVCodecContext->hw_frames_ctx,cuda是在avcodec_open2之前创建,qsv是在get_format的回调里创建,并且qsv须要做一些额外的设置;

其余在查找codec_id的时候,须要加上“_qsv”的后缀,这个所支持的qsv的编码格式,可以通过ffmpeg.exe查看到

C++音视频开拓学习资料:点击领取→音视频开拓(资料文档+视频教程+口试题)(FFmpeg+WebRTC+RTMP+RTSP+HLS+RTP)

其他部分与cuda的创建基本同等

AVPixelFormat DemuxStream::GetHwFormat(AVCodecContext ctx, const AVPixelFormat pix_fmts){ const enum AVPixelFormat p; DemuxStream pThis = (DemuxStream )ctx->opaque; for (p = pix_fmts; p != -1; p++) { if (p == pThis->m_AVStreamInfo.hw_pix_fmt && p == AV_PIX_FMT_QSV) { if (pThis->HwQsvDecoderInit(ctx) < 0) return AV_PIX_FMT_NONE; return p; } } fprintf(stderr, "Failed to get HW surface format.\n"); return AV_PIX_FMT_NONE;} int DemuxStream::HwQsvDecoderInit(AVCodecContext ctx){ DemuxStream pThis = (DemuxStream )ctx->opaque; AVHWFramesContext frames_ctx; AVQSVFramesContext frames_hwctx; / create a pool of surfaces to be used by the decoder / ctx->hw_frames_ctx = av_hwframe_ctx_alloc(pThis->m_qsv_device_ref); if (!ctx->hw_frames_ctx) return -1; frames_ctx = (AVHWFramesContext)ctx->hw_frames_ctx->data; frames_hwctx = (AVQSVFramesContext )frames_ctx->hwctx; frames_ctx->format = AV_PIX_FMT_QSV; frames_ctx->sw_format = ctx->sw_pix_fmt; frames_ctx->width = FFALIGN(ctx->coded_width, 32); frames_ctx->height = FFALIGN(ctx->coded_height, 32); frames_ctx->initial_pool_size = 16; frames_hwctx->frame_type = MFX_MEMTYPE_VIDEO_MEMORY_DECODER_TARGET; return av_hwframe_ctx_init(ctx->hw_frames_ctx);} AVCodecContext DemuxStream::GetQsvDecoder(AVStream stream){ AVCodecContext decoder_ctx = nullptr; int ret = av_hwdevice_ctx_create(&m_qsv_device_ref, AV_HWDEVICE_TYPE_QSV, "auto", NULL, 0); if (ret < 0) { goto failed; } AVCodec find_decoder = avcodec_find_decoder(stream->codec->codec_id); if (!find_decoder) { goto failed; } find_decoder = avcodec_find_decoder_by_name((std::string(find_decoder->name) + "_qsv").c_str()); if (!find_decoder) { goto failed; } for (int i = 0;; i++) { const AVCodecHWConfig config = avcodec_get_hw_config(find_decoder, i); if (!config) { fprintf(stderr, "Decoder %s does not support device type %s.\n", find_decoder->name, av_hwdevice_get_type_name(AV_HWDEVICE_TYPE_QSV)); goto failed; } if (config->device_type == AV_HWDEVICE_TYPE_QSV) { m_AVStreamInfo.hw_pix_fmt = config->pix_fmt; m_AVStreamInfo.device_type = AV_HWDEVICE_TYPE_QSV; break; } } if (m_AVStreamInfo.device_type == AV_HWDEVICE_TYPE_NONE || m_AVStreamInfo.hw_pix_fmt == AV_PIX_FMT_NONE) goto failed; decoder_ctx = avcodec_alloc_context3(find_decoder); if (!decoder_ctx) goto failed; if (avcodec_parameters_to_context(decoder_ctx, stream->codecpar) < 0) goto failed; decoder_ctx->opaque = this; decoder_ctx->get_format = DemuxStream::GetHwFormat; ret = avcodec_open2(decoder_ctx, NULL, NULL); if (ret < 0) goto failed; return decoder_ctx;failed: m_AVStreamInfo.hw_pix_fmt = AV_PIX_FMT_NONE; m_AVStreamInfo.device_type = AV_HWDEVICE_TYPE_NONE; av_buffer_unref(&m_qsv_device_ref); m_qsv_device_ref = nullptr; avcodec_free_context(&decoder_ctx); return nullptr;}

标签:

相关文章

网站SEO优化方法让您的网站脱颖而出

网站已成为企业展示形象、拓展业务的重要平台。在竞争激烈的市场环境中,如何让您的网站脱颖而出,吸引更多潜在客户,成为了企业关注的焦点...

PHP教程 2025-04-09 阅读1 评论0

绥化抖音SEO企业助力企业腾飞的新引擎

抖音这一短视频平台迅速崛起,成为了众多企业和个人展示自我、拓展业务的重要平台。在绥化这片充满活力的土地上,抖音SEO企业应运而生,...

PHP教程 2025-04-09 阅读1 评论0

网站改版对SEO的影响与应对步骤

网站改版已经成为企业提升品牌形象、优化用户体验、增强竞争力的重要手段。网站改版对SEO(搜索引擎优化)的影响不容忽视。本文将分析网...

PHP教程 2025-04-09 阅读1 评论0

职场SEO专员如何打造高效关键词布局步骤

搜索引擎优化(SEO)已成为企业提升品牌知名度、扩大市场份额的重要手段。SEO专员作为企业网络营销的核心力量,其工作内容之一便是关...

PHP教程 2025-04-09 阅读1 评论0