利用FFMPEG命令進行檔案分割
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
版權宣告:本文為博主原創文章,轉載請附上博文連結!