ffmpeg的一些基础和流程,记录下,加深理解。

1 首先来说,重要的结构体

AVFormatContext  环境上下文,对每个媒体或者网络媒体环境的管理类

AVCodecContext  编解码上下文,某个流(音频或视频)的上下文环境,记录媒体信息

AVCodec              具体编解码器,编解码器,用于媒体编解码

AVPacket              编码包

AVFrame               解码包

2  公共的一些函数

av_register_all()  注册的接口

avfilter_register_all()  过滤器的一些接口

avformat_network_init()  网络上下文注册接口

avdevice_register_all()   设备环境上下文注册

av_log_set_level()          日志级别

3 输入部分(一般表示渲染之前流程)

 avformat_alloc_context   申请上下文的环境,ffmpeg的申请环境。

 av_find_input_format       根据名称打开输入上下文,windows下一般都是通过dshow来查找相关的上下文输入环境

                                         avfoundation用于苹果系统的上下文,alsa gdigrab x11grab  linux上下文 还有其他一些上下文

                                         bktr    dv1394     fbdev    jack    openal     oss     pulse     video4linux2 ,v4l2    vfwcap     decklink

avformat_open_input       打开输入环境上下文,根据av_find_input_format ,根据查找到的输入媒体格式,打开上下文 ,通过

                                          avformat_close_input来关闭,第一个参数为返回的输入环境上下文,第二个参数定义了具体的网络url

                                         设备url 比如 "audio=dummy"  "video=HP Camera"  "rtmp://helloworld/"等,第三个参数是通过上面函数

                                          找到的输入设备格式,第四个参数AVDictionary 通过此变量设置的一些参数,通过av_dict_set_int

                                         av_dict_set 设置的输入参数,比如"video_size 1024x768"  "probesize 100 * 1024"  "pixel_format nv12"

                                          "framerate 30"等。

avformat_find_stream_info  读取一些包,然后从中提取流的信息,有一些文件格式没有头,比如MPEG格式的,这个时候,这个函数就很有用

                                             他可以从读取到的包中得到流的信息,还可以读出流的帧率,而读出来的包会被缓存起来以供处理

avcodec_find_decoder       通过codecID来查找解码器,或者可以通过avcodec_find_decoder_by_name()也可以通过name查找,一般是在avstream中

                                            查找解码器

avcodec_open2                  打开解码器 ,根据解码上下文,来打开编码器

av_read_frame                   从解码器中读取一帧,放在AVPacket中

avcodec_decode_video2  解码视频

avcodec_decode_audio4  解码音频

avcodec_send_packet      新版本的解码,先将AVPacket放入然后通过avcodec_receive_frame得到解码的帧

avcodec_receive_frame

avformat_close_input          关闭编码环境

4   输出部分(一般表示采集过程)

  avformat_alloc_output_context2  用于初始化一个用于输出的环境上下文。首先通过avformat_alloc_context();

                                                        申请环境上下文,然后通过传递的format_name(输出格式的名称)av_guess_format

                                                      获取AVOutputFormat,然后根据filename输出文件的名称得到环境上下文

avio_open2                                   打开输入输出文件,调用成功后创建AVIOContext,在URLProtocol得到协议读写的函数指针

                                                      可以读写网络媒体流

avcodec_find_encoder                 用于查找编码环境

avcodec_alloc_context3                用于申请编码上下文

avcodec_open2                            打开编码器

avformat_new_stream                  创建stream通道,即创建输出码流的AVStream

avformat_copy_context                 --> avcodec_find_decoder + avcodec_alloc_context3 + avcodec_parameters_to_context

                                                       + avcodec_parameters_from_context   

avformat_write_header                  根据输出文件写文件头。

avcodec_encode_video2              编码媒体流

avcodec_send_frame                    新版本的编码媒体流,输入frame,得到packet

avcodec_receive_packet

av_interleaved_write_frame          写入媒体帧

av_write_trailer                              写入输出文件的文件尾

avcodec_close                               关闭编码器

avformat_close_input                      关闭环境上下文

5  示例代码

#include <stdio.h>
extern "C"
{
    #include "libavutil/timestamp.h"
    #include "libavformat/avformat.h"
}
///包的日志输出
static void log_packet(const AVFormatContext *fmt_ctx, const AVPacket *pkt, const char *tag)
{
根据环境上下文获取时间基数
    AVRational *time_base = &fmt_ctx->streams[pkt->stream_index]->time_base;

    printf("%s: pts:%s pts_time:%s dts:%s dts_time:%s duration:%s duration_time:%s stream_index:%d\n",
           tag,
           av_ts2str(pkt->pts), av_ts2timestr(pkt->pts, time_base),
           av_ts2str(pkt->dts), av_ts2timestr(pkt->dts, time_base),
           av_ts2str(pkt->duration), av_ts2timestr(pkt->duration, time_base),
           pkt->stream_index);
}

int cut_video(double from_seconds, double end_seconds, const char* in_filename, const char* out_filename) {
定义的变量,输出环境上下文,输入环境上下文,编码包,函数调用返回值,帧循环计数
    AVOutputFormat *ofmt = NULL;
    AVFormatContext *ifmt_ctx = NULL, *ofmt_ctx = NULL;
    AVPacket pkt;
    int ret, i;

    av_register_all();

    if ((ret = avformat_open_input(&ifmt_ctx, in_filename, 0, 0)) < 0) {
        fprintf(stderr, "Could not open input file '%s'", in_filename);
        goto end;
    }

    if ((ret = avformat_find_stream_info(ifmt_ctx, 0)) < 0) {
        fprintf(stderr, "Failed to retrieve input stream information");
        goto end;
    }
    //打印输入和输出格式的详细信息
    av_dump_format(ifmt_ctx, 0, in_filename, 0);

    avformat_alloc_output_context2(&ofmt_ctx, NULL, NULL, out_filename);
    if (!ofmt_ctx) {
        fprintf(stderr, "Could not create output context\n");
        ret = AVERROR_UNKNOWN;
        goto end;
    }

    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, in_stream->codec->codec);
        if (!out_stream) {
            fprintf(stderr, "Failed allocating output stream\n");
            ret = AVERROR_UNKNOWN;
            goto end;
        }

        ret = avcodec_copy_context(out_stream->codec, in_stream->codec);
        if (ret < 0) {
            fprintf(stderr, "Failed to copy context from input to output stream codec context\n");
            goto end;
        }
        out_stream->codec->codec_tag = 0;
        if (ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER)
            out_stream->codec->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
*/
        AVStream *in_stream = ifmt_ctx->streams[i];
        AVCodec * codec = avcodec_find_decoder(in_stream->codecpar->codec_id);
        AVStream *out_stream = avformat_new_stream(ofmt_ctx, codec);
        if (!out_stream) {
            fprintf(stderr, "Failed allocating output stream\n");
            ret = AVERROR_UNKNOWN;
            goto end;
        }

        AVCodecContext * codec_ctx = avcodec_alloc_context3(codec);
        ret = avcodec_parameters_to_context(codec_ctx, in_stream->codecpar);
        if (ret < 0) {
            fprintf(stderr, "Failed to copy in_stream codecpar to codec context\n");
            goto end;
        }

        codec_ctx->codec_tag = 0;
        if (ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER)
            codec_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;

        ret = avcodec_parameters_from_context(out_stream->codecpar, codec_ctx);
        if (ret < 0) {
            fprintf(stderr, "Failed to copy codec context to out_stream codecpar context\n");
            goto end;
        }
    }
    av_dump_format(ofmt_ctx, 0, out_filename, 1);


    if (!(ofmt->flags & AVFMT_NOFILE)) {
        ret = avio_open(&ofmt_ctx->pb, out_filename, AVIO_FLAG_WRITE);
        if (ret < 0) {
            fprintf(stderr, "Could not open output file '%s'", out_filename);
            goto end;
        }
    }

    ret = avformat_write_header(ofmt_ctx, NULL);
    if (ret < 0) {
        fprintf(stderr, "Error occurred when opening output file\n");
        goto end;
    }
    //按视频定位媒体
    // int64_t start_from = 8*AV_TIME_BASE;
    ret = av_seek_frame(ifmt_ctx, -1, from_seconds*AV_TIME_BASE, AVSEEK_FLAG_ANY);
    if (ret < 0) {
        fprintf(stderr, "Error seek\n");
        goto end;
    }

    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];

        log_packet(ifmt_ctx, &pkt, "in");

        if (av_q2d(in_stream->time_base) * pkt.pts > end_seconds) {
            av_free_packet(&pkt);
            break;
        }

        if (dts_start_from[pkt.stream_index] == 0) {
            dts_start_from[pkt.stream_index] = pkt.dts;
            printf("dts_start_from: %s\n", av_ts2str(dts_start_from[pkt.stream_index]));
        }
        if (pts_start_from[pkt.stream_index] == 0) {
            pts_start_from[pkt.stream_index] = pkt.pts;
            printf("pts_start_from: %s\n", av_ts2str(pts_start_from[pkt.stream_index]));
        }

        /* copy packet */
        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;
        log_packet(ofmt_ctx, &pkt, "out");
        printf("\n");

        ret = av_interleaved_write_frame(ofmt_ctx, &pkt);
        if (ret < 0) {
            fprintf(stderr, "Error muxing packet\n");
            //break;    // 注释后,解决 错误 pts (6000) < dts (12000) in stream 0
        }
        av_free_packet(&pkt);
    }
    free(dts_start_from);
    free(pts_start_from);

    av_write_trailer(ofmt_ctx);
end:

    avformat_close_input(&ifmt_ctx);

    /* close output */
    if (ofmt_ctx && !(ofmt->flags & AVFMT_NOFILE))
        avio_closep(&ofmt_ctx->pb);
    avformat_free_context(ofmt_ctx);

    if (ret < 0 && ret != AVERROR_EOF) {
        fprintf(stderr, "Error occurred: %s\n", av_err2str(ret));
        return 1;
    }

    return 0;
}

///测试函数,实现了从源文件读出写入目的文件,输入开始时间和结束时间以及输入文件和输出文件名
int test(const char *starttime,const char *endtime,const char *srcfile,const char *outfile){
    

    double startime = atoi(starttime);
    double endtime = atoi(endtime);
    cut_video(startime, endtime, srcfile, endfile);

    return 0;
}

   6  上述剪切出现问题,由于pts和dts不同步导致异常写入

所以在写入的时候,需要判断如果 if(packet.pts < packet.dts) continue; 即将B帧解码略过