ffmpeg编码视频总结

上一篇文章总结了ffmpeg解码,本文继续总结一下编码。

一:编码流程:

编码流程图

ffmpeg函数:
avformat_alloc_output_context2():初始化输出码流的AVFormatContext。
avio_open():打开输出文件。
av_new_stream():创建输出码流的AVStream。
avcodec_find_encoder():查找编码器。
avcodec_open2():打开编码器。
avformat_write_header():写文件头。
avcodec_send_frame() + avcodec_receive_packet():编码一帧视频。即将AVFrame编码为AVPacket(因为ffmpeg的版本更新原因,雷神用的函数是avcodec_encode_video2(),需要进行替换)。
av_write_frame():将编码后的视频码流写入文件。
flush_encoder():输入的像素数据读取完成后调用此函数。用于输出编码器中剩余的AVPacket。
av_write_trailer():写文件尾。

原文链接:https://blog.csdn.net/leixiaohua1020/article/details/39770947

雷霄骅相关文章: https://blog.csdn.net/leixiaohua1020/article/details/39770947

编码部分的主要代码:
因为软件是基于CS模式开发的,客户端这边收到的是服务器转发过来的包含高空抛物信息的视频流,在解码不分已经将数据转成带有抛物轨迹的qt图片了(类型是QImage),所以在保存时需要首先将QImage转成AVFrame类型。

代码:

//初始化
void SaveImage::initRecordParas()
{
	m_pFormatCtxOut = avformat_alloc_context();
	AVOutputFormat* pOutFormat = av_guess_format(NULL, m_szURL.toStdString().data(), NULL);
	pOutFormat->video_codec = AV_CODEC_ID_H264;
	m_pFormatCtxOut->oformat = pOutFormat;
	avformat_alloc_output_context2(&m_pFormatCtxOut, pOutFormat, NULL, m_szURL.toStdString().data());
	//Open output URL
	if (avio_open(&m_pFormatCtxOut->pb, m_szURL.toStdString().data(), AVIO_FLAG_READ_WRITE) < 0)
	{
		printf("Failed to open output file! \n");
		return;
	}

	AVCodec* pCodec = avcodec_find_encoder(pOutFormat->video_codec);
	if (!pCodec)
	{
		printf("Can not find encoder! \n");
		return;
	}
	m_pVideoStream = avformat_new_stream(m_pFormatCtxOut, pCodec);

	m_pCodecCtxOut = avcodec_alloc_context3(pCodec);
	m_pCodecCtxOut->codec_id = pOutFormat->video_codec;
	m_pCodecCtxOut->pix_fmt = AV_PIX_FMT_YUV420P;
	m_pCodecCtxOut->width = 1920;//m_nWidth;//1920
	m_pCodecCtxOut->height = 1080;// m_nHeight;//1080
	m_pCodecCtxOut->bit_rate = 1024 * 12000;// 1024 * 8 * 1100;//1100000;  //note: it's bit rate, not byte rate
	m_pCodecCtxOut->gop_size = 12;
	m_pCodecCtxOut->time_base.num = 1;
	m_pCodecCtxOut->time_base.den = 25;//5
	m_pCodecCtxOut->qmin = 0;
	m_pCodecCtxOut->qmax = 51;
	m_pCodecCtxOut->max_b_frames = 1;//3;

	AVDictionary* param = 0;
	//H.264
	if (m_pCodecCtxOut->codec_id == AV_CODEC_ID_H264)
	{
		av_dict_set(&param, "preset", "superfast", 0);//superfast
		av_dict_set(&param, "tune", "zerolatency", 0);
	}

	if (avcodec_open2(m_pCodecCtxOut, pCodec, &param) < 0)
	{
		printf("Failed to open encoder! \n");
		return;
	}

	avcodec_parameters_from_context(m_pVideoStream->codecpar, m_pCodecCtxOut);

	m_avFrameOutput = av_frame_alloc();
	int picture_size = av_image_get_buffer_size(m_pCodecCtxOut->pix_fmt, m_pCodecCtxOut->width, m_pCodecCtxOut->height, 1);
	uint8_t* picture_buf = (uint8_t*)av_malloc(picture_size);
	av_image_fill_arrays(m_avFrameOutput->data, m_avFrameOutput->linesize, picture_buf, m_pCodecCtxOut->pix_fmt, m_pCodecCtxOut->width, m_pCodecCtxOut->height, 1);

	m_pPacketOut = (AVPacket*)malloc(sizeof(AVPacket));
	av_new_packet(m_pPacketOut, picture_size);

	int nBytes = av_image_get_buffer_size(AV_PIX_FMT_YUV420P, m_nWidth, m_nHeight, 1);
	m_pYUVBuffer = (uint8_t*)av_malloc(nBytes);
}

//存储开始、结束控制
void SaveImage::startFlag()
{
	m_pPacketOut = NULL;
	m_avFrameOutput = NULL;
	m_pVideoStream = NULL;

	QString szOutUrl = "C:\\system\\recordVideo\\";
	QDateTime dateTime = QDateTime::currentDateTime();
	QString szTime = dateTime.toString("yyyy-MM-dd_HH_mm_ss");
	m_szURL = szOutUrl + szTime + ".mp4";

	initRecordParas();

	m_bStartRecord = true;
	m_bStopRecord = false;
	writeHead();
}

void SaveImage::stopFlag()
{
	m_bStartRecord = false;
	m_bStopRecord = true;
}

//收到解码后的QImage并转成AVFrame
void SaveImage::recvImage(const QImage& image)
{
	if (m_bStartRecord)
	{
		int nWidth = image.width();
		int nHeight = image.height();

		AVFrame* pFrameRGB = av_frame_alloc();
		int numBytes1 = av_image_get_buffer_size(AV_PIX_FMT_RGB32, nWidth, nHeight, 1);
		uint8_t* buffer1 = (uint8_t*)av_malloc(numBytes1);
		av_image_fill_arrays(pFrameRGB->data, pFrameRGB->linesize, buffer1, AV_PIX_FMT_RGB32, nWidth, nHeight, 1);
		pFrameRGB->data[0] = (uint8_t*)image.bits();

		AVFrame* pFrameYUV = av_frame_alloc();
		av_image_fill_arrays(pFrameYUV->data, pFrameYUV->linesize, m_pYUVBuffer, AV_PIX_FMT_YUV420P, nWidth, nHeight, 1);

		SwsContext* rgb_to_yuv_ctx = sws_getContext(nWidth, nHeight, AV_PIX_FMT_RGB32, nWidth, nHeight, AV_PIX_FMT_YUV420P, SWS_FAST_BILINEAR, NULL, NULL, NULL);

		sws_scale(rgb_to_yuv_ctx, (const uint8_t* const*)pFrameRGB->data, pFrameRGB->linesize, 0, nHeight, pFrameYUV->data, pFrameYUV->linesize);
		pFrameYUV->height = 1080;
		pFrameYUV->width = 1920;
		pFrameYUV->format = 12;//这三个值需要自己填写,因为在转为image时,这些信息已经没有了

		sws_freeContext(rgb_to_yuv_ctx);
		av_frame_free(&pFrameRGB);
		av_free(buffer1);

		m_pFrameMutex->lock();
		m_qFrameBuffer.enqueue(pFrameYUV);
		m_pFrameMutex->unlock();
	}	
}

//Record的线程
void SaveImage::startRecord()
{
	while (!m_bStopRecord)
	{
		//if (m_bStartRecord && m_bReceivedFrame)
		if (m_bStartRecord && !m_qFrameBuffer.empty())
		{
			startVideoRecord();
		}
	}
	while (!m_qFrameBuffer.empty())
	{
		startVideoRecord();
	}

	writeTail();
	freeRecordParas();
}

//编码的核心函数
void SaveImage::startVideoRecord()
{
	//Encode
	m_pFrameMutex->lock();
	if (m_qFrameBuffer.size() <= 0)
	{
		return;
		m_pFrameMutex->unlock();
	}
	AVFrame* pFrameTemp = m_qFrameBuffer.dequeue();
	m_pFrameMutex->unlock();

	pFrameTemp->pts = m_nPts++;
	avcodec_send_frame(m_pCodecCtxOut, pFrameTemp);//avFrameOutPut
	int ret = avcodec_receive_packet(m_pCodecCtxOut, m_pPacketOut);

	if (ret < 0 && ret != -11)
	{
		qDebug("Failed to encode! \n");
		writeTail();
		//show a message box, let flag = false, clear para
	}
	else
	{
		av_packet_rescale_ts(m_pPacketOut, m_pCodecCtxOut->time_base, m_pVideoStream->time_base);
		m_pPacketOut->stream_index = m_pVideoStream->index;

		ret = av_write_frame(m_pFormatCtxOut, m_pPacketOut);

		av_packet_unref(m_pPacketOut);
		av_freep(m_pPacketOut);
	}

	
	av_frame_unref(pFrameTemp);
	av_frame_free(&pFrameTemp);
}

二:遇到的问题:
在QImage转AVFrame类型时,出现了编码失败的问题,保存下来的视频打不开,且大小固定为200多kb,说明每次保存都只保存了视频格式的头和尾,并没有写入帧数据。逐步排查发现问题出现在类型转换时,转换函数sws_scale,虽然执行成功了,AVFrame中的帧数据也在,但是对比转换后的帧,和刚解码出来的帧发现转换后的帧缺少了一些信息,手动补上了代码中注释的三个信息后,可正常编码。

之前编码265一直失败,找了半天的原因,在设置参数时,不要设置tune参数就可以正常保存了或者不将其设置为"zerolatency",暂时不知具体原因。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值