视频合成与分割程序使用
作者开发了一款软件,可以实现对视频的合成和分割,界面如下:
播放时,可以选择多个视频源;在选中“保存视频”情况下,会将多个视频源合成一个视频。如果只取一个视频源中一段视频,就实现了视频分割。
对视频的处理采用了ffmpeg库。作者在此库的基础上,做了进一步封装,使用起来更加简便。
底层处理逻辑可用如下函数表示:
bool InitVideo(); bool AddImage(unsigned char* imageFileBuffer, int bufferSize); bool CloseVideo();
可见底层函数是十分简洁的; 但是ffmpeg函数调用复杂,使用起来不便; 将ffmpeg封装亦非易事;本文就讲述对ffmpeg封装的过程。
视频编码与解码
对视频的处理分为两种:解码和编码。视频播放属于解码,视频生成属于编码。视频播放方面的文章和例子很多;我也写过一篇文章《使用Emgu.CV开发视频播放器简述》。
视频其实就是连续的图片,编码的作用就是压缩图片,减小视频文件的占用。可以把视频文件想象成容器,把一些列图片放入容器,经过编码,生成标准格式的视频文件(如mp4),这个过程就是编码;
把不同视频来源的图片放入容器,就实现了视频的合成;把视频中某段包含的图片放入容器,就实现了视频的分割。只要实现了对多个图片到视频的编码,就实现了视频的合成和分割。
初始化编码器,包括选择编码器,生成输入流,写入文件头等操作。
bool ImageToVideo::InitVideo() { InitFfmpeg(); AVFormatContext* pFormatCtx = NULL; _errnum = avformat_alloc_output_context2(&pFormatCtx, NULL, NULL, _destVideoFileName.c_str()); if (_errnum < 0) { av_strerror(_errnum, _errbuf, sizeof(_errbuf)); return false; } _initFree.pFormatCtx = pFormatCtx; // h264视频编码器 const AVCodec* vcodec = avcodec_find_encoder(AVCodecID::AV_CODEC_ID_H264); if (!vcodec) { return false; } // 创建编码器上下文 AVCodecContext* pVideoCodecCtx = avcodec_alloc_context3(vcodec); if (!pVideoCodecCtx) { return false; } _initFree.pVideoCodecCtx = pVideoCodecCtx; // 比特率、宽度、高度 pVideoCodecCtx->bit_rate = 4000000; pVideoCodecCtx->width = _videoWidth; // 视频宽度 pVideoCodecCtx->height = _videoHeight; // 视频高度 // 时间基数、帧率 pVideoCodecCtx->time_base = { 1, 25 }; pVideoCodecCtx->framerate = { 25, 1 }; // 关键帧间隔 pVideoCodecCtx->gop_size = 10; // 不使用b帧 pVideoCodecCtx->max_b_frames = 0; // 帧、编码格式 pVideoCodecCtx->pix_fmt = AVPixelFormat::AV_PIX_FMT_YUV420P; pVideoCodecCtx->codec_id = AVCodecID::AV_CODEC_ID_H264; // 预设:快速 av_opt_set(pVideoCodecCtx->priv_data, "preset", "superfast", 0); // 全局头 pVideoCodecCtx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; _errnum = avcodec_open2(pVideoCodecCtx, vcodec, NULL); if (_errnum < 0) { return false; } // 为封装器创建视频流 AVStream* pVideoStream = avformat_new_stream(pFormatCtx, NULL); if (!pVideoStream) { return false; } _initFree.pVideoStream = pVideoStream; pVideoStream->codec->codec_tag = 0; pVideoStream->codecpar->codec_tag = 0; // 配置视频流的编码参数 avcodec_parameters_from_context(pVideoStream->codecpar, pVideoCodecCtx); // 打开输出流IO _errnum = avio_open(&pFormatCtx->pb, _destVideoFileName.c_str(), AVIO_FLAG_WRITE); // 打开AVIO流 if (_errnum < 0) { avio_close(pFormatCtx->pb); return false; } _errnum = avformat_write_header(pFormatCtx, NULL); if (_errnum < 0) { return false; } return true; }
添加图片
1 对添加的图片缩放处理:
SwsContext* pSwsCtx = sws_getContext( imageWidth, imageHeight, srcFormat, _initFree.pVideoCodecCtx->width, _initFree.pVideoCodecCtx->height, AVPixelFormat::AV_PIX_FMT_YUV420P, // 输出 SWS_BICUBIC, 0, 0, 0);
2 发送frame ,接收编码后的packet
vframe->pts = vpts++; _errnum = avcodec_send_frame(_initFree.pVideoCodecCtx, vframe); if (_errnum < 0) { // cout << "avcodec_send_frame failed" << endl; av_frame_free(&vframe); return false; } // 视频编码报文 AVPacket* packet = av_packet_alloc(); int writeCount = 0; while (true) { _errnum = avcodec_receive_packet(_initFree.pVideoCodecCtx, packet); if (_errnum < 0 || packet->size <= 0) { int e1 = AVERROR_EOF; int e2 = AVERROR(EAGAIN); if (writeCount == 0) { av_frame_free(&vframe); av_packet_free(&packet); // cout << "avcodec_receive_packet failed" << endl; return false; } else { break; } }
编码完成:写入文件尾数据,释放资源
_errnum = av_write_trailer(_initFree.pFormatCtx); if (_errnum != 0) { return false; } if (pFormatCtx != NULL) { avio_closep(&pFormatCtx->pb); avformat_close_input(&pFormatCtx); } if (pVideoCodecCtx != NULL) { avcodec_close(pVideoCodecCtx); avcodec_free_context(&pVideoCodecCtx); } if (pSwsCtx != NULL) { sws_freeContext(pSwsCtx); }
后记:对于视频的合成和分割,网上有不少这方面的文章,大都是讲述如何使用ffmpeg工具操作,这些方法不灵活,很难满足个性化的需求。本文从视频最基本的原理剖析,实现了图片合成视频的功能;这样就为上层丰富多彩的应用打开了大门。
比如 将两个视频文件合成到一个播放画面;处理过程为:同时读取两个视频源的文件,将两个图片拼接,再放入视频容器。
标签:视频,分割,return,ffmpeg,errnum,pVideoCodecCtx,NULL,pFormatCtx From: https://www.cnblogs.com/yuanchenhui/p/18410039/ffmpeg-video-creator