FFmpeg程式碼實現視訊剪下
有幾天沒寫FFmpeg程式碼了,今天趁著有空閒來擼下FFmpeg剪下視訊程式碼,我也是邊學習邊寫,如果有錯誤,請在評論中指出,互相學習。
思路
說起來這個功能的實現也很簡單,給定一個起始時間、一個結束時間,把視訊檔案開啟,然後把容器中的每條流從起始時間開始,到結束時間為止的資料拷貝到輸出流,然後輸出流儲存為容器,這樣就能看到一個剪下後的視訊檔案了。
程式碼實現
第一步 定義引數
AVFormatContext *ifmt_ctx = NULL; AVFormatContext *ofmt_ctx = NULL; AVOutputFormat *ofmt = NULL; AVPacket pkt; double start_seconds; //開始時間 double end_seconds;//結束時間 const char *in_filename; //輸入檔案 const char *out_filename;//輸出檔案 複製程式碼
第二步 初始化上下文
avformat_open_input(&ifmt_ctx, in_filename, 0, 0); //本質上呼叫了avformat_alloc_context、av_guess_format這兩個函式,即建立了輸出上下文,又根據輸出檔案字尾生成了最適合的輸出容器 avformat_alloc_output_context2(&ofmt_ctx, NULL, NULL, out_filename); ofmt = ofmt_ctx->oformat; 複製程式碼
第三步 建立流及引數拷貝
for (i = 0; i < ifmt_ctx->nb_streams; i++) { AVStream *in_stream = ifmt_ctx->streams[i]; AVStream *out_stream = avformat_new_stream(ofmt_ctx, NULL); if (!out_stream) { fprintf(stderr, "Failed allocating output stream\n"); ret = AVERROR_UNKNOWN; goto end; } avcodec_parameters_copy(out_stream->codecpar, in_stream->codecpar); out_stream->codecpar->codec_tag = 0; } 複製程式碼
第四步 開啟輸出檔案
avio_open(&ofmt_ctx->pb, out_filename, AVIO_FLAG_WRITE); 複製程式碼
第五步處理、寫入資料
// 寫頭資訊 ret = avformat_write_header(ofmt_ctx, NULL); if (ret < 0) { fprintf(stderr, "Error occurred when opening output file\n"); goto end; } //跳轉到指定幀 ret = av_seek_frame(ifmt_ctx, -1, start_seconds * AV_TIME_BASE, AVSEEK_FLAG_ANY); if (ret < 0) { fprintf(stderr, "Error seek\n"); goto end; } // 根據流數量申請空間,並全部初始化為0 int64_t *dts_start_from = malloc(sizeof(int64_t) * ifmt_ctx->nb_streams); memset(dts_start_from, 0, sizeof(int64_t) * ifmt_ctx->nb_streams); int64_t *pts_start_from = malloc(sizeof(int64_t) * ifmt_ctx->nb_streams); memset(pts_start_from, 0, sizeof(int64_t) * ifmt_ctx->nb_streams); while (1) { AVStream *in_stream, *out_stream; //讀取資料 ret = av_read_frame(ifmt_ctx, &pkt); if (ret < 0) break; in_stream = ifmt_ctx->streams[pkt.stream_index]; out_stream = ofmt_ctx->streams[pkt.stream_index]; // 時間超過要擷取的時間,就退出迴圈 if (av_q2d(in_stream->time_base) * pkt.pts > end_seconds) { av_packet_unref(&pkt); break; } // 將擷取後的每個流的起始dts 、pts儲存下來,作為開始時間,用來做後面的時間基轉換 if (dts_start_from[pkt.stream_index] == 0) { dts_start_from[pkt.stream_index] = pkt.dts; } if (pts_start_from[pkt.stream_index] == 0) { pts_start_from[pkt.stream_index] = pkt.pts; } // 時間基轉換 pkt.pts = av_rescale_q_rnd(pkt.pts - pts_start_from[pkt.stream_index], in_stream->time_base, out_stream->time_base, AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX); pkt.dts = av_rescale_q_rnd(pkt.dts - dts_start_from[pkt.stream_index], in_stream->time_base,out_stream->time_base, AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX); if (pkt.pts < 0) { pkt.pts = 0; } if (pkt.dts < 0) { pkt.dts = 0; } pkt.duration = (int) av_rescale_q((int64_t) pkt.duration, in_stream->time_base, out_stream->time_base); pkt.pos = -1; //一幀視訊播放時間必須在解碼時間點之後,當出現pkt.pts < pkt.dts時會導致程式異常,所以我們丟掉有問題的幀,不會有太大影響。 if (pkt.pts < pkt.dts) { continue; } ret = av_interleaved_write_frame(ofmt_ctx, &pkt); if (ret < 0) { fprintf(stderr, "Error write packet\n"); break; } av_packet_unref(&pkt); } //釋放資源 free(dts_start_from); free(pts_start_from); //寫檔案尾資訊 av_write_trailer(ofmt_ctx); 複製程式碼
整個處理流程就這樣了,還是比較簡單的。