ffmpeg视频截取

本文采用的截取方式不对视频进行解码,直接在AVPacket级对视频进行截取。

1. 代码

#include <string>
#include <stdio.h>

extern "C"
{
    #include "libavformat/avformat.h"
    #include "libavcodec/bsf.h"
};

//释放所有上下文信息
void release_context(AVFormatContext **in, AVFormatContext *out, AVIOContext *ctx, AVBSFContext **bsf_ctx_)
{
    //关闭输入
    avformat_close_input(in);
    if (!((out->oformat->flags) & AVFMT_NOFILE))
    {
        //关闭输出
        if (avio_close(ctx) < 0)
        {
            printf("failed to close output file\n");
            return;
        }
    }
    //释放输出AVFormatContext
    avformat_free_context(out);
    //释放AVBSFContext
    av_bsf_free(bsf_ctx_);
}

int main1(int argc, char *argv[])
{
    const char *in_filename = "/data/cmhu/test_data/20041609451310232511_20211128090026~1.mp4";
    AVFormatContext *in_fmt_ctx = NULL;
    //打开输入流,创建并初始化输入AVFormatContext
    if (avformat_open_input(&in_fmt_ctx, in_filename, NULL, NULL) < 0)
    {
        printf("failed to open input file\n");
        release_context(&in_fmt_ctx, NULL, NULL, NULL);
        return -1;
    }
    //格式化输出输入流信息
    av_dump_format(in_fmt_ctx, 0, in_filename, 0);
    //寻找流的信息以及编解码信息
    if (avformat_find_stream_info(in_fmt_ctx, NULL) < 0)
    {
        printf("failed to find media stream info\n");
        release_context(&in_fmt_ctx, NULL, NULL, NULL);
        return -1;
    }
    AVFormatContext *out_fmt_ctx = NULL;
    const char *out_filename = "/data/cmhu/jks_aiplatform_projectx/bin/res/test_trans.mp4";
    //创建并初始化输出AVFormatContext
    //不指定AVOutputFormat,ffmpeg会根据格式名或者文件名猜出上下文信息并初始化输出AVFormatContext
    if (avformat_alloc_output_context2(&out_fmt_ctx, NULL, NULL, out_filename) < 0)
    {
        printf("failed to create output format context\n");
        release_context(&in_fmt_ctx, out_fmt_ctx, NULL, NULL);
        return -1;
    }

    int video_index = -1;
    int audio_index = -1;
    AVStream *in_video_stream = nullptr;
    AVStream *in_audio_stream = nullptr;
    //遍历所有输入流
    for (int i = 0; i < in_fmt_ctx->nb_streams; i++)
    {
        AVStream *in_stream = in_fmt_ctx->streams[i];
         enum AVMediaType avMediaType = in_stream->codecpar->codec_type;
        if (avMediaType == AVMEDIA_TYPE_AUDIO)  {
            audio_index = i;
            in_audio_stream = in_stream;
        } else if (avMediaType == AVMEDIA_TYPE_VIDEO) {
            video_index = i;
            in_video_stream = in_stream;
        }
        //创建输出流
        AVStream *out_stream = avformat_new_stream(out_fmt_ctx, NULL);
        if (!out_stream)
        {
            printf("failed to create new stream\n");
            release_context(&in_fmt_ctx, out_fmt_ctx, NULL, NULL);
            return -1;
        }
        //拷贝编解码器的参数设置
        if (avcodec_parameters_copy(out_stream->codecpar, in_stream->codecpar) < 0)
        {
            printf("failed to copy codec parameters from src to dst\n");
            release_context(&in_fmt_ctx, out_fmt_ctx, NULL, NULL);
            return -1;
        }
        //不同封装格式码流格式不同,所以要将codec_tag设为0
        //这样ffmpeg会自动选择和封装格式匹配的码流格式
        out_stream->codecpar->codec_tag = 0;
    }
    //格式化输出输出流信息
    av_dump_format(out_fmt_ctx, 0, out_filename, 1);
    //判断该上下文是否依赖于输入输出,1:不依赖,0:依赖
    if (!((out_fmt_ctx->oformat->flags) & AVFMT_NOFILE))
    {
        //打开输出文件
        if (avio_open2(&out_fmt_ctx->pb, out_filename, AVIO_FLAG_WRITE, NULL, NULL) < 0)
        {
            printf("failed to open output file\n");
            release_context(&in_fmt_ctx, out_fmt_ctx, out_fmt_ctx->pb, NULL);
            return -1;
        }
    }
    //写入文件头
    if (avformat_write_header(out_fmt_ctx, NULL) < 0)
    {
        printf("failed to write header\n");
        release_context(&in_fmt_ctx, out_fmt_ctx, out_fmt_ctx->pb, NULL);
        return -1;
    }

    AVStream *out_video_stream = out_fmt_ctx->streams[video_index];


    AVBSFContext *bsf_ctx = NULL;
    //获取比特流过滤器

    AVCodecParameters *codecpar = in_video_stream->codecpar;
    const AVBitStreamFilter * filter = nullptr;
    if(codecpar->codec_id == AV_CODEC_ID_H264){
        filter = av_bsf_get_by_name("h264_mp4toannexb");
    }else if(codecpar->codec_id == AV_CODEC_ID_HEVC){
        filter = av_bsf_get_by_name("hevc_mp4toannexb");
    }else {
        printf("codec_id is not supported!");
        release_context(&in_fmt_ctx, out_fmt_ctx, out_fmt_ctx->pb, NULL);
        return -1;
    }

    //创建AVBSFContext
    if (av_bsf_alloc(filter, &bsf_ctx) < 0)
    {
        printf("failed to alloc AVBSFContext\n");
        release_context(&in_fmt_ctx, out_fmt_ctx, out_fmt_ctx->pb, &bsf_ctx);
        return -1;
    }
    //拷贝编解码器参数
    if (avcodec_parameters_copy(bsf_ctx->par_in, in_video_stream->codecpar) < 0)
    {
        printf("failed to copy codec parameters from src to dst\n");
        release_context(&in_fmt_ctx, out_fmt_ctx, out_fmt_ctx->pb, &bsf_ctx);
        return -1;
    }
    //初始化AVBSFContext
    if (av_bsf_init(bsf_ctx) < 0)
    {
        printf("failed to init AVBSFContext\n");
        release_context(&in_fmt_ctx, out_fmt_ctx, out_fmt_ctx->pb, &bsf_ctx);
        return -1;
    }

    AVRational ctx_time_base = AVRational{ 1, 25 };
    
    //写入流信息
    int counting = 0;
    int video_frame_index = 0;
    int audio_frame_index = 0;
    AVPacket* pkt ;
    pkt = av_packet_alloc();
    av_init_packet( pkt );
    while (true)
    {
        //读取一帧视频或者几帧音频
        int ret = av_read_frame(in_fmt_ctx, pkt);
        if (ret < 0)
        {
            if (ret == AVERROR_EOF)
            {
                printf("end of file\n");
                //取消对缓缓区的引用,将packet其他字段置为默认值
                av_packet_unref(pkt);
                break;
            }
            printf("failed to read frame\n");
            av_packet_unref(pkt);
            release_context(&in_fmt_ctx, out_fmt_ctx, out_fmt_ctx->pb, NULL);
            return -1;
        }


       //限定为输出格式为avi且是视频流
       if (video_index == pkt->stream_index){

            if ((pkt->flags & AV_PKT_FLAG_KEY) != 0)
            {
                counting++;
            }

            if (counting < 1)
            {
                continue;
            }

            if (counting > 2)
            {
                break;
            }

            video_frame_index++;

            //将输入时间基表示的时间戳转换为输出时间基表示
            pkt->pts = av_rescale_q_rnd(video_frame_index, ctx_time_base, out_video_stream->time_base, (enum AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
            pkt->dts = av_rescale_q_rnd(video_frame_index, ctx_time_base, out_video_stream->time_base, (enum AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
            pkt->duration = av_rescale_q(1, ctx_time_base, out_video_stream->time_base);
            pkt->stream_index = out_video_stream->index;

            //将数据送入过滤器
            ret = av_bsf_send_packet(bsf_ctx, pkt);
            if (ret < 0)
            {
                //单个packet不足以完成过滤,需要继续送入数据
                if (ret == AVERROR(EAGAIN))
                {
                    av_packet_unref(pkt);
                    continue;
                }
                printf("failed to send pakcet to bit stream filter\n");
                av_packet_unref(pkt);
                release_context(&in_fmt_ctx, out_fmt_ctx, out_fmt_ctx->pb, &bsf_ctx);
                return -1;
            }
            //接收过滤后的数据
            //由于单个输入packet可能产生多个输出packet,因此需要使用循环
            int ans = 0;
            do
            {
                //获取过滤后的数据
                ans = av_bsf_receive_packet(bsf_ctx, pkt);
                if (ans < 0)
                {
                    if (ans == AVERROR_EOF)
                    {
                        av_packet_unref(pkt);
                        break;
                    }
                    if (ans != AVERROR(EAGAIN))
                    {
                        printf("failed to reveive filtered packet\n");
                        av_packet_unref(pkt);
                        release_context(&in_fmt_ctx, out_fmt_ctx, out_fmt_ctx->pb, &bsf_ctx);
                        return -1;
                    }
                }
                //交错写入
                if (av_interleaved_write_frame(out_fmt_ctx, pkt) < 0)
                {
                    printf("failed to write frame\n");
                    av_packet_unref(pkt);
                    release_context(&in_fmt_ctx, out_fmt_ctx, out_fmt_ctx->pb, &bsf_ctx);
                    return -1;
                }
            } while (ans == AVERROR(EAGAIN));
            //**********************过滤结束****************************
       } else if (audio_index == pkt->stream_index){
            //交错写入
            if (av_interleaved_write_frame(out_fmt_ctx, pkt) < 0)
            {
                printf("failed to write frame\n");
                av_packet_unref(pkt);
                release_context(&in_fmt_ctx, out_fmt_ctx, out_fmt_ctx->pb, &bsf_ctx);
                return -1;
            }
       }

    //    av_packet_unref(pkt);
    }
    //写入文件尾
    if (av_write_trailer(out_fmt_ctx) != 0)
    {
        printf("failed to write tail\n");
        release_context(&in_fmt_ctx, out_fmt_ctx, out_fmt_ctx->pb, &bsf_ctx);
        return -1;
    }
    release_context(&in_fmt_ctx, out_fmt_ctx, out_fmt_ctx->pb, &bsf_ctx);

    return 0;
}

 

 

2. 在实际使用中的一些注意点

(1)保存需要从关键帧开始保存,从非关键帧开始保存,结果的开头部分会播放异常

(2)一个与本代码关联不大,但是比较通用但是容易出错的点:在拷贝AVPacket时,如果想要做的事深拷贝,不要用ffmpeg自带的一些拷贝操作(除非你彻底的了解你用的接口做的是深拷贝),本人在此处被坑过很久,ffmpeg自带的一些复制、拷贝、克隆都是浅拷贝,在一处释放后会把其他处的也给释放,从而导致程序出现严重问题。我的做法是直接用malloc和memcpy根据pkt->data、pkt->size进行拷贝即可实现深拷贝数据

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


 

posted @ 2024-09-29 11:47  yeren2046  阅读(14)  评论(0编辑  收藏  举报