ffmpeg使用scale_npp进行AV_PIX_FMT_CUDA到AV_PIX_FMT_YUV420P的转换

本文介绍了如何使用ffmpeg的scale_npp滤镜进行AV_PIX_FMT_CUDA到AV_PIX_FMT_YUV420P的视频帧转换。通过对比使用CPU的av_hwframe_transfer_data和sws_scale,强调了GPU转换以提高转码速度的重要性。虽然使用scale_npp在GPU上进行转换的fps略有提升,但效率提升不显著,可能存在的代码优化问题有待进一步研究。
摘要由CSDN通过智能技术生成

一、关于filter的基础命令

《FFmpeg filter的使用介绍》

1. 列出所有filter

ffmpeg -filters

2. 查看某个filter的详细信息

ffmpeg -h filter=scale_npp

3. 实现画面切分为两半并将上半边翻转(本条命令比较全面的展示了filter的使用语法)

ffmpeg -i INPUT -vf "split [main][tmp]; [tmp] crop=iw:ih/2:0:0, vflip [flip]; [main][flip] overlay=0:H/2" OUTPUT
  • 用到了四个filter: split,crop,vflip,overlay
  • split的输入是解码后的数据,crop的输入是tmp, vflip的输出是flip,overlay的输入是main和flip
  • =(等号)后边为本filter的参数,每个参数用分号分隔,顺序需要按照说明文档中说的参数顺序,例如overlay的参数x=0, y=H/2,也可以写为 [main][flip] overlay=x=0:y=H/2
  • 是对输出取的别名,用于后续的filter使用

4. 抽取视频Y、U、V分量到不同的文件

ffmpeg -i jack.mp4 -filter_complex "extractplanes=y+u+v[y][u][v]" -map "[y]" jack_y.mp4 -map "[u]" jack_u.mp4 -map "[v]" jack_v.mp4    

5. 使用scale_npp进行转码

ffmpeg -vsync 0 -hwaccel_device 0 -hwaccel cuda -hwaccel_output_format cuda -i ~/vedio/drone1.flv -vf "scale_npp=format=yuv420p,hwdownload,format=yuv420p" ff22cuda2.yuv

二、AV_PIX_FMT_CUDA到AV_PIX_FMT_BGR24的转码

1. 背景

当使用cuda进行h264解码硬件加速时,通过avcodec_receive_frame函数得到的解码过后的视频帧格式为AV_PIX_FMT_CUDA,需要调用av_hwframe_transfer_data函数将显存中的CUDA格式的帧转存到内存中,并且转存的结果为NV12格式,如果自己需要的帧格式为BGR24,则需要调用sws_scale进行转码,可惜的是通过sws_scale进行NV12到BGR24的效率非常低。
因上,想要通过scale_npp在GPU中直接进行CUDA->YUV420P, 再使用CPU进行YUV420P-> BGR24转换,以加快转码的速度。

2. 使用cpu

av_hwframe_transfer_data从显存中取数据+sws_scale转码,每帧耗时13ms,fps=50

3. 使用GPU(filter = scale_npp)

代码(不确定是否完全正确)

 /**
  * @file   cudaDecoder.cpp
  * @brief  再cuda进行硬解码的基础, 将转码部分也改为cuda加速 使用filter scale_npp
  * @date   2022/09/29
  * @note   matters needing attention
  * @version 1.0
  */

#include <stdio.h>
#include <stdlib.h>
extern "C"
{
   
#include "libavcodec/avcodec.h"
#include "libavfilter/avfilter.h"
#include "libavformat/avformat.h"
#include "libavutil/avutil.h"
#include "libavutil/ffversion.h"
#include "libswresample/swresample.h"
#include "libswscale/swscale.h"
#include "libpostproc/postprocess.h"
#include "libavutil/imgutils.h"
#include "libavutil/hwcontext.h"
#include "libavutil/pixdesc.h"
#include "libavutil/imgutils.h"
#include "libavutil/opt.h"
#include "libavfilter/buffersrc.h"
#include "libavfilter/buffersink.h"
}

#include <chrono>
#include <iostream>
using namespace std::chrono;


AVFilterGraph* filter_graph;
AVFilterContext* buffersink_ctx;
AVFilterContext* buffersrc_ctx;

static enum AVPixelFormat hw_pix_fmt;

static AVBufferRef* hw_device_ctx = NULL;

AVPixelFormat get_hw_format(AVCodecContext* ctx, const AVPixelFormat* pix_fmts)
{
   
	const enum AVPixelFormat* p;

	for (p = pix_fmts; *p != -1; p++) {
   
		if (*p == hw_pix_fmt)
			return *p;
	}

	fprintf(stderr, "Failed to get HW surface format.\n");
	return AV_PIX_FMT_NONE;
}

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


//将FFmpeg解码后的数据保存到本地文件
void saveFrame(AVFrame* pFrame, int width, int height, int iFrame)
{
   
	FILE* pFile;
	char szFilename[256];
	int y;

	// 打开文件
	//sprintf(szFilename, "/home/chao/testffmpeg/frame%d.ppm", iFrame);
	sprintf(szFilename, "/home/sports/Downloads/CL/media/frame%d.ppm", iFrame);//Micro_start
	pFile = fopen(szFilename, "wb");
	if (pFile == NULL)
		return;

	// 写入文件头
	fprintf(pFile, "P6\n%d %d\n255\n", width, height);

	// 写入像素数据
	for (y = 0; y < height; y++)
		fwrite(pFrame->data[0] + y * pFrame->linesize[0], 1, width * 3, pFile);
	//fwrite(pFrame->data[0] + y * 11520, 1, width * 3, pFile);

// 关闭文件
	fclose
以下是一个简单的示例代码,可以将 OpenCV 的 `cv::Mat` 对象转换FFmpeg 的 `AVFrame`,并将其编码为 YUV 420P 格式。 ```c++ #include <opencv2/opencv.hpp> #include <libavformat/avformat.h> #include <libavcodec/avcodec.h> #include <libswscale/swscale.h> int main() { // 初始化 FFmpeg av_register_all(); // 创建格式上下文 AVFormatContext* format_ctx = avformat_alloc_context(); if (!format_ctx) { std::cerr << "Failed to allocate format context" << std::endl; return -1; } // 设置输出格式 AVOutputFormat* output_fmt = av_guess_format("mp4", nullptr, nullptr); if (!output_fmt) { std::cerr << "Failed to guess output format" << std::endl; return -1; } format_ctx->oformat = output_fmt; // 打开输出文件 AVIOContext* io_ctx = nullptr; if (avio_open(&io_ctx, "output.mp4", AVIO_FLAG_WRITE) < 0) { std::cerr << "Failed to open output file" << std::endl; return -1; } format_ctx->pb = io_ctx; // 创建视频流 AVStream* video_stream = avformat_new_stream(format_ctx, nullptr); if (!video_stream) { std::cerr << "Failed to create video stream" << std::endl; return -1; } // 设置编码器参数 AVCodecParameters* codec_params = video_stream->codecpar; codec_params->codec_type = AVMEDIA_TYPE_VIDEO; codec_params->codec_id = output_fmt->video_codec; codec_params->width = 640; codec_params->height = 480; codec_params->format = AV_PIX_FMT_YUV420P; // 查找编码器 AVCodec* codec = avcodec_find_encoder(output_fmt->video_codec); if (!codec) { std::cerr << "Failed to find encoder" << std::endl; return -1; } // 创建编码器上下文 AVCodecContext* codec_ctx = avcodec_alloc_context3(codec); if (!codec_ctx) { std::cerr << "Failed to allocate codec context" << std::endl; return -1; } codec_ctx->codec_type = AVMEDIA_TYPE_VIDEO; codec_ctx->width = codec_params->width; codec_ctx->height = codec_params->height; codec_ctx->pix_fmt = codec_params->format; codec_ctx->time_base = {1, 25}; // 打开编码器 if (avcodec_open2(codec_ctx, codec, nullptr) < 0) { std::cerr << "Failed to open codec" << std::endl; return -1; } // 创建帧 AVFrame* frame = av_frame_alloc(); if (!frame) { std::cerr << "Failed to allocate frame" << std::endl; return -1; } frame->format = codec_ctx->pix_fmt; frame->width = codec_ctx->width; frame->height = codec_ctx->height; // 分配帧数据空间 if (av_frame_get_buffer(frame, 0) < 0) { std::cerr << "Failed to allocate frame data" << std::endl; return -1; } // 创建格式转换器 SwsContext* sws_ctx = sws_getContext(codec_ctx->width, codec_ctx->height, AV_PIX_FMT_BGR24, codec_ctx->width, codec_ctx->height, codec_ctx->pix_fmt, SWS_BICUBIC, nullptr, nullptr, nullptr); if (!sws_ctx) { std::cerr << "Failed to create format converter" << std::endl; return -1; } // 读取输入帧 cv::Mat input_frame = cv::imread("input.jpg"); if (input_frame.empty()) { std::cerr << "Failed to read input frame" << std::endl; return -1; } // 转换输入帧 uint8_t* input_data[AV_NUM_DATA_POINTERS] = {0}; input_data[0] = input_frame.data; int input_linesize[AV_NUM_DATA_POINTERS] = {0}; input_linesize[0] = input_frame.step; sws_scale(sws_ctx, input_data, input_linesize, 0, codec_ctx->height, frame->data, frame->linesize); // 编码帧 AVPacket pkt; av_init_packet(&pkt); pkt.data = nullptr; pkt.size = 0; int got_packet = 0; if (avcodec_encode_video2(codec_ctx, &pkt, frame, &got_packet) < 0) { std::cerr << "Failed to encode frame" << std::endl; return -1; } // 写入输出文件 if (got_packet) { av_packet_rescale_ts(&pkt, codec_ctx->time_base, video_stream->time_base); pkt.stream_index = video_stream->index; if (av_interleaved_write_frame(format_ctx, &pkt) < 0) { std::cerr << "Failed to write packet" << std::endl; return -1; } av_packet_unref(&pkt); } // 写入文件尾 av_write_trailer(format_ctx); // 释放资源 avcodec_free_context(&codec_ctx); av_frame_free(&frame); avio_closep(&format_ctx->pb); avformat_free_context(format_ctx); sws_freeContext(sws_ctx); return 0; } ``` 需要注意的是,上述代码中的 `AV_PIX_FMT_BGR24` 表示输入图像的像素格式,如果您的输入图像格式不是 BGR24,需要相应地修改代码。另外,上述代码中的像素格式硬编码为 YUV420P,如果您需要使用其他像素格式,也需要相应地修改代码。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值