前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >SkeyeExPlayer(Windows)开发系列之采用ffmpeg进行录像

SkeyeExPlayer(Windows)开发系列之采用ffmpeg进行录像

原创
作者头像
Openskeye
发布2023-04-14 11:11:58
2090
发布2023-04-14 11:11:58
举报
文章被收录于专栏:国标视频云平台

这篇和ffmpeg进行截图类似,不过省略掉编码的过程,从网络上或者文件读取的数据为编码后的数据,直接进行写文件即可,本文以写MP4文件为例进行讲解。

1.创建线程执行开启录像

代码语言:txt
复制
	player->record_duration = duration*60;
	player->record_piece_id = 0;
	player->record_time = 0.0f;
	memset(player->record_path, 0, sizeof(MAX_PATH_LENGTH));
	strcpy(player->record_path, file);
	player->bRecording = true;

	//开启录像线程
	player->record_thread = CreateThread(NULL, 0, av_record_thread_proc, player, 0, NULL);

2.初始化拉去流进行录像

代码语言:txt
复制
void* av_record_thread_proc(void *thread_param)
{
	PLAYER* play = (PLAYER*)thread_param;
	if (!play)
	{
		return NULL;
	}

	AVFormatContext *i_fmt_ctx = NULL;
	AVStream *i_video_stream = NULL;
	AVFormatContext *o_fmt_ctx = NULL;
	AVStream *o_video_stream = NULL;

	av_register_all();
	avcodec_register_all();
	avformat_network_init();

	/* should set to NULL so that avformat_open_input() allocate a new one */
	i_fmt_ctx = NULL;

	if (avformat_open_input(&i_fmt_ctx, play->file_url, NULL, NULL) != 0)
	{
		//fprintf(stderr, "could not open input file\n");
		return NULL;
	}

	int nRet = avformat_find_stream_info(i_fmt_ctx, NULL);
	if (nRet<0)
	{
		///fprintf(stderr, "could not find stream info\n");
		return NULL;
	}

	//bool bSupportVideo = true;
	//bool bSupportAudio = true;
	int nVideoIndex = -1;
	int nAudioIndex = -1;

	int nPathLen = strlen(play->record_path);
	char *csFileName = new char[nPathLen];
	memset(csFileName, 0, nPathLen);
	strncpy(csFileName, play->record_path, nPathLen-4);

	char sSliceupName[MAX_PATH_LENGTH] = {0,};

	if (play->record_duration > 0)
	{
		sprintf(sSliceupName, "%s_%d.mp4", csFileName, play->record_piece_id);
	}
	else
	{
		sprintf(sSliceupName, "%s.mp4", csFileName);
	}
	//创建MP4文件
	if (CreateMediaFile(&o_fmt_ctx, i_fmt_ctx, sSliceupName) == 0)
	{
		return 0;
	}

	/*
	* since all input files are supposed to be identical (framerate, dimension, color format, ...)
	* we can safely set output codec values from first input file
	*/
	int nOutStreamId = 0;
	for (int i = 0; i < i_fmt_ctx->nb_streams; i++)
	{
		AVStream *in_stream = i_fmt_ctx->streams[i];
		if (in_stream->codec->codec_type == AVMEDIA_TYPE_AUDIO)
		{
			AVCodecID codecId = in_stream->codec->codec_id;
			//音频格式过滤(just support aac,mp3)
			if ((codecId != AV_CODEC_ID_MP3 && codecId != AV_CODEC_ID_AAC )/*|| in_stream->codec->extradata_size==0*/) ///< preferred ID for decoding MPEG audio layer 1, 2 or 3
			{
				//bSupportAudio = false;
				continue;
			}
			nAudioIndex = nOutStreamId;

		}
		if (i_fmt_ctx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) 
		{
			AVCodecID codecId = in_stream->codec->codec_id;
			//视频频格式过滤(just support h264,265)
			if ((codecId != AV_CODEC_ID_H264 && codecId != AV_CODEC_ID_H265) /*|| in_stream->codec->extradata_size == 0*/) ///< preferred ID for decoding MPEG audio layer 1, 2 or 3
			{
				//bSupportVideo = false;
				continue;
			}
			nVideoIndex = nOutStreamId;
		}
		nOutStreamId++;
	}

	int start_pts = -1;
	int start_dts = -1;
	int64_t audio_start_pts = -1;
	int64_t audio_start_dts = -1;
	int64_t video_start_pts = -1;
	int64_t video_start_dts = -1;

	bool audio_re_record = false;
	bool video_re_record = false;

	int64_t pts, dts;
	//int total_frame = 3000;//写3000帧文件  
	//while (total_frame--)
	while (play->bRecording)
	{
		AVPacket i_pkt;
		av_init_packet(&i_pkt);
		i_pkt.size = 0;
		i_pkt.data = NULL;

		if (av_read_frame(i_fmt_ctx, &i_pkt) <0)
			break;

		//ret = av_interleaved_write_frame(pOFormat, tmppkt);
		AVStream *in_stream = i_fmt_ctx->streams[i_pkt.stream_index];
		AVStream *out_stream = NULL;

		if (in_stream->codec->codec_type == AVMEDIA_TYPE_VIDEO && nVideoIndex > -1 )
		{
			int nTimeBase = in_stream->time_base.den;
			if (nTimeBase>0)
				play->record_time = (float)(i_pkt.dts - start_dts) / nTimeBase;

			//TRACE("Video Timestamp:	%f time_base = %d %lld %lld duration = %d  \n", m_fRecordTime, in_stream->time_base.den, i_pkt.pts, i_pkt.dts, i_pkt.duration);
			if (start_pts < 0)
				start_pts = i_pkt.pts;
			if (start_dts < 0)
				start_dts = i_pkt.dts;

			//录像时间,单位: S
			float fRecTime = 0.0f;
			if (nTimeBase>0)
				fRecTime = (float)(i_pkt.dts - start_dts) / nTimeBase;
			//判断是否达到切片的要求
			if (play->record_duration > 0 && fRecTime > play->record_duration && i_pkt.flags == AV_PKT_FLAG_KEY)
			{
				play->record_piece_id++;
				//关闭已经完成切片的文件
				CloseMediaFile(o_fmt_ctx);
				memset(sSliceupName, 0x00, MAX_PATH_LENGTH);
				sprintf(sSliceupName, "%s_%d.mp4", csFileName, play->record_piece_id);

				//创建MP4文件
				if (CreateMediaFile(&o_fmt_ctx, i_fmt_ctx, sSliceupName) == 0)
				{
					return 0;
				}
				start_pts = i_pkt.pts;
				start_dts = i_pkt.dts;
				audio_re_record = true;
				video_re_record = true;

			}
		}
		else if (in_stream->codec->codec_type == AVMEDIA_TYPE_AUDIO && nAudioIndex > -1 && nVideoIndex == -1)
		{
			int nTimeBase = in_stream->time_base.den;
			if (nTimeBase>0)
				play->record_time = (float)(i_pkt.dts) / nTimeBase;

			//TRACE("Audio Timestamp:	%f time_base = %d %lld %lld duration = %d  \n", play->record_time, in_stream->time_base.den, i_pkt.pts, i_pkt.dts, i_pkt.duration);
			if (start_pts < 0)
				start_pts = i_pkt.pts;
			if (start_dts < 0)
				start_dts = i_pkt.dts;

			//录像时间,单位: S
			float fRecTime = 0.0f;
			if (nTimeBase>0)
				fRecTime = (float)(i_pkt.dts - start_dts) / nTimeBase;
			//判断是否达到切片的要求
			if (play->record_duration > 0 && fRecTime > play->record_duration)
			{
				play->record_piece_id++;
				//关闭已经完成切片的文件
				CloseMediaFile(o_fmt_ctx);
				memset(sSliceupName, 0x00, 512);
				sprintf(sSliceupName, "%s_%d.mp4", csFileName, play->record_piece_id);

				//创建MP4文件
				if (CreateMediaFile(&o_fmt_ctx, i_fmt_ctx, sSliceupName) == 0)
				{
					return 0;
				}
				start_pts = i_pkt.pts;
				start_dts = i_pkt.dts;
				audio_re_record = true;
				video_re_record = true;
			}
		}

		if (in_stream->codec->codec_type == AVMEDIA_TYPE_VIDEO)//不支持的视频 过滤
		{
			if( nVideoIndex == -1)
				continue;
			out_stream  = o_fmt_ctx->streams[nVideoIndex];

			if (video_start_pts < 0)
				video_start_pts = i_pkt.pts;
			if (video_start_dts < 0)
				video_start_dts = i_pkt.dts;

			if (video_re_record)
			{
				video_start_pts = i_pkt.pts;
				video_start_dts = i_pkt.dts;
				video_re_record = false;
			}
			i_pkt.pts = i_pkt.pts - video_start_pts;
			i_pkt.dts = i_pkt.dts - video_start_dts;
		}

		if (in_stream->codec->codec_type == AVMEDIA_TYPE_AUDIO )//不支持的音频 过滤
		{
			if (nAudioIndex == -1)
				continue;
			out_stream = o_fmt_ctx->streams[nAudioIndex];

			if (audio_start_pts < 0)
				audio_start_pts = i_pkt.pts;
			if (audio_start_dts < 0)
				audio_start_dts = i_pkt.dts;

			if (audio_re_record)
			{
				audio_start_pts = i_pkt.pts;
				audio_start_dts = i_pkt.dts;
				audio_re_record = false;
			}
			i_pkt.pts = i_pkt.pts - audio_start_pts;
			i_pkt.dts = i_pkt.dts - audio_start_dts;

		}
		if (!out_stream)
			continue;

		i_pkt.pts = (i_pkt.pts > 0) ? i_pkt.pts : 0;
		i_pkt.dts = (i_pkt.dts > 0) ? i_pkt.dts : 0;
		i_pkt.duration = (i_pkt.duration > 0) ? i_pkt.duration : 0;

		i_pkt.pts = av_rescale_q_rnd(i_pkt.pts, in_stream->time_base, out_stream->time_base, AV_ROUND_NEAR_INF);
		i_pkt.dts = av_rescale_q_rnd(i_pkt.dts, in_stream->time_base, out_stream->time_base, AV_ROUND_NEAR_INF);
		i_pkt.duration = av_rescale_q(i_pkt.duration, in_stream->time_base, out_stream->time_base);
		i_pkt.pos = -1;

		int ret = av_interleaved_write_frame(o_fmt_ctx, &i_pkt);
		if (ret < 0)
			continue;
			//break;
	}

	avformat_close_input(&i_fmt_ctx);

	av_write_trailer(o_fmt_ctx);

	avcodec_close(o_fmt_ctx->streams[0]->codec);
	av_freep(&o_fmt_ctx->streams[0]->codec);
	av_freep(&o_fmt_ctx->streams[0]);

	avio_close(o_fmt_ctx->pb);
	av_free(o_fmt_ctx);
	av_bitstream_filter_close(aacBsf);
	if (csFileName)
		delete[] csFileName;
	return NULL;
}

纵观以上录像代码,通过另外开辟线程进行录像,线程执行过程分为以下几个部分:

1.拉流读取流数据模块

关于这块不做过多赘述,大家有兴趣可以参考系列文章的前几篇文章;

2.录像切片

由于mp4文件过长可能导致播放不了的问题,所以我们支持对录制mp4文件进行切片录像,录像参考时间戳默认以视频为准,如果没有视频则以音频为准,判断条件以设置的一段MP4长度为准:

代码语言:txt
复制
if (play->record_duration > 0 && fRecTime > play->record_duration)

当达到可以切片的条件时,创建文件进行切片,也就是关掉上一次的录像,重新开启下一个录像,创建和关闭录像如下:

代码语言:txt
复制
int CreateMediaFile(AVFormatContext ** o_fmt_ctx, AVFormatContext *i_fmt_ctx, char *csFileName)
{
	int RET = avformat_alloc_output_context2(o_fmt_ctx, NULL, NULL, csFileName);
	/*
	* since all input files are supposed to be identical (framerate, dimension, color format, ...)
	* we can safely set output codec values from first input file
	*/
	for (int i = 0; i < i_fmt_ctx->nb_streams; i++)
	{
		AVStream *in_stream = i_fmt_ctx->streams[i];
		if (in_stream->codec->codec_type == AVMEDIA_TYPE_AUDIO)
		{
			AVCodecID codecId = in_stream->codec->codec_id;
			//音频格式过滤(just support aac,mp3)
			if ((codecId != AV_CODEC_ID_MP3 && codecId != AV_CODEC_ID_AAC) /*|| in_stream->codec->extradata_size == 0*/) ///< preferred ID for decoding MPEG audio layer 1, 2 or 3
			{
				//bSupportAudio = false;
				continue;
			}
		}
		if (i_fmt_ctx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)
		{
			AVCodecID codecId = in_stream->codec->codec_id;
			//视频频格式过滤(just support h264,265)
			if ((codecId != AV_CODEC_ID_H264 && codecId != AV_CODEC_ID_H265) /*|| in_stream->codec->extradata_size == 0*/) ///< preferred ID for decoding MPEG audio layer 1, 2 or 3
			{
				//bSupportVideo = false;
				continue;
			}
		}

		//// 获取AVC结构,包涵SPS和PPS [Dingshuai 2017/07/12]
		//for (int j = 0;j<i_fmt_ctx->streams[i]->codec->extradata_size;j++)
		//{
		//	TRACE("%x ", i_fmt_ctx->streams[i]->codec->extradata[j]);
		//}

		AVStream *out_stream = avformat_new_stream(*o_fmt_ctx, in_stream->codec->codec);
		if (!out_stream)
		{
			continue;
			//return 0;
		}

		int ret = 0;
		ret = avcodec_copy_context(out_stream->codec, in_stream->codec);
		if (ret < 0) {
			fprintf(stderr, "Failed to copy context from input to output stream codec context\n");
			return 0;
		}
		out_stream->codec->codec_tag = 0;
		if ((*o_fmt_ctx)->oformat->flags & AVFMT_GLOBALHEADER)
			out_stream->codec->flags |= CODEC_FLAG_GLOBAL_HEADER;
	}

	avio_open(&((*o_fmt_ctx)->pb), csFileName, AVIO_FLAG_WRITE);

	av_dump_format((*o_fmt_ctx), 0, csFileName, 1);

	if (avformat_write_header((*o_fmt_ctx), NULL) < 0)
	{
		return 0;
	}
	return 1;
}
代码语言:txt
复制
void CloseMediaFile(AVFormatContext* o_fmt_ctx)
{
	av_write_trailer(o_fmt_ctx);

	avcodec_close(o_fmt_ctx->streams[0]->codec);
	av_freep(&o_fmt_ctx->streams[0]->codec);
	av_freep(&o_fmt_ctx->streams[0]);

	avio_close(o_fmt_ctx->pb);
	av_free(o_fmt_ctx);
}

需要注意的是,编译ffmpeg的时候需要将写文件相关的模块编译进去,否则avformat_alloc_output_context2就会调用出错,还需要注意的就是写MP4支持的格式有限,上文代码中限定只支持aac和MP3格式,为了格式统一,这里应该将其他不支持的格式均转成aac或者MP3这种MP4录制、所支持的格式,大家可以参考上文截图的做法进行重编码

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档