zoukankan      html  css  js  c++  java
  • FFmpeg简单转码程序--视频剪辑

    学习了雷神的文章,慕斯人分享精神,感其英年而逝,不胜唏嘘。他有分享一个转码程序《最简单的基于FFMPEG的转码程序》其中使用了filter(参考了ffmpeg.c中的流程),他曾说想再编写一个不需要filter的版本,可惜未有机会。恰好工作中有相关ffmpeg处理内容,故狗尾续貂,撰写本文。

    相关流程:

    1.打开输入文件

    2.打开输出文件

    3.设置解码环境

    4.设置输出流信息

    5.设置编码环境

    6.打开输入流循环读取,解码再编码写入

    7.fflush解码和编码ctx

    8.关闭文件

    本文的代码,为了支持视频精确剪辑,因为GOP关键帧问题,需要使用解码再编码,在编码中对时间做校验

    使用方式:

    ./mycut input output start end
    

      如,截取1到10秒的视频:

    代码如下:

    // mycut.cpp
    extern "C"
    {
    #include <libavutil/time.h>
    #include <libavutil/timestamp.h>
    #include <libavformat/avformat.h>
    #include <libavformat/avio.h>
    #include <libavfilter/avfiltergraph.h>
    #include <libavfilter/buffersink.h>
    #include <libavfilter/buffersrc.h>
    #include <libavutil/opt.h>
    #include <libavutil/imgutils.h>
    
    #include <sys/types.h>
    #include <unistd.h>
    #include <sys/socket.h>
    #include <arpa/inet.h>
    #include <strings.h>
    #include <stdio.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <errno.h>
    #include <sys/file.h>
    #include <signal.h>
    #include <sys/wait.h>
    #include <time.h> // time_t, tm, time, localtime, strftime
    #include <sys/time.h> // time_t, tm, time, localtime, strftime
    }
    
    #define TIME_DEN 1000
    
    // Returns the local date/time formatted as 2014-03-19 11:11:52
    char* getFormattedTime(void);
    
    // Remove path from filename
    #define __SHORT_FILE__ (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__)
    
    // Main log macro
    //#define __LOG__(format, loglevel, ...) printf("%s %-5s [%s] [%s:%d] " format "
    ", getFormattedTime(), loglevel, __func__, __SHORT_FILE__, __LINE__, ## __VA_ARGS__)
    #define __LOG__(format, loglevel, ...) printf("%ld %-5s [%s] [%s:%d] " format "
    ", current_timestamp(), loglevel, __func__, __SHORT_FILE__, __LINE__, ## __VA_ARGS__)
    
    // Specific log macros with 
    #define LOGDEBUG(format, ...) __LOG__(format, "DEBUG", ## __VA_ARGS__)
    #define LOGWARN(format, ...) __LOG__(format, "WARN", ## __VA_ARGS__)
    #define LOGERROR(format, ...) __LOG__(format, "ERROR", ## __VA_ARGS__)
    #define LOGINFO(format, ...) __LOG__(format, "INFO", ## __VA_ARGS__)
    
    
    // Returns the local date/time formatted as 2014-03-19 11:11:52
    char* getFormattedTime(void) {
    
        time_t rawtime;
        struct tm* timeinfo;
    
        time(&rawtime);
        timeinfo = localtime(&rawtime);
    
        // Must be static, otherwise won't work
        static char _retval[26];
        strftime(_retval, sizeof(_retval), "%Y-%m-%d %H:%M:%S", timeinfo);
    
        return _retval;
    }
    
    long long current_timestamp() {
        struct timeval te; 
        gettimeofday(&te, NULL); // get current time
        long long milliseconds = te.tv_sec*1000LL + te.tv_usec/1000; // calculate milliseconds
        // printf("milliseconds: %lld
    ", milliseconds);
        return milliseconds;
    }
    
    static int encode_and_save_pkt(AVCodecContext *enc_ctx, AVFormatContext *ofmt_ctx, AVStream *out_stream)
    {
        AVPacket enc_pkt;
        av_init_packet(&enc_pkt);
        enc_pkt.data = NULL;
        enc_pkt.size = 0;
    
        int ret = 0;
        while (ret >= 0)
        {
            ret = avcodec_receive_packet(enc_ctx, &enc_pkt);
            if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
            {
                ret = 0;
                break;
            }
    
            else if (ret < 0)
            {
                printf("[avcodec_receive_packet]Error during encoding, ret:%d
    ", ret);
                break;
            }
            LOGDEBUG("encode type:%d pts:%d dts:%d
    ", enc_ctx->codec_type, enc_pkt.pts, enc_pkt.dts);
    
            /* rescale output packet timestamp values from codec to stream timebase */
            av_packet_rescale_ts(&enc_pkt, enc_ctx->time_base, out_stream->time_base);
            enc_pkt.stream_index = out_stream->index;
    
            ret = av_interleaved_write_frame(ofmt_ctx, &enc_pkt);
            if (ret < 0)
            {
                printf("write frame error, ret:%d
    ", ret);
                break;
            }
    
            av_packet_unref(&enc_pkt);
        }
        return ret;
    }
    
    static int decode_and_send_frame(AVCodecContext *dec_ctx, AVCodecContext *enc_ctx, int start, int end)
    {
        AVFrame *frame = av_frame_alloc();
        int ret = 0;
    
        while (ret >= 0)
        {
            ret = avcodec_receive_frame(dec_ctx, frame);
            if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
            {
                ret = 0;
                break;
            }
            else if (ret < 0)
            {
                printf("Error while receiving a frame from the decoder");
                break;
            }
            int pts = frame->pts;
            LOGDEBUG("decode type:%d pts:%d
    ", dec_ctx->codec_type, pts);
            if ((pts < start*TIME_DEN) || (pts > end*TIME_DEN)) {  // 数据裁剪
                continue;
            }
            frame->pict_type = AV_PICTURE_TYPE_NONE;
            // 修改pts
            frame->pts  = pts - start*TIME_DEN;
            //printf("pts:%d
    ", frame->pts);
    
            ret = avcodec_send_frame(enc_ctx, frame);
            if (ret < 0)
            {
                printf("Error sending a frame for encoding
    ");
                break;
            }
        }
        av_frame_free(&frame);
        return ret;
    }
    
    static int open_decodec_context(int *stream_idx,
                                    AVCodecContext **dec_ctx, AVFormatContext *fmt_ctx, enum AVMediaType type)
    {
        int ret, stream_index;
        AVStream *st;
        AVCodec *dec = NULL;
        AVDictionary *opts = NULL;
    
        ret = av_find_best_stream(fmt_ctx, type, -1, -1, &dec, 0);
        if (ret < 0)
        {
            fprintf(stderr, "Could not find %s stream in input file 
    ",
                    av_get_media_type_string(type));
            return ret;
        }
        else
        {
            stream_index = ret;
            st = fmt_ctx->streams[stream_index];
    
            /* Allocate a codec context for the decoder */
            *dec_ctx = avcodec_alloc_context3(dec);
            if (!*dec_ctx)
            {
                fprintf(stderr, "Failed to allocate the %s codec context
    ",
                        av_get_media_type_string(type));
                return AVERROR(ENOMEM);
            }
    
            /* Copy codec parameters from input stream to output codec context */
            if ((ret = avcodec_parameters_to_context(*dec_ctx, st->codecpar)) < 0)
            {
                fprintf(stderr, "Failed to copy %s codec parameters to decoder context
    ",
                        av_get_media_type_string(type));
                return ret;
            }
    
            if ((*dec_ctx)->codec_type == AVMEDIA_TYPE_VIDEO)
                (*dec_ctx)->framerate = av_guess_frame_rate(fmt_ctx, st, NULL);
    
            /* Init the decoders, with or without reference counting */
            av_dict_set_int(&opts, "refcounted_frames", 1, 0);
            if ((ret = avcodec_open2(*dec_ctx, dec, &opts)) < 0)
            {
                fprintf(stderr, "Failed to open %s codec
    ",
                        av_get_media_type_string(type));
                return ret;
            }
            *stream_idx = stream_index;
        }
    
        return 0;
    }
    
    static int set_encode_option(AVCodecContext *dec_ctx, AVDictionary **opt)
    {
        const char *profile = avcodec_profile_name(dec_ctx->codec_id, dec_ctx->profile);
        if (profile)
        {
            if (!strcasecmp(profile, "high"))
            {
                av_dict_set(opt, "profile", "high", 0);
            }
        }
        else
        {
            av_dict_set(opt, "profile", "main", 0);
        }
    
        av_dict_set(opt, "threads", "16", 0);
    
        av_dict_set(opt, "preset", "slow", 0);
        av_dict_set(opt, "level", "4.0", 0);
    
        return 0;
    }
    
    static int open_encodec_context(int stream_index, AVCodecContext **oenc_ctx, AVFormatContext *fmt_ctx, enum AVMediaType type)
    {
        int ret;
        AVStream *st;
        AVCodec *encoder = NULL;
        AVDictionary *opts = NULL;
        AVCodecContext *enc_ctx;
    
        st = fmt_ctx->streams[stream_index];
    
        /* find encoder for the stream */
        encoder = avcodec_find_encoder(st->codecpar->codec_id);
        if (!encoder)
        {
            fprintf(stderr, "Failed to find %s codec
    ",
                    av_get_media_type_string(type));
            return AVERROR(EINVAL);
        }
    
        enc_ctx = avcodec_alloc_context3(encoder);
        if (!enc_ctx)
        {
            printf("Failed to allocate the encoder context
    ");
            return AVERROR(ENOMEM);
        }
    
        AVCodecContext *dec_ctx = st->codec;
        if (type == AVMEDIA_TYPE_VIDEO)
        {
            enc_ctx->height = dec_ctx->height;
            enc_ctx->width = dec_ctx->width;
            enc_ctx->sample_aspect_ratio = dec_ctx->sample_aspect_ratio;
    
            enc_ctx->bit_rate = dec_ctx->bit_rate;
            enc_ctx->rc_max_rate = dec_ctx->bit_rate;
            enc_ctx->rc_buffer_size = dec_ctx->bit_rate;
            enc_ctx->bit_rate_tolerance = 0;
            // use yuv420P
            enc_ctx->pix_fmt = AV_PIX_FMT_YUV420P;
            // set frame rate
            enc_ctx->time_base.num = 1;
            enc_ctx->time_base.den = TIME_DEN;
    
            enc_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
            enc_ctx->has_b_frames = false;
            enc_ctx->max_b_frames = 0;
            enc_ctx->gop_size = 120;
    
            set_encode_option(dec_ctx, &opts);
        }
        else if (type == AVMEDIA_TYPE_AUDIO)
        {
            enc_ctx->sample_rate = dec_ctx->sample_rate;
            enc_ctx->channel_layout = dec_ctx->channel_layout;
            enc_ctx->channels = av_get_channel_layout_nb_channels(enc_ctx->channel_layout);
            /* take first format from list of supported formats */
            enc_ctx->sample_fmt = encoder->sample_fmts[0];
            enc_ctx->time_base = (AVRational){1, TIME_DEN};
            enc_ctx->bit_rate = dec_ctx->bit_rate;
            enc_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
        }
        else
        {
            ret = avcodec_copy_context(enc_ctx, st->codec);
            if (ret < 0)
            {
                fprintf(stderr, "Failed to copy context from input to output stream codec context
    ");
                return ret;
            }
        }
    
        if ((ret = avcodec_open2(enc_ctx, encoder, &opts)) < 0)
        {
            fprintf(stderr, "Failed to open %s codec
    ",
                    av_get_media_type_string(type));
            return ret;
        }
    
        *oenc_ctx = enc_ctx;
        return 0;
    }
    int test_cut(char *input_file, char *output_file, int start, int end)
    {
        int ret = 0;
    
        av_register_all();
    
        // 输入流
        AVFormatContext *ifmt_ctx = NULL;
        AVCodecContext *video_dec_ctx = NULL;
        AVCodecContext *audio_dec_ctx = NULL;
        char *flv_name = input_file;
        int video_stream_idx = 0;
        int audio_stream_idx = 1;
    
        // 输出流
        AVFormatContext *ofmt_ctx = NULL;
        AVCodecContext *audio_enc_ctx = NULL;
        AVCodecContext *video_enc_ctx = NULL;
    
        if ((ret = avformat_open_input(&ifmt_ctx, flv_name, 0, 0)) < 0)
        {
            printf("Could not open input file '%s' ret:%d
    ", flv_name, ret);
            goto end;
        }
    
        if ((ret = avformat_find_stream_info(ifmt_ctx, 0)) < 0)
        {
            printf("Failed to retrieve input stream information");
            goto end;
        }
    
        if (open_decodec_context(&video_stream_idx, &video_dec_ctx, ifmt_ctx, AVMEDIA_TYPE_VIDEO) < 0)
        {
            printf("fail to open vedio decode context, ret:%d
    ", ret);
            goto end;
        }
        if (open_decodec_context(&audio_stream_idx, &audio_dec_ctx, ifmt_ctx, AVMEDIA_TYPE_AUDIO) < 0)
        {
            printf("fail to open audio decode context, ret:%d
    ", ret);
            goto end;
        }
        av_dump_format(ifmt_ctx, 0, input_file, 0);
    
        // 设置输出
        avformat_alloc_output_context2(&ofmt_ctx, NULL, NULL, output_file);
        if (!ofmt_ctx)
        {
            printf("can not open ouout context");
            goto end;
        }
    
        // video stream
        AVStream *out_stream;
        out_stream = avformat_new_stream(ofmt_ctx, NULL);
        if (!out_stream)
        {
            printf("Failed allocating output stream
    ");
            ret = AVERROR_UNKNOWN;
            goto end;
        }
        if ((ret = open_encodec_context(video_stream_idx, &video_enc_ctx, ifmt_ctx, AVMEDIA_TYPE_VIDEO)) < 0)
        {
            printf("video enc ctx init err
    ");
            goto end;
        }
        ret = avcodec_parameters_from_context(out_stream->codecpar, video_enc_ctx);
        if (ret < 0)
        {
            printf("Failed to copy codec parameters
    ");
            goto end;
        }
        video_enc_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
        out_stream->time_base = video_enc_ctx->time_base;
        out_stream->codecpar->codec_tag = 0;
    
        // audio stream
        out_stream = avformat_new_stream(ofmt_ctx, NULL);
        if (!out_stream)
        {
            printf("Failed allocating output stream
    ");
            ret = AVERROR_UNKNOWN;
            goto end;
        }
    
        if ((ret = open_encodec_context(audio_stream_idx, &audio_enc_ctx, ifmt_ctx, AVMEDIA_TYPE_AUDIO)) < 0)
        {
            printf("audio enc ctx init err
    ");
            goto end;
        }
        ret = avcodec_parameters_from_context(out_stream->codecpar, audio_enc_ctx);
        if (ret < 0)
        {
            printf("Failed to copy codec parameters
    ");
            goto end;
        }
        audio_enc_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
        out_stream->time_base = audio_enc_ctx->time_base;
        out_stream->codecpar->codec_tag = 0;
    
        av_dump_format(ofmt_ctx, 0, output_file, 1);
    
        // 打开文件
        if (!(ofmt_ctx->oformat->flags & AVFMT_NOFILE))
        {
            ret = avio_open(&ofmt_ctx->pb, output_file, AVIO_FLAG_WRITE);
            if (ret < 0)
            {
                printf("Could not open output file '%s'
    ", output_file);
                goto end;
            }
        }
    
        ret = avformat_write_header(ofmt_ctx, NULL);
        if (ret < 0)
        {
            printf("Error occurred when opening output file
    ");
            goto end;
        }
    
        AVPacket flv_pkt;
        int stream_index;
        while (1)
        {
            AVPacket *pkt = &flv_pkt;
            ret = av_read_frame(ifmt_ctx, pkt);
            if (ret < 0)
            {
                break;
            }
    
            if (pkt->stream_index == video_stream_idx && (pkt->flags & AV_PKT_FLAG_KEY))
            {
                printf("pkt.dts = %ld pkt.pts = %ld pkt.stream_index = %d is key frame
    ", pkt->dts, pkt->pts, pkt->stream_index);
            }
            stream_index = pkt->stream_index;
    
            if (pkt->stream_index == video_stream_idx)
            {
                ret = avcodec_send_packet(video_dec_ctx, pkt);
                LOGDEBUG("read type:%d pts:%d dts:%d
    ", video_dec_ctx->codec_type, pkt->pts, pkt->dts);
    
                ret = decode_and_send_frame(video_dec_ctx, video_enc_ctx, start, end);
                ret = encode_and_save_pkt(video_enc_ctx, ofmt_ctx, ofmt_ctx->streams[0]);
                if (ret < 0)
                {
                    printf("re encode video error, ret:%d
    ", ret);
                }
            }
            else if (pkt->stream_index == audio_stream_idx)
            {
                ret = avcodec_send_packet(audio_dec_ctx, pkt);
                LOGDEBUG("read type:%d pts:%d dts:%d
    ", audio_dec_ctx->codec_type, pkt->pts, pkt->dts);
    
                ret = decode_and_send_frame(audio_dec_ctx, audio_enc_ctx, start, end);
                ret = encode_and_save_pkt(audio_enc_ctx, ofmt_ctx, ofmt_ctx->streams[1]);
                if (ret < 0)
                {
                    printf("re encode audio error, ret:%d
    ", ret);
                }
            }
            av_packet_unref(pkt);
        }
        // fflush
        // fflush encode 
        avcodec_send_packet(video_dec_ctx, NULL);
        decode_and_send_frame(video_dec_ctx, video_enc_ctx, start, end);
        avcodec_send_packet(audio_dec_ctx, NULL);
        decode_and_send_frame(audio_dec_ctx, audio_enc_ctx, start, end);
        // fflush decode 
        avcodec_send_frame(video_enc_ctx, NULL);
        encode_and_save_pkt(video_enc_ctx, ofmt_ctx, ofmt_ctx->streams[0]);
        avcodec_send_frame(audio_enc_ctx, NULL);
        encode_and_save_pkt(audio_enc_ctx, ofmt_ctx, ofmt_ctx->streams[1]);
        LOGDEBUG("stream end
    ");
        av_write_trailer(ofmt_ctx);
    
    end:
        avformat_close_input(&ifmt_ctx);
        if (ofmt_ctx && !(ofmt_ctx->oformat->flags & AVFMT_NOFILE))
            avio_closep(&ofmt_ctx->pb);
        avformat_free_context(ofmt_ctx);
    
        avcodec_free_context(&video_dec_ctx);
        avcodec_free_context(&audio_dec_ctx);
    
        return ret;
    }
    
    int main(int argc, char **argv)
    {
        int ret = 0;
        if (argc < 5)
        {
            printf("Usage: %s input output start end 
    ", argv[0]);
            return 1;
        }
        char *input_file = argv[1];
        char *output_file = argv[2];
        int start = atoi(argv[3]);
        int end = atoi(argv[4]);
        ret = test_cut(input_file, output_file, start, end);
        return ret;
    }
    

      

  • 相关阅读:
    python+django+uwsgi 搭建环境
    python系列-3 pyenv的使用
    生产消费者队列(TaskCompletionSource)的应用
    socket
    Redis 参考
    webform调用windows服务
    文件编码格式获取
    webform版部分视图与请求拦截
    asp.net 自定义节配置 (configSections下的section)
    组合配置草稿
  • 原文地址:https://www.cnblogs.com/hustlijian/p/9753787.html
Copyright © 2011-2022 走看看