解复用FLV文件(不用FFMPEG,C++实现)

技术在于交流、沟通,转载请注明出处并保持作品的完整性。

原文:https://blog.csdn.net/hiwubihe/article/details/82346800

[本系列相关文章]

本篇运用C++实现一个FLV文件的解复用,FLV可以是h264+AAC封装,也可以是h264+mp3封装的。参考ffmpeg源码flvenc.c实现。

探测视频格式是否FLV格式,这部分直接参考协议定义即可。

static bool ProbeFlv(unsigned char*p,long lSize)
{
	unsigned char *d = p;
	
	d+=5;
	unsigned offset = avio_rb32(&d);

	//头FLV 版本<5 头长度>8
	if (p[0] == 'F' &&
		p[1] == 'L' &&
		p[2] == 'V' &&
		p[3] < 5 && p[5] == 0 &&
		offset + 100 < lSize &&
		offset > 8)
	{
		return true;
	}
	return false;
}

读取FLV头

//读取FLV头
static bool flvReadHeader(unsigned char*pData,long lSize,int &iConsumed)
{
	iConsumed = 0;
	unsigned char*p = pData;

	p+=4;
	iConsumed+=4;

	int flags = avio_r8(&p);
	iConsumed+=1;

	g_FlvInfo.iStreamFlags = flags & (FLV_HEADER_FLAG_HASVIDEO | FLV_HEADER_FLAG_HASAUDIO);

	int offset = avio_rb32(&p);
	iConsumed+=4;

	if(offset != iConsumed)
	{
		return false;
	}
	//前TAG长度字段读取
	int iPreTagSize = avio_rb32(&p);
	iConsumed+=4;

	if(iPreTagSize != 0)
	{
		return false;
	}

	return true;
}

循环读取数据,并解析一个完整的TAG

bool FindFlvTag(unsigned char*pData,long lSize,TagHeadInfo &stTagHeadInfo)
{
	//头长度11
	if(lSize<(11))
	{
		//长度不够
		return false;
	}
	//没解析头 现在解析
	if(stTagHeadInfo.iHeadLen == 0)
	{
		unsigned char*p = pData;
		int iOffset=0;

		//TAG头类型
		stTagHeadInfo.type = (FlvTagType)(avio_r8(&p) & 0x1F);
		iOffset += 1;
		//TAG数据长度
		stTagHeadInfo.iTagSize = avio_rb24(&p);
		iOffset += 3;

		uint64_t dts  = avio_rb24(&p);
		dts |= (unsigned)avio_r8(&p) << 24;
		stTagHeadInfo.dts = dts;
		iOffset += 4;

		//跳过STREAMID
		iOffset += 3;

		stTagHeadInfo.iHeadLen =iOffset;

		stTagHeadInfo.pData = pData;

		//头长度+Tag消息长度+前一个Tag长度
		if(lSize>= stTagHeadInfo.iHeadLen + stTagHeadInfo.iTagSize+4)
		{
			return true;
		}
		else
		{
			return false;
		}
	}
	//已经解析过头
	else
	{
		//头长度+Tag消息长度+前一个Tag长度
		if(lSize>= stTagHeadInfo.iHeadLen + stTagHeadInfo.iTagSize+4)
		{
			return true;
		}
		else
		{
			return false;
		}
	}
}

找到TAG 类型FLV_TAG_TYPE_META没有解析,只解析了 FLV_TAG_TYPE_AUDIO和FLV_TAG_TYPE_VIDEO

解析 FLV_TAG_TYPE_AUDIO数据,一般音频如MP3直接保存,AAC需要特殊处理。

bool ParserAudioTag(unsigned char*pData,TagHeadInfo &stTagHeadInfo)
{
	unsigned char*p = pData+stTagHeadInfo.iHeadLen;
	int iUseLen =0;
	//视频flag (帧类型和帧编码格式)
	int flags    = avio_r8(&p);
	iUseLen+=1;

	stTagHeadInfo.iAudioCodeType = flags & FLV_AUDIO_CODECID_MASK;

	stTagHeadInfo.iChans = (flags & FLV_AUDIO_CHANNEL_MASK) == FLV_STEREO ? 2 : 1;
	stTagHeadInfo.iSampleRate = 44100 << ((flags & FLV_AUDIO_SAMPLERATE_MASK) >>
		FLV_AUDIO_SAMPLERATE_OFFSET) >> 3;
	stTagHeadInfo.iSampleBits = (flags & FLV_AUDIO_SAMPLESIZE_MASK) ? 16 : 8;

	//AAC 特殊处理
	if(stTagHeadInfo.iAudioCodeType == FLV_CODECID_AAC)
	{
		int AACPacketType = avio_r8(&p);	
		iUseLen+=1;
		//The AudioSpecificConfig is explained in ISO 14496-3
		if(AACPacketType == 0)
		{
			memcpy(g_FlvInfo.stAACInfo.pAacSpecificConfig, p , stTagHeadInfo.iTagSize - iUseLen);
			g_FlvInfo.stAACInfo.iAacSpecificConfigLen = stTagHeadInfo.iTagSize - iUseLen;
			//解析AudioSpecificConfig结构
			aac_decode_extradata(&(g_FlvInfo.stAACInfo.stADTSContext), g_FlvInfo.stAACInfo.pAacSpecificConfig, g_FlvInfo.stAACInfo.iAacSpecificConfigLen);
			

		}
		//
		else if(AACPacketType == 1)
		{

			unsigned char szAdtsHead[ADTS_HEADER_SIZE];
			aac_set_adts_head(&(g_FlvInfo.stAACInfo.stADTSContext), szAdtsHead, stTagHeadInfo.iTagSize-iUseLen);
			printf("write aac autio adts:%d\n",ADTS_HEADER_SIZE);
			fwrite(szAdtsHead, 1,ADTS_HEADER_SIZE, gAudioOutputFile);

			printf("write aac autio data:%d\n",stTagHeadInfo.iTagSize-iUseLen);
			fwrite(p, stTagHeadInfo.iTagSize-iUseLen, 1, gAudioOutputFile);
			
		}
		else
		{

		}


	}
	else
	{
		printf("write autio data:%d\n",stTagHeadInfo.iTagSize-iUseLen);
		fwrite(p, stTagHeadInfo.iTagSize-iUseLen, 1, gAudioOutputFile);
	}
	
	return true;
}

 处理FLV_TAG_TYPE_VIDEO,这里主要保存SPS/PPS信息,遇到I帧需要加上SPS/PPS信息。注意一个TAG里面有多个包的情况,一般一个TAG里面是P帧,或者SEI+PPS+SPS+I帧1+I帧2等,注意处理好。I帧可能分成几个包。每个包格式都是"长度+数据"。

//长度上层已经保证
bool ParserVideoTag(unsigned char*pData,TagHeadInfo &stTagHeadInfo)
{
	unsigned char*p = pData+stTagHeadInfo.iHeadLen;
	int iUseLen =0;
	//视频flag (帧类型和帧编码格式)
	int flags    = avio_r8(&p);
	iUseLen+=1;

	int flvCodeId = flags& FLV_VIDEO_CODECID_MASK;
	//只支持H264目前
	switch (flvCodeId)
	{
		case FLV_CODECID_H263:
			
			break;
		case FLV_CODECID_REALH263:
			
			break; // Really mean it this time
		case FLV_CODECID_SCREEN:
			
			break;
		case FLV_CODECID_SCREEN2:
			
			break;
		case FLV_CODECID_VP6:
		case FLV_CODECID_VP6A:
			break;
			//标准里面的结构 AVCVIDEOPACKET
		case FLV_CODECID_H264:
			{
				/*  0: AVC sequence header
					1: AVC NALU
					2: AVC end of sequence (lower level NALU
					sequence ender is not required or supported)
				*/

				int AVCPacketType = avio_r8(&p);	
				iUseLen+=1;

				//CompositionTime
				//CTS 是PTS-DTS 一般没有B帧时cts=0
				int32_t cts = (avio_rb24(&p) + 0xff800000) ^ 0xff800000;
				iUseLen+=3;
				stTagHeadInfo.pts = stTagHeadInfo.dts + cts;
				//AVCDecoderConfigurationRecord
				if(AVCPacketType == 0)
				{
					//生成sps和pps 保存
					h264_AVCDecoderConfigurationRecord_2SpsPpsSerial(p);
				}
				//一个TAG里可能有多个NALU 所以是NALUS,可能是把一帧分成几个包如
				//一个I帧 分成 (包1长度+包1数据+包2长度+包2数据),处理时需要改成
				//00000001+包1数据+000001+包2数据
				else if(AVCPacketType == 1)
				{
					h264_NalusParser(p,stTagHeadInfo.iTagSize - iUseLen  );
				}
				else
				{

				}
				

			}
			break;
		case FLV_CODECID_MPEG4:
			break;
		default:
			break;

	}

	unsigned char*p1 = pData+stTagHeadInfo.iHeadLen+stTagHeadInfo.iTagSize;
    int iPreTagLen = avio_rb32(&p1);
	if(iPreTagLen != stTagHeadInfo.iHeadLen+stTagHeadInfo.iTagSize)
	{
		printf("PreviousTagSize Error\n");
	}



	return true;

}

 

 

 

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
FFmpeg是一个开源的跨平台的音视频处理工具,提供了多种功能来实现音视频的录制和转码等操作。 在使用FFmpeg进行录音操作时,需要借助FFmpeg的音频处理库libavcodec和音频采集库libavdevice。 首先,我们需要打开一个音频输入设备,可以是麦克风、音频文件等。使用libavformat中的avformat_open_input函数打开音频输入设备,并设置相应的参数,如采样率、声道数和格式等。 接下来,创建一个AVCodecContext对象,用于编码音频数据。可以使用libavcodec中的avcodec_find_encoder函数找到合适的音频编码器,然后通过avcodec_open2函数打开编码器,得到相应的AVCodecContext对象。 然后,我们需要创建一个AVPacket对象,用于存储音频数据。使用av_packet_alloc函数创建一个空的AVPacket对象。 接下来,进入一个循环中不断读取音频数据并进行编码。使用av_read_frame函数从音频输入设备读取音频数据,传入之前创建的AVPacket对象,将读取到的音频数据存储到AVPacket对象中。 然后,调用avcodec_send_packet函数将AVPacket对象的数据发送给编码器进行编码。再使用avcodec_receive_frame函数从编码器中接收编码后的音频数据。 最后,将编码后的音频数据写入到输出文件或者输出设备中。可以使用libavformat中的av_write_frame函数将音频数据写入到输出文件或者输出设备中。 最后,需要释放资源。关闭音频输入设备、释放AVFormatContext对象和AVPacket对象等。 通过以上的步骤,我们就可以使用FFmpeg的C语言接口实现音频录制的功能了。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值