ffmpeg解码

本文分别介绍软解和硬解两种方式,同时将获取的每一帧,转换成二进制或者图片格式
本文只是介绍如何解码,

硬解码,这个类中将每一帧转换成二进制格式,然后存放到队列中,注意硬解码需要安装cuda,百度一下你就知道,

#include "hw_video_decoder.h"
#include <cstdio>
#include <QDebug>
#include <QDateTime>

AVPixelFormat HWVideoDecoder::hw_pix_fmt;

bool HWVideoDecoder::init(SharedQueue<VideoFrame>* frameQueue, const QString& path)
{
	this->frameQueue = frameQueue;
	bRunning = true;

	// 初始化解码对象
	hw_device_ctx = nullptr;
	input_ctx = nullptr;
	decoder_ctx = nullptr;
	decoder = nullptr;

	// 查找对应的硬件解码设备
	const char* device_name = "cuda";
	AVHWDeviceType hw_device_type = av_hwdevice_find_type_by_name(device_name);
	if (hw_device_type == AV_HWDEVICE_TYPE_NONE) {
		qDebug() << "Device name %s is not supported" << device_name;
		qDebug() << "Available device types:";
		while ((hw_device_type = av_hwdevice_iterate_types(hw_device_type)) != AV_HWDEVICE_TYPE_NONE)
			qDebug() << av_hwdevice_get_type_name(hw_device_type);
		return false;
	}

	// 打开输入视频文件
	AVDictionary *options = nullptr;
	av_dict_set(&options, "rtsp_transport", "tcp", 0);
	input_ctx = avformat_alloc_context();
	const char* input_file = path.toStdString().c_str();
	if (avformat_open_input(&input_ctx, input_file, nullptr, &options) != 0) {
		qDebug() << "Cannot open input file" << input_file;
		return false;
	}

	// 查找流信息
	if (avformat_find_stream_info(input_ctx, nullptr) < 0) {
		qDebug() << "Cannot find input stream information";
		return false;
	}

	// 查找视频流信息
	result = av_find_best_stream(input_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, &decoder, 0);
	if (result < 0) {
		qDebug() << "Cannot find a video stream in the input file";
		return false;
	}
	stream_index = result;

	// 判断硬件解码设备是否兼容视频解码器
	for (int i = 0;; i++) {
		const AVCodecHWConfig *config = avcodec_get_hw_config(decoder, i);
		if (!config) {
			qDebug() << "Decoder does not support device hw_device_type"
				<< decoder->name << av_hwdevice_get_type_name(hw_device_type);
			return false;
		}
		// 得到硬件解码像素格式
		if (config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX &&
			config->device_type == hw_device_type) {
			hw_pix_fmt = config->pix_fmt;
			break;
		}
	}

	// 得到解码器管理器
	if (!(decoder_ctx = avcodec_alloc_context3(decoder)))
		return (bool)AVERROR(ENOMEM);

	// 得到视频流对象
	stream = input_ctx->streams[stream_index];
	if (avcodec_parameters_to_context(decoder_ctx, stream->codecpar) < 0)
		return false;

	// 设置解码器管理器的像素格式回调函数
	decoder_ctx->get_format = get_hw_format;

	// 初始化硬件解码器
	if ((result = av_hwdevice_ctx_create(&hw_device_ctx, hw_device_type, nullptr, nullptr, 0)) < 0) {
		qDebug() << "Failed to create specified HW device.";
		return false;
	}
	decoder_ctx->hw_device_ctx = av_buffer_ref(hw_device_ctx);

	// 打开解码器流
	if ((result = avcodec_open2(decoder_ctx, decoder, nullptr)) < 0) {
		qDebug() << "Failed to open codec for stream" << stream_index;
		return false;
	}

	// 初始化帧缓存
	ping_frame = av_frame_alloc();
	pong_frame = av_frame_alloc();
	yuv_frame = av_frame_alloc();
	frame = nullptr;

	// 初始化帧转换数据
	input_fmt = AV_PIX_FMT_NV12;
	output_fmt = AV_PIX_FMT_BGR32;
	size = av_image_get_buffer_size(output_fmt, decoder_ctx->width, decoder_ctx->height, 1);
	buffer = (uint8_t *)av_malloc(size);
	av_image_fill_arrays(yuv_frame->data, yuv_frame->linesize, buffer, output_fmt, decoder_ctx->width, decoder_ctx->height, 1);
	swsContext = sws_getContext(
		decoder_ctx->width, decoder_ctx->height, input_fmt,
		decoder_ctx->width, decoder_ctx->height, output_fmt,
		SWS_BICUBIC, NULL, NULL, NULL);

	return true;
}

void HWVideoDecoder::stop()
{
	bRunning = false;
	wait();
	destroy();
}

bool HWVideoDecoder::destroy()
{
	packet.data = nullptr;
	packet.size = 0;
	av_packet_unref(&packet);
	av_freep(&buffer);
	av_frame_free(&ping_frame);
	av_frame_free(&pong_frame);
	avcodec_free_context(&decoder_ctx);
	avformat_close_input(&input_ctx);
	av_buffer_unref(&hw_device_ctx);

	return true;
}

void HWVideoDecoder::run()
{
	static const int kWaitTime = 50;
	static const int kMaxQueueSize = 16;

	std::chrono::milliseconds duration(kWaitTime);
	while (bRunning)
	{
		if (frameQueue->size() > kMaxQueueSize)
		{
			std::this_thread::sleep_for(duration);
			continue;
		}

		result = av_read_frame(input_ctx, &packet);
		if (result == AVERROR_EOF)
		{
			avio_seek(input_ctx->pb, 0, SEEK_SET);
			avformat_seek_file(input_ctx, stream_index, 0, 0, stream->duration, 0);
			continue;
		}
		if (result < 0)
		{
			qDebug() << "Failed to read frame!";
			return;
		}

		if (stream_index == packet.stream_index)
		{
			result = avcodec_send_packet(decoder_ctx, &packet);
			if (result < 0)
			{
				//qDebug() << "Failed to send packet" << result;
				continue;
			}

			result = avcodec_receive_frame(decoder_ctx, ping_frame);
			if (result == AVERROR(EAGAIN) || result == AVERROR_EOF || result < 0)
			{
				qDebug() << "Failed to receive frame";
				return;
			}

			if (ping_frame->format == hw_pix_fmt) 
			{
				/* retrieve data from GPU to CPU */
				if (av_hwframe_transfer_data(pong_frame, ping_frame, 0) < 0) 
				{
					qDebug() << "Failed to transfer the data to system memory";
					return;
				}
				frame = pong_frame;
			}
			else
			{
				frame = ping_frame;
			}

			// 转换成yuv格式
			result = sws_scale(swsContext, (const uint8_t* const*)frame->data, frame->linesize, 0,
				decoder_ctx->height, yuv_frame->data, yuv_frame->linesize);

			// 填充到帧缓存队列中
			VideoFrame frame;
			frame.width = decoder_ctx->width;
			frame.height = decoder_ctx->height;
			frame.data = QByteArray((char *)buffer, size * sizeof(uint8_t));
			frameQueue->push_back(frame);

		}
		//av_packet_unref(&packet);
		av_free_packet(&packet);
	}
}

int HWVideoDecoder::getFrameTimeMs()
{
	return (int)(1000.0 / av_q2d(stream->avg_frame_rate));
}

AVPixelFormat HWVideoDecoder::get_hw_format(AVCodecContext *, const AVPixelFormat *pix_fmts)
{
	const AVPixelFormat *p;

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

	//qDebug() << "Failed to get HW surface format";
	return AV_PIX_FMT_NONE;
}

软解码,将每一帧转换成二进制和图片格式,

#include "video_decoder.h"
#include <QDebug>
#include <QDateTime>
#include <thread>

bool VideoDecoder::init(SharedQueue<VideoFrame>* frameQueue, const QString& path)
{
	this->frameQueue = frameQueue;
	bRunning = true;
	nowPath = path;

	avformat_network_init();
	av_register_all(); //注册所有容器格式

	pFormatCtx = avformat_alloc_context();

	AVDictionary *avdic = NULL;
	char option_key[] = "rtsp_transport";
	char option_value[] = "tcp";

	av_dict_set(&avdic, option_key, option_value, 0);
	char option_key2[] = "max_delay";
	char option_value2[] = "100";

	av_dict_set(&avdic, option_key2, option_value2, 0);

	if (avformat_open_input(&pFormatCtx, path.toStdString().c_str(), NULL, &avdic) != 0)
	{
		qDebug() << "can't open the file:" << path;
		return false;
	}

	if (avformat_find_stream_info(pFormatCtx, NULL) < 0)
	{
		qDebug() << "can't find stream infomation";
		return false;
	}

	videoStreamIndex = -1;

	for (unsigned int i = 0; i < pFormatCtx->nb_streams; i++)
	{
		if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)
		{
			videoStreamIndex = i;
		}
	}

	if (videoStreamIndex == -1)
	{
		qDebug() << "can't find a video stream";
		return false;
	}

	pCodecCtx = pFormatCtx->streams[videoStreamIndex]->codec;
	pCodecCtx->thread_count = 4;
	pCodecCtx->flags |= AV_CODEC_FLAG_LOW_DELAY;
	pCodec = findCodec(pCodecCtx);
	if (pCodec == NULL)
	{
		qDebug() << "can't find a codec";
		return false;
	}

	if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0)
	{
		qDebug() << "can't open a codec";
		return false;
	}

	pFrame = av_frame_alloc();
	pFrameBGR = av_frame_alloc();

	imgConvertCtx = sws_getContext(pCodecCtx->width, pCodecCtx->height,
		pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height,
		AV_PIX_FMT_BGR32, SWS_BICUBIC, NULL, NULL, NULL);

	byteNum = avpicture_get_size(AV_PIX_FMT_BGR32, pCodecCtx->width, pCodecCtx->height);

	outBuffer = (uint8_t *)av_malloc(byteNum * sizeof(uint8_t));
	avpicture_fill((AVPicture *)pFrameBGR, outBuffer, AV_PIX_FMT_BGR32,
		pCodecCtx->width, pCodecCtx->height);

	packet = (AVPacket *)malloc(sizeof(AVPacket));
	av_new_packet(packet, pCodecCtx->width * pCodecCtx->height);

	return true;
}

void VideoDecoder::stop()
{
	bRunning = false;
	wait();
	destroy();
}

void VideoDecoder::run()
{
	static const int kWaitTime = 5;
	static const int kMaxQueueSize = 30;
	
	std::chrono::milliseconds duration(kWaitTime);
	//int frameIndex = 0;
	while (bRunning)
	{
		if (frameQueue->size() > kMaxQueueSize)
		{
			std::this_thread::sleep_for(duration);
			continue;
		}

		int nextFrame = av_read_frame(pFormatCtx, packet);
		if (nextFrame == AVERROR_EOF)
		{
			auto stream = pFormatCtx->streams[videoStreamIndex];
			avio_seek(pFormatCtx->pb, 0, SEEK_SET);
			avformat_seek_file(pFormatCtx, videoStreamIndex, 0, 0, stream->duration, 0);
			continue;
		}

		if (nextFrame < 0)
		{
			qDebug() << "read frame error";
			bRunning = false;
			break;
		}

		if (packet->stream_index == videoStreamIndex)
		{
			int got_picture = 0;
			int ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, packet);

			if (ret < 0)
			{
				qDebug() << "decode error";
				break;
			}

			if (got_picture)
			{
				sws_scale(imgConvertCtx,
					(uint8_t const * const *)pFrame->data,
					pFrame->linesize, 0, pCodecCtx->height, pFrameBGR->data,
					pFrameBGR->linesize);

				VideoFrame frame;
				frame.width = pFrame->width;
				frame.height = pFrame->height;
				frame.data = QByteArray((char *)outBuffer, byteNum * sizeof(uint8_t));
				QImage tmpImg((uchar *)outBuffer, pCodecCtx->width, pCodecCtx->height, QImage::Format_RGB32);
				frame.img = tmpImg.convertToFormat(QImage::Format_RGB888, Qt::NoAlpha);

				lastTime = currentTime;
				frameQueue->push_back(frame);
			}
		}
		av_free_packet(packet);
		std::this_thread::sleep_for(duration);
	}
}

void VideoDecoder::destroy()
{
	av_free(outBuffer);
	av_free(pFrameBGR);
	avcodec_close(pCodecCtx);
	avformat_close_input(&pFormatCtx);
}

int VideoDecoder::getFrameTimeMs()
{
	return (int)(1000.0 / av_q2d(pCodecCtx->framerate));
}

AVCodec* VideoDecoder::findCodec(AVCodecContext *codecCtx)
{
		pCodec = avcodec_find_decoder(codecCtx->codec_id);//软解
	return pCodec;
}

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值