这篇和 ffmpeg 进行截图相似,不过省略掉编码的过程,从网络上或者文件读取的数据为编码后的数据,间接进行写文件即可,本文以写 MP4 文件为例进行解说。
1. 创立线程执行开启录像
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. 初始化拉去流进行录像
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 长度为准:
if (play->record_duration > 0 && fRecTime > play->record_duration)
当达到能够切片的条件时,创立文件进行切片,也就是关掉上一次的录像,从新开启下一个录像,创立和敞开录像如下:
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;
}
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 录制、所反对的格局,大家能够参考上文截图的做法进行重编码