ffmpeg读取rtsp并保存到mp4文件


本文章只讲述mp4文件的录像 至于音频录入 会在下个文章中介绍


总体思路为:初始化----连接相机获取码流--读取码流中的视频--创建输出mp4上下文---写mp4头----循环读取码流--写入mp4----写文件尾--关闭文件



第一步:初始化网络环境

	//环境注册
	av_register_all();

	avcodec_register_all();

	avformat_network_init();


第二步:连接相机 读取rtsp码流

一个avformatcontext包含多个avstream--也就是说一个连接可能有多个码流通道--例如:0通道为视频 1通道为音频等等

他们的包含关系为:AVFormatContext----AVStream[N]                  AVStream----AVCodecContext----AVCodec

如果需要编解码--则需要为每个码流指定一个编解码器--

(1)如果是读取到的码流 则每个码流里面就自动包含了编码器和编码器上下文

(2)如果是需要构造码流 则需要创建编解码器上下文和编解码器


                                                                                         

BOOL CRecordRtspAndMicrophone::ConnectRtsp()
{


	//分配网络文件格式
	m_pRtspFmt = avformat_alloc_context();
	if (!m_pRtspFmt) return FALSE;



	//连接rtsp
	if (avformat_open_input(&m_pRtspFmt, m_strIPCAddr, NULL, NULL) != 0)
	{
		return FALSE;
	}

	//读入一串流 用于分析
	if (avformat_find_stream_info(m_pRtspFmt, NULL) < 0)
	{
		return FALSE;
	}

	//分析读入的流 读取其音频和视频下标
	for (int i = 0; inb_streams; i++)
	{
		if (m_pRtspFmt->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)
		{
			m_pInVst = m_pRtspFmt->streams[i];
			m_nInViStreamIdx = i;
		}
		else if (m_pRtspFmt->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO)
		{
			m_pInAst =m_pRtspFmt->streams[i];
			m_nInAuStreamIdx = i;
		}
	}
	return CreateMp4File();

}

第三步:创建一个用于输出的mp4


ffmpeg对文件操作上下文是AVFormatContext-->AVOutputFormat

ffmpeg操作文件的对象是AVFormatContext-->AVIOContext


BOOL CRecordRtspAndMicrophone::CreateMp4File()
{
	SYSTEMTIME st;
	m_nAudioPts = 0;
	m_nVideoPts = 0;

	GetLocalTime(&st);
	
	//关闭之前的文件
	if (m_pMp4Fmt)
	{
		av_write_trailer(m_pMp4Fmt);
		avio_close(m_pMp4Fmt->pb);
		avcodec_close(m_pOutAst->codec);
		avcodec_close(m_pOutVst->codec);
		m_pOutAst = NULL;
		m_pOutVst = NULL;
		avformat_free_context(m_pMp4Fmt);
		RTSPLOG("Close mp4 file %s success!", m_strOutFilePath);
	}


	sprintf(m_strOutFilePath, "%s%04d%02d%02d%02d%02d%02d%03d.mp4", m_strFilePath,st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, st.wMilliseconds);
	
	//创建文件夹
	CreateMultistageFolder();
	
	//构造输出文件格式
	m_pMp4Fmt = avformat_alloc_context();
	if (!m_pMp4Fmt)
	{
		RTSPLOG("Open mp4 file %s failed because of avformat_alloc_context failed!", m_strOutFilePath);
		return FALSE;
	}

	//输出上下文
	m_pOutFmt = av_guess_format(NULL, m_strOutFilePath, NULL);
	if (NULL == m_pOutFmt)
	{
		RTSPLOG("Open mp4 file %s failed because of av_guess_format failed!", m_strOutFilePath);
		return FALSE;
	}
	m_pMp4Fmt->oformat = m_pOutFmt;

	m_pMp4Fmt->audio_codec_id = CODEC_ID_AAC;
	m_pMp4Fmt->video_codec_id = CODEC_ID_H264;

	

	if (avio_open2(&m_pMp4Fmt->pb, m_strOutFilePath, AVIO_FLAG_WRITE, NULL, NULL) < 0)
	{
		RTSPLOG("Open mp4 file %s failed!", m_strOutFilePath);
		return FALSE;
	}
	RTSPLOG("Open mp4 file %s success!", m_strOutFilePath);

	if (NULL != m_pFun)
	{
		m_pFun(m_strOutFilePath, m_pUser);
	}
	return TRUE;
}


//创建多级目录
BOOL CRecordRtspAndMicrophone::CreateMultistageFolder()
{
	int i = 0;
	while (m_strOutFilePath[i] != '0')
	{
		//如果是文件夹目录
		if ('\\' == m_strOutFilePath[i] || '/' == m_strOutFilePath[i])
		{
			char szTempPath[URL_LEN] = { 0 };
			memcpy(szTempPath, m_strOutFilePath, i + 1);
			if (!PathIsDirectoryA(szTempPath))
			{
				RTSPLOG("File Folder %s not exisit! will create!", szTempPath);
				if (!CreateDirectoryA(szTempPath, NULL))
				{
					RTSPLOG("Close mp4 file %s failed!", szTempPath);
					return FALSE;
				}

			}
		}
	
		i++;
	}
	return TRUE;
}


四、循环读取码流

思路为:创建一个AVPacket对象  循环读取数据到对象中 读取完成之后释放对象内存

DWORD WINAPI CRecordRtspAndMicrophone::ReadStreamThread(LPVOID param)
{
	
	AVPacket pkt;
	CRecordRtspAndMicrophone *pDlg = (CRecordRtspAndMicrophone*)param;

	if (NULL == pDlg) return -1;

	if (!pDlg->ConnectRtsp())
	{
		RTSPLOG("ConnectRtsp failed!");
		return 0;
	}

	pDlg->m_bPause = FALSE;
	av_init_packet(&pkt);
	av_read_play(pDlg->m_pRtspFmt);//play RTSP

	while (pDlg->m_bContinue )
	{
		if ( !pDlg->m_bRecord)
		{
			if (!pDlg->m_bPause)
			{
				av_read_pause(pDlg->m_pRtspFmt);
				pDlg->m_bPause = TRUE;
			}
			Sleep(1);
			continue;
		}
		pDlg->m_bPause = FALSE;

		if (av_read_frame(pDlg->m_pRtspFmt, &pkt) < 0 )
		{
			Sleep(1);
			continue;
		}

		pDlg->m_nVideoFrameCnt++;

		if (pDlg->NeedCreateNewFile())
		{
			pDlg->CreateMp4File();
			pDlg->m_nVideoFrameCnt = 0;
		}
		if (( pDlg->m_nAudioPts < pDlg->m_nVideoPts) && pDlg->m_pOutAst )
		{
			if (FALSE == pDlg->WriteAudioFrame())
			{
				Sleep(0);
			}
		}

		if (pDlg->m_nInViStreamIdx == pkt.stream_index)
		{
			if (NULL == pDlg->m_pOutVst)
			{
				if (!pDlg->AddVideoOutput())
				{
					RTSPLOG("AddVideoOutput failed!");
					break;
				}

				if (!pDlg->AddAudioOutput())
				{
					RTSPLOG("AddVideoOutput failed!");
					break;
				}

				if (!pDlg->WriteMP4Header())
				{
					RTSPLOG("AddVideoOutput failed!");
					break;
				}
				continue;
			}
			pDlg->WriteVideoFrame(&pkt);
			/*pDlg->VideoPacketPush(&pkt);
			if (pDlg->m_bDisplay)
			{
				pDlg->VideoPacketPush( &pkt);
			}
			else
			{
				av_free_packet(&pkt);
			}*/
			if (!pDlg->m_player.InputStream(&pkt))
			{
				av_free_packet(&pkt);
			}

		}

		//if (pDlg->m_nInViStreamIdx == pkt.stream_index)
		//{
		//	//TODO:目前工程不需要接收来自于网络的音频
		//}
		
	}
	pDlg->WriteMP4Trailer();

	if (!pDlg->DisconnectRtsp())
	{
		RTSPLOG("ConnectRtsp failed!");
	}

	return TRUE;
}
//将读取到的数据包写入到mp4文件中
这里有个很重要的问题 Pts和dts的问题 如果这两个值设置不争取 那么图像有可能会显示不出来
BOOL CRecordRtspAndMicrophone::WriteVideoFrame( AVPacket *pPkt )
{
	pPkt->stream_index = m_nOutViStreamIdx;//输出文件中包含码流通道为0--视频 1音频
	pPkt->dts = m_nVideoFrameCnt;		//dts为视频包数目				
	pPkt->pts = 2 * av_rescale_q(m_nVideoFrameCnt, m_pOutVst->codec->time_base, m_pOutVst->time_base);//pts计算出来
	if (av_write_frame(m_pMp4Fmt, pPkt))
	{
		RTSPLOG("Error while writing video frame,fmt=0x%x,audiostream=0x%x,videostream=0x%x,pktsize=%d", m_pMp4Fmt,m_pOutAst,m_pOutVst, pPkt->size);
	}
	else
	{
		//RTSPLOG("[video] VIO frame len=%d", pPkt->size);
	}
	
	m_nVideoPts = pPkt->pts;
	return TRUE;
}


五、文件操作

1、写文件头

BOOL CRecordRtspAndMicrophone::WriteMP4Header()
{	
	av_dump_format(m_pMp4Fmt, 0, "mydump0.txt", 1);
	
	if (NULL == m_pMp4Fmt) return FALSE;
	if (0 != avformat_write_header(m_pMp4Fmt, NULL))
	{
		RTSPLOG("Write file header error!");
		return FALSE;
	}

	return TRUE;
}

2、写文件尾
BOOL CRecordRtspAndMicrophone::WriteMP4Trailer()
{
	av_write_trailer( m_pMp4Fmt );
	return TRUE;
}

3、关闭文件--关闭相机连接

BOOL CRecordRtspAndMicrophone::DisconnectRtsp()
{

	if (m_pMp4Fmt)					//关闭文件
	{
		avio_close(m_pMp4Fmt->pb);
		avformat_free_context(m_pMp4Fmt);	//释放MP4文件上下文
		m_pMp4Fmt = NULL;
	}
	
	if (NULL != m_pVideoDecCtx)			//释放视频解码器
	{
		avcodec_close(m_pVideoDecCtx);
		m_pVideoDecCtx = NULL;
		m_pVideoDec = NULL;
	}

	if (m_pRtspFmt)					//关闭相机连接
	{
		avformat_close_input(&m_pRtspFmt);
		//avformat_free_context(m_pRtspFmt);
		m_pRtspFmt = NULL;
	}
	
	m_pInVst = NULL;
	m_pInAst = NULL;
	m_pOutVst = NULL;
	m_pOutAst = NULL;
	m_bFileInitOK = FALSE;
	m_bContinue = FALSE;
	
	return TRUE;
}




你可能感兴趣的:(流媒体)