利用FFMPEG進行視頻分割

轉載地址:http://blog.csdn.net/bikeytang/article/details/51491139

利用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;

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36

幀拷貝

//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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章