本文章只讲述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;
}
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;
}