EasyPlayerPro(Windows)开发系列之采用ffmpeg进行录像
来源:互联网 发布:nginx生成ssl证书 编辑:程序博客网 时间:2024/06/04 19:15
这篇和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录制、所支持的格式,大家可以参考上文截图的做法进行重编码
阅读全文
0 0
- EasyPlayerPro(Windows)开发系列之采用ffmpeg进行录像
- EasyPlayerPro(Windows)开发系列之采用ffmpeg进行截图
- EasyPlayerPro(Windows)开发系列之解决分片录像时间戳不正常的问题
- EasyPlayerPro(Windows)开发系列之解决ffmpeg接口调用卡住的问题
- EasyPlayerPro基于FFMPEG实现播放同时进行录像的功能
- EasyPlayerPro(Windows)开发之ffmpeg log输出报错
- EasyPlayerPro(Windows)开发系列之快放慢放的实现
- EasyPlayerPro(Windows)流媒体播放器开发之ffmpeg log输出报错
- EasyPlayerPro(Windows)开发之接口说明
- EasyPlayerPro(Windows)开发之跨语言调用
- EasyPlayerPro(Windows)开发之框架讲解
- EasyPlayerPro(Windows)流媒体播放器开发之接口设计
- EasyPlayerPro(Windows)流媒体播放器开发之跨语言调用
- EasyPlayerPro(Windows)流媒体播放器开发之框架讲解
- FFMpeg Windows下屏幕录像
- FFmpeg录屏软件开发之屏幕录像
- 使用ffmpeg录像,同时进行语音识别
- 【Windows Mobile开发系列 之 开始二】使用VS2008进行Windows Mobile开发环境的搭建
- 求解强连通分量的Tarjan算法描述
- 暑假总结(六)
- C语言初步-第43讲: 从文本文件中读入数据(成绩统计)
- Java中的溢出现象
- SpringBoot
- EasyPlayerPro(Windows)开发系列之采用ffmpeg进行录像
- 支持向量机support vector machines
- 暑假总结(七)
- 隐式数据类型转换
- Ubuntu安装Atom的activate-power-mode插件
- 算法----八皇后扩展
- Keras私房手册
- 停课总结(一)
- 图片下的阴影 box-shadow