ffmpeg -ss 00:00:00 -i input.mp4 -c copy -t 60 output.mp4

-ss 表示视频分割的起始时间,-t 表示分割时长,同时也可以用 00:01:00表示

注意 :-ss 要放在 -i 之前

对于普通的视频分割这个命令可能够用了

但是

如果你想要连续风格一段视频,简单的使用此命令就会发现一个问题:连续分割的视频之间存在细微的交集

原因:

视频的开始都是一个关键帧,如果视频的第一帧不是关键帧就会导致视频播放的前面简短画面模糊不清,所以为了让视频不会出现开始画面模糊的情况,就会从所开始时间定位到其对应帧,如果该帧不是关键帧,则在其位置附近找关键帧的位置,然后从该关键帧处开始复制视频帧。

根据起始时间定位到的帧不是关键帧,而是位于两个关键帧中间的B帧或P帧上,那么是从前一个关键帧开始还是后一个关键帧开始呢?
截至时间定位的帧同样可能处于非关键帧处,这时候不一定要向两边找关键帧?

这时候起始帧如果找前面的关键帧作为起始帧开始复制,就会导致本段视频的和前面视频有重复帧:重复帧数为起始关键帧和上一段截至帧之间的帧数。

如果起始帧找后面的关键帧开始复制,就会导致两段连续分割的视频可能出现跳帧现象

利用ffmpeg提供的库自己实现不重复不跳帧分割

利用上述分析,我们在分割的时候自己统一设置分割视频的截止帧为截止时间对应帧(假设此帧为非关键帧,否则为此帧的前一帧)附近前面关键帧的前一帧,而下一段分割视频就从该关键帧开始。

关键代码

视频头信息设置

AVOutputFormat *ofmt = NULL;
int ret;
ofmt = ofmtCtx->oformat;
for (int i = 0; i < ifmtCtx->nb_streams; i++) {

//根据输入流创建输出流
AVStream *in_stream = ifmtCtx->streams[i];

AVStream *out_stream = avformat_new_stream(ofmtCtx, in_stream->codec->codec);
if (!out_stream) {
return false;
}
//复制AVCodecContext的设置
ret = avcodec_copy_context(out_stream->codec, in_stream->codec);
if (ret < 0) {
return false;
}
out_stream->codec->codec_tag = 0;
if (ofmtCtx->oformat->flags & AVFMT_GLOBALHEADER)
out_stream->codec->flags |= CODEC_FLAG_GLOBAL_HEADER;

}
if (!(ofmt->flags & AVFMT_NOFILE)) {
ret = avio_open(&ofmtCtx->pb, out_filename.c_str(), AVIO_FLAG_WRITE);
if (ret < 0) {
return false;
}
}
ret = avformat_write_header(ofmtCtx, NULL);
if (ret < 0){
return false;
}
return true;

123456789101112131415161718192021222324252627282930313233343536

帧拷贝

//param splitSeconds 为视频分割的时长
bool executeSplit(unsigned int splitSeconds)
{
AVPacket readPkt, splitKeyPacket;
int ret;
av_register_all();
if ((ret = avformat_open_input(&ifmtCtx, inputFileName.c_str(), 0, 0)) < 0) {
return false;
}

if ((ret = avformat_find_stream_info(ifmtCtx, 0)) < 0) {
return false;
}
for (int i = 0; i < ifmtCtx->nb_streams; i++) {

AVStream *in_stream = ifmtCtx->streams[i];
if (in_stream->codec->codec_type == AVMEDIA_TYPE_VIDEO){
video_index = i;
}

}
int den = ifmtCtx->streams[video_index]->r_frame_rate.den;
int num = ifmtCtx->streams[video_index]->r_frame_rate.num;
float fps = (float)num / den;
unsigned int splitVideoSize = fps*splitSeconds;
string save_name;
save_name = outputFileName.substr(0, outputFileName.find_last_of("."));
string temp_name = save_name + "0"+suffixName;
avformat_alloc_output_context2(&ofmtCtx, NULL, NULL, temp_name.c_str());
if (!ofmtCtx) {
return false;
}
if (!writeVideoHeader(ifmtCtx, ofmtCtx, temp_name))
{
return false;
}
vector<uint64_t> vecKeyFramePos;
uint64_t frame_index = 0;
uint64_t keyFrame_index = 0;
int frameCount = 0;
//读取分割点附近的关键帧位置
while (1)
{
++frame_index;
ret = av_read_frame(ifmtCtx, &readPkt);
if (ret < 0)
{
break;
}
//过滤,只处理视频流
if (readPkt.stream_index == video_index){

++frameCount;
if (readPkt.flags&AV_PKT_FLAG_KEY)
{
keyFrame_index = frame_index;
}
if (frameCount>splitVideoSize)
{
vecKeyFramePos.push_back(keyFrame_index);
frameCount = 0;
}
}
av_packet_unref(&readPkt);
}

avformat_close_input(&ifmtCtx);
ifmtCtx = NULL;
//为了重新获取avformatcontext
if ((ret = avformat_open_input(&ifmtCtx, inputFileName.c_str(), 0, 0)) < 0) {
return -1;
}

if ((ret = avformat_find_stream_info(ifmtCtx, 0)) < 0) {
return -1;
}
int number = 0;
av_init_packet(&splitKeyPacket);
splitKeyPacket.data = NULL;
splitKeyPacket.size = 0;
//时长对应的帧数超过视频的总视频帧数,则拷贝完整视频
if (vecKeyFramePos.empty()){
vecKeyFramePos.push_back(frame_index);
}
vector<uint64_t>::iterator keyFrameIter = vecKeyFramePos.begin();

keyFrame_index = *keyFrameIter;
++keyFrameIter;
frame_index = 0;
int64_t lastPts = 0;
int64_t lastDts = 0;
int64_t prePts = 0;
int64_t preDts = 0;
while (1)
{
++frame_index;
ret = av_read_frame(ifmtCtx, &readPkt);
if (ret < 0)
{
break;
}

av_packet_rescale_ts(&readPkt, ifmtCtx->streams[readPkt.stream_index]->time_base, ofmtCtx->streams[readPkt.stream_index]->time_base);
prePts = readPkt.pts;
preDts = readPkt.dts;
readPkt.pts -= lastPts;
readPkt.dts -= lastDts;
if (readPkt.pts < readPkt.dts)
{
readPkt.pts = readPkt.dts + 1;
}
//为分割点处的关键帧要进行拷贝
if (readPkt.flags&AV_PKT_FLAG_KEY&&frame_index == keyFrame_index)
{
av_copy_packet(&splitKeyPacket, &readPkt);
}
else{
ret = av_interleaved_write_frame(ofmtCtx, &readPkt);
if (ret < 0) {
//break;

}
}

if (frame_index == keyFrame_index)
{
lastDts = preDts;
lastPts = prePts;
if (keyFrameIter != vecKeyFramePos.end())
{
keyFrame_index = *keyFrameIter;
++keyFrameIter;
}
av_write_trailer(ofmtCtx);
avio_close(ofmtCtx->pb);
avformat_free_context(ofmtCtx);
++number;
char num[10];
_itoa_s(number, num, 10);
temp_name = save_name + num + suffixName;
avformat_alloc_output_context2(&ofmtCtx, NULL, NULL, temp_name.c_str());
if (!ofmtCtx) {
return false;
}
if (!writeVideoHeader(ifmtCtx, ofmtCtx, save_name + num + suffixName))
{
return false;
}
splitKeyPacket.pts = 0;
splitKeyPacket.dts = 0;
//把上一个分片处的关键帧写入到下一个分片的起始处,保证下一个分片的开头为I帧
ret = av_interleaved_write_frame(ofmtCtx, &splitKeyPacket);
}

av_packet_unref(&readPkt);

}
av_packet_unref(&splitKeyPacket);
av_write_trailer(ofmtCtx);
avformat_close_input(&ifmtCtx);
avio_close(ofmtCtx->pb);
avformat_free_context(ofmtCtx);

return true;
}
---------------------
作者:bikeytang
来源:CSDN
原文:https://blog.csdn.net/BikeyTang/article/details/51491139
版权声明:本文为博主原创文章,转载请附上博文链接!

利用FFMPEG命令进行文件分割的更多相关文章

  1. 程序员的脑袋系列---利用ffmpeg命令提取音频

    今日各大播放器的版权控制越来越严格.导致很多歌曲无法听,但是MV却可以听.这样很蛋疼有木有? 然而,我们可以利用ffmpeg工具提取MV的音频,比如做成MP3格式,这样就可以听了.--哈哈(邪恶地笑) ...

  2. CMD下利用subst命令将一个文件夹镜像成本地的一个虚拟磁盘

    我们都知道net use可以建立网络驱动器映射,这里不说了. 我今天刚看到这命令的,叫镜像虚拟磁盘subst命令,这个命令可以简化好多操作,比如一个常用的文件放在一个路径很深的文件夹中,每次我们想要操 ...

  3. Verilog利用$fdisplay命令往文件中写入数据

    最近在做的事情是,用FPGA生成一些满足特定分布的序列.因此为了验证我生成的序列是否拥有预期的性质,我需要将生成的数据提取出来并且放到MATLAB中做数据分析. 但是网上的程序很乱,表示看不懂==其实 ...

  4. 利用subst命令将一个文件夹镜像成本地的一个磁盘

    企业里都是只有一个c盘,因为这样安全性好,性能也好 那么有时候,我们是需要其他的系统盘来做一些事情的,比如远程的时候需要带过去一个系统盘,这个时候,就可以用subset这个命令来解决这个问题. 叫镜像 ...

  5. 利用grep命令查找文件内容

    例如查找PHP源码某个函数的具体实现 grep -rn "PHP_FUNCTION(socket_accept)" ./ext

  6. Linux中利用grep命令如何检索文件内容详解

    前言 Linux系统中搜索.查找文件中的内容,一般最常用的是grep命令,另外还有egrep命令,同时vi命令也支持文件内容检索.下面来一起看看Linux利用grep命令检索文件内容的详细介绍. 方法 ...

  7. 利用ffmpeg一步一步编程实现摄像头采集编码推流直播系统

    了解过ffmpeg的人都知道,利用ffmpeg命令即可实现将电脑中摄像头的画面发布出去,例如发布为UDP,RTP,RTMP等,甚至可以发布为HLS,将m3u8文件和视频ts片段保存至Web服务器,普通 ...

  8. 利用mongoimport命令导入csv大文件

    最近我同事做了一个PHP项目,其中有一个功能是 上传excel文件并将数据导入mongodb某个集合中. 通常的做法是 写一个上传文件的页面,然后后端 读取 这个文件,利用phpexcel类库将这个e ...

  9. 利用sql命令把结果集输出到文件

    利用sql命令把结果集输出到文件 红色部分的三条命令完成把结果集输出到文件!! [root@test root]# psql -hlocalhost -Utest testWelcome to psq ...

随机推荐

  1. opencv4.0 cuda10 编译速度太慢

    opencv4.0 cuda10 对比不带cuda的时候,编译速度太慢,主要卡在cuda的编译上. 参考http://answers.opencv.org/question/5090/why-open ...

  2. Python编码简要说明

    ●python2默认编码:ASCII编码 达到正确显示,程序需要编码转换: UTF-8 -- >decode解码 --> Unicode Unicode -- > encode编码 ...

  3. cocos2dx spine之二 :spine变色

    cocos2dx版本为3.10 1.具体原理和代码可以参考博文<利用shader改变图片色相Hue>,下面的代码根据该博文进行整理优化. 基本原理就是将RGB值转换为HSL值后加上输入的H ...

  4. 测试环境的好工具bginfo

    省的自己来回找这台机器的IP,剩余空间了. 直接都显示在桌面了. https://www.howtogeek.com/school/sysinternals-pro/lesson7/

  5. gdb 的 symbos 加载详情(set verbose on)

    http://stackoverflow.com/questions/10000335/how-to-use-debug-version-of-libc 两个重要参数 set verbose on d ...

  6. 雷林鹏分享:查看 XML 文件

    查看 XML 文件 在所有主流的浏览器中,均能够查看原始的 XML 文件. 不要指望 XML 文件会直接显示为 HTML 页面. 查看 XML 文件 - Tove Jani Reminder Don' ...

  7. apiCloud 下拉刷新

    api.setRefreshHeaderInfo({ bgColor: '#fff', textColor: '#4d4d4d', },function(ret, err){ //下拉刷新时,刷新的数 ...

  8. php 路途一点启示

    wo:  面试了很多说后台不适合女孩,我不相信,而且我还很笨 he:不是立马就能让别人认可你,其中过程要经历很多得,有时候也要换个方式的'' wo: 我只是想用学的知识得到实践 he:那学习的过程不是 ...

  9. day 7 -10 集合,文本、文件操作,函数

    day7 一.回顾 1.列表和字典在循环里边尽量不要删除元素,很麻烦 2.元组:如果元组里边只有一个逗号,且不加逗号,次元素是什么类型,就是什么类型. 二.集合 ''' 集合:可变的数据类型,它里边的 ...

  10. 0.1.2 max_element和min_element的用法

    找到的位置都是第一个最大(小)的元素,即存在多个相同大小的元素的时候找到的是第一个. 返回的是指针(元素地址). printf("%d\n",*max_element(a,a+n) ...