FFmpeg程式碼實現視訊剪下

MzDavid發表於2018-12-06

有幾天沒寫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;

}// 根據流數量申請空間,並全部初始化為0int64_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);
複製程式碼

整個處理流程就這樣了,還是比較簡單的。

來源:https://juejin.im/post/5c091425f265da6166244518

相關文章