FFNvDecoder.cpp 6.23 KB
#include "FFNvDecoder.h"
#include<iostream>

#include <chrono>
#include <thread>
#include <fstream>

using namespace std;

// 参考博客: https://blog.csdn.net/qq_40116098/article/details/120704340

static AVPixelFormat get_hw_format(AVCodecContext *avctx, const AVPixelFormat *pix_fmts)
{
	FFNvDecoder* _this = (FFNvDecoder*)avctx->opaque;

	const AVPixelFormat *p;

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

	//cout << "Failed to get HW surface format";
	return AV_PIX_FMT_NONE;
}

FFNvDecoder::FFNvDecoder()
{
	// 初始化解码对象
	fmt_ctx = nullptr;
	avctx = nullptr;
	m_bRunning = false;

	stream = nullptr;
    stream_index = -1;
    hw_pix_fmt = AV_PIX_FMT_NONE;
    name = "";

	m_bPause = false;
	m_bReal = true;
}

FFNvDecoder::~FFNvDecoder()
{
	
}

bool FFNvDecoder::init(const string& path)
{
	fstream infile(path);
	if (infile.is_open()){
		m_bReal = false;
		infile.close();
	}else {
		m_bReal = true;
	}

	// 查找对应的硬件解码设备
	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) {
		while ((hw_device_type = av_hwdevice_iterate_types(hw_device_type)) != AV_HWDEVICE_TYPE_NONE)
			cout << av_hwdevice_get_type_name(hw_device_type);
		return false;
	}

	// 打开输入视频文件
	AVDictionary *options = nullptr;
	av_dict_set( &options, "bufsize", "1024000", 0 );
	av_dict_set( &options, "rtsp_transport", "tcp", 0 );
	av_dict_set( &options, "listen_timeout", "30", 0 ); // 单位为s
	fmt_ctx = avformat_alloc_context();
	const char* input_file = path.c_str();
	if (avformat_open_input(&fmt_ctx, input_file, nullptr, &options) != 0) {
		cout << "Cannot open input file" << input_file;
		return false;
	}

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

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

	// 判断硬件解码设备是否兼容视频解码器
	for (int i = 0;; i++) {
		const AVCodecHWConfig *config = avcodec_get_hw_config(decoder, i);
		if (!config) {
			cout << "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 (!(avctx = avcodec_alloc_context3(decoder)))
		return (bool)AVERROR(ENOMEM);

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

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

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

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

	return true;
}

void FFNvDecoder::start(){

	m_bRunning = true;

	pthread_create(&m_decode_thread,0,
        [](void* arg)
        {
            FFNvDecoder* a=(FFNvDecoder*)arg;
            a->decode_thread();
            return (void*)0;
        }
    ,this);

	pthread_create(&m_post_decode_thread,0,
        [](void* arg)
        {
            FFNvDecoder* a=(FFNvDecoder*)arg;
            a->post_decode_thread();
            return (void*)0;
        }
    ,this);
}

void FFNvDecoder::decode_thread()
{
	AVPacket* pkt ;
	pkt = av_packet_alloc();
	av_init_packet( pkt );

	while (m_bRunning)
	{
		if (!m_bReal)
		{
			if (m_bPause)
			{
				std::this_thread::sleep_for(std::chrono::milliseconds(3));
				continue;
			}
		}
		
		int result = av_read_frame(fmt_ctx, pkt);
		if (result == AVERROR_EOF)
		{
			cout << "Failed to read frame!" << endl;
			break;
		}
		if (result < 0)
		{
			cout << "Failed to read frame!" << endl;
			break;
		}

		if (m_bReal)
		{
			if (m_bPause)
			{
				av_packet_unref(pkt);
				std::this_thread::sleep_for(std::chrono::milliseconds(3));
				continue;
			}
		}

		if (stream_index == pkt->stream_index)
		{
			result = avcodec_send_packet(avctx, pkt);
			if (result < 0)
			{
				cout << "Failed to send pkt" << result << endl;
				continue;
			}

			AVFrame * gpuFrame = mFrameQueue.getTail();
			if (gpuFrame == nullptr)
			{
				continue;
			}

			result = avcodec_receive_frame(avctx, gpuFrame);
			if (result == AVERROR(EAGAIN) || result == AVERROR_EOF || result < 0)
			{
				cout << "Failed to receive frame"<< endl;
				continue;
			}

			mFrameQueue.addTail();
		}
		av_packet_unref(pkt);
	}

	m_bRunning = false;
	cout << "decode thread exited." << endl;
}

void FFNvDecoder::post_decode_thread()
{
	 while (m_bRunning)
	 {
		AVFrame * gpuFrame = mFrameQueue.getHead();
		if (gpuFrame == nullptr)
		{
			std::this_thread::sleep_for(std::chrono::milliseconds(3));
			continue;
		}

		post_decoded_cbk(m_userPtr, gpuFrame);

		mFrameQueue.addHead();
	 }
	 
	 cout << "post decode thread exited." << endl;
}

void FFNvDecoder::close()
{
	m_bRunning=false;
    pthread_join(m_decode_thread,0);
	pthread_join(m_post_decode_thread,0);

	if (avctx)
	{
		if (avctx->hw_device_ctx)
		{
			av_buffer_unref(&avctx->hw_device_ctx);
		}
		avcodec_free_context(&avctx);
	}
	
	if (fmt_ctx)
	{
		avformat_close_input(&fmt_ctx);
	}
}

void FFNvDecoder::setName(string nm){
	name = nm;
}

string FFNvDecoder::getName(){
	return name;
}

AVPixelFormat FFNvDecoder::getHwPixFmt()
{
	return hw_pix_fmt;
}

bool FFNvDecoder::isRunning()
{
	return m_bRunning;
}

bool FFNvDecoder::getResolution( int &width, int &height )
{
	if (avctx != nullptr)
	{
		width = avctx->width;
		height = avctx->height;
		return true;
	}
	
	return false;
}

void FFNvDecoder::pause()
{
	m_bPause = true;
}

void FFNvDecoder::resume()
{
	m_bPause = false;
}