개발은 하는건가..

[FFMPEG] H264 AvPacket 데이터를 mp4 파일로 만들기 본문

C, C++, MFC

[FFMPEG] H264 AvPacket 데이터를 mp4 파일로 만들기

수동애비 2022. 3. 14. 17:21
반응형

 

AvPacket 를 raw 형태 그대로 저장한 파일을  mp4 파일 형태로 만든 코드이다.
m_RecordFile 이 raw 파일을 open 한 CFile 형태이므로 m_RecordFile 사용하는 부분만 필요한 형태로 교체하면된다.

CVideoRecorder::SRecordFrameInfo 이 구조체도 raw 파일 저장 시 영상크기나 코덱 정보를 저장했던 구조체이므로 반드시 필요하진 않으므로  영상크기만 필요한 형태로 교체해주면 된다.

BOOL CRecordFilePlayer::ExportRecordFile(CString &strFileName)
{
	BOOL bResult = FALSE;
	AVFormatContext* m_pOfmt_ctx = NULL;	
	AVCodecParameters codecpar;
	AVStream* pOutVideoStream = NULL;
	UINT nOldPosition = 0;
	UINT nFileTotalSize = 0, nReadSize = 0, nReqSize = 0;
	UINT nAccPts = 1;
	AVPacket avPacket;
	BYTE* pPacketData = new BYTE[MAX_FRAME_BUFF_SIZE];
	
	CVideoRecorder::SRecordFrameInfo rfi;

	ZeroMemory(&codecpar, sizeof(AVCodecParameters));
		
	if (avformat_alloc_output_context2(&m_pOfmt_ctx, NULL, NULL, CT2A(strFileName)) == 0) {
		pOutVideoStream = avformat_new_stream(m_pOfmt_ctx, NULL);
		
		if (pOutVideoStream != NULL) {
			pOutVideoStream->time_base = { 1, 90000 };

			codecpar.codec_id = AV_CODEC_ID_H264;
			codecpar.codec_type = AVMEDIA_TYPE_VIDEO;
			codecpar.width = m_RecordFileHdr.SVideoInfo.nWidth;			
			codecpar.height = m_RecordFileHdr.SVideoInfo.nHeight;			
			
			avcodec_parameters_copy(pOutVideoStream->codecpar, &codecpar);
			pOutVideoStream->codecpar->codec_tag = 0;

			if (!(m_pOfmt_ctx->oformat->flags & AVFMT_NOFILE)) {
				if (avio_open(&m_pOfmt_ctx->pb, CT2A(strFileName), AVIO_FLAG_WRITE) == 0) {
					if (avformat_write_header(m_pOfmt_ctx, 0) < 0) {
						TRACE("Record file header write failed.");
						goto FINALIZE;
					}
				}
				else {
					TRACE("Record file av open failed.");
					goto FINALIZE;
				}
			}			
		}
		else {
			goto FINALIZE;
		}


		// 기존 파일 포지션 저장 후 맨 처음 위치로 이동.
		nFileTotalSize = (UINT)m_RecordFile.GetLength();
		nOldPosition = (UINT)m_RecordFile.GetPosition();
		m_RecordFile.Seek(sizeof(CVideoRecorder::SRecordHeader), CFile::begin);

		av_init_packet(&avPacket);
		avPacket.stream_index = 0;
		avPacket.pos = -1;
		
		while (m_RecordFile.GetPosition() < nFileTotalSize) {
			nReqSize = sizeof(CVideoRecorder::SRecordFrameInfo);
			nReadSize = m_RecordFile.Read(&rfi, nReqSize);

			if (nReadSize != nReqSize) {
				break;
			}

			nReqSize = rfi.nFrameSize;
			nReadSize = m_RecordFile.Read(pPacketData, nReqSize);

			if (nReqSize != nReadSize) {
				break;
			}
						
			avPacket.pts = nAccPts; 
			avPacket.dts = nAccPts; 
			avPacket.flags = (rfi.nIsKeyFrame == TRUE) ? AV_PKT_FLAG_KEY : 0;
			avPacket.data = pPacketData;
			avPacket.size = nReqSize;
			
			av_interleaved_write_frame(m_pOfmt_ctx, &avPacket);

			nAccPts += (rfi.nFrameDuration * m_RecordFileHdr.SVideoInfo.nTimebase);
		}

		bResult = TRUE;	
	}


FINALIZE:
	if (m_pOfmt_ctx != NULL) {
		av_write_trailer(m_pOfmt_ctx);

		if (!(m_pOfmt_ctx->oformat->flags & AVFMT_NOFILE)) {
			avio_closep(&m_pOfmt_ctx->pb);
		}

		avformat_free_context(m_pOfmt_ctx);
	}

	if (pPacketData != NULL) {
		delete[] pPacketData;
	}

	if (nOldPosition != 0) {
		// 이전 파일 포지션 원복
		m_RecordFile.Seek(nOldPosition, CFile::begin);
	}
	   
	return bResult;
}

*  경우에 따라 AVCodecParameters 값이 원본 AvStream 의 codecpar 를 이용해야 하는 경우도 있다.
   그때는 아래와 같이 처리하면 된다.

// 원본 스트림의 AvStream 구조체가 m_sAvStream 일 경우 아래와 같이 codec 파라메터를 출력 AvStream 에 codecpar 에 복제해준다.

pOutVideoStream->time_base = { 1,  m_sAvStream.time_base.den };
avcodec_parameters_copy(pOutVideoStream->codecpar, m_sAvStream.codecpar);

 

Comments