【FFmpeg】关键结构体的初始化和释放(AVFormatContext、AVIOContext等)

目录

  • 1.AVFormatContext
    • 1.1 初始化(avformat_alloc_context)
    • 1.2 释放(avformat_free_context)
  • 2.AVIOContext
    • 2.1 初始化(avio_alloc_context)
    • 2.2 释放(avio_context_free)
  • 3. AVStream
    • 3.1 初始化(avformat_new_stream)
    • 3.2 释放(ff_free_stream)
  • 4.AVCodecContext
    • 4.1 初始化(avcodec_alloc_context3)
    • 4.2 释放(avcodec_free_context)
  • 5.AVCodec
    • 5.1 h264decoder的初始化(h264_decode_init)
    • 5.2 h264decoder的释放(h264_decode_end)
  • 6.AVFrame
    • 6.1 初始化(av_alloc_frame)
    • 6.2 释放(av_frame_free)
  • 7.AVPacket
    • 7.1 初始化(av_packet_alloc)
    • 7.2 释放(av_packet_free)
  • 8.写在最后的思考

参考:
FFmpeg源代码简单分析:常见结构体的初始化和销毁(AVFormatContext,AVFrame等)

FFmpeg相关记录:

示例工程:
【FFmpeg】调用ffmpeg库实现264软编
【FFmpeg】调用ffmpeg库实现264软解
【FFmpeg】调用ffmpeg库进行RTMP推流和拉流
【FFmpeg】调用ffmpeg库进行SDL2解码后渲染

流程分析:
【FFmpeg】编码链路上主要函数的简单分析
【FFmpeg】解码链路上主要函数的简单分析

结构体分析:
【FFmpeg】AVCodec结构体
【FFmpeg】AVCodecContext结构体
【FFmpeg】AVStream结构体
【FFmpeg】AVFormatContext结构体
【FFmpeg】AVIOContext结构体
【FFmpeg】AVPacket结构体

函数分析:
【通用】
【FFmpeg】avcodec_find_encoder和avcodec_find_decoder

【推流】
【FFmpeg】avformat_open_input函数
【FFmpeg】avformat_find_stream_info函数
【FFmpeg】avformat_alloc_output_context2函数
【FFmpeg】avio_open2函数
【FFmpeg】avformat_write_header函数
【FFmpeg】av_write_frame函数

在记录了一些主要函数的执行流程之后,可以看看几个关键结构体的初始化函数和释放函数,这样对整个FFmpeg更加熟悉一些。需要说明的是,在FFmpeg当中,很多函数都能够实现关键结构体的初始化,举例来说,对于AVFormatContext,可以使用avformat_alloc_output_context2初始化,这个函数会根据输出的格式来初始化AVFormatContext,这样的操作避免了先进行malloc,然后手动赋值的流程。不过本文记录的函数是最基本的函数,尽量避免与其他结构体交织在一起

结构体初始化释放备注
AVFormatContextavformat_alloc_context()avformat_free_context()也可以通过avformat_alloc_output_context2,根据输出的format来进行初始化
AVIOContextavio_alloc_context()avio_context_free()通过avio_open2进行初始化
AVStreamavformat_new_stream()单独释放函数为ff_free_stream,会在avformat_free_context中调用
AVCodecContextavcodec_alloc_context3()avcodec_free_context()
AVCodec->init->close编解码器的初始化操作,会使用函数指针链接,例如ff_h264_decoder,其init为h264_decode_init,close为h264_decode_end
AVFrameav_frame_alloc()
av_frame_get_buffer()
av_frame_free()av_frame_alloc()不会分配AVFrame之中的data,需要使用av_frame_get_buffer()初始化
AVPacketav_packet_alloc()av_packet_free()(1)av_init_packet用于已分配内存的AVPacket结构体的初始化
(2)av_new_packet用于创建新的AVPacket结构体并分配内存
(3)av_packet_alloc创建新的AVPacket结构体的同时,还设置了额外字段

另外,在雷博的记录之中,AVFrame的初始化除了调用av_frame_alloc()还会调用av_image_fill_arrays()进行data buffer的初始化,但是看新版本中的注释,似乎会推荐使用av_frame_get_buffer(),所以本文中记录的是av_frame_get_buffer()。对于AVPacket而言,由于FFmpeg7.0给出的examples\路径下的代码示例中,使用的是av_packet_alloc(),所以本文也重点记录这个函数,并不代表另外两个函数不重要

1.AVFormatContext

1.1 初始化(avformat_alloc_context)

avformat_alloc_context的定义位于libavformat\options.c中,从代码看,函数执行的流程为,调用av_mallocz分配内存空间,随后初始化输入输出的函数,将AVOptions中的字段设置为默认值,分配pkt和parse_pkt的内存。如果pkt和parse_pkt分配失败,则调用avformat_free_context进行释放

/**
 * Allocate an AVFormatContext.
 * avformat_free_context() can be used to free the context and everything
 * allocated by the framework within it.
*/
AVFormatContext *avformat_alloc_context(void)
{
    FFFormatContext *const si = av_mallocz(sizeof(*si));
    AVFormatContext *s;

    if (!si)
        return NULL;

    s = &si->pub;
    s->av_class = &av_format_context_class;
    s->io_open  = io_open_default;
    s->io_close2= io_close2_default;
	// 将所有AVOption字段的值设置为默认值
    av_opt_set_defaults(s);

    si->pkt = av_packet_alloc();
    si->parse_pkt = av_packet_alloc();
    if (!si->pkt || !si->parse_pkt) {
        avformat_free_context(s);
        return NULL;
    }

#if FF_API_LAVF_SHORTEST
    si->shortest_end = AV_NOPTS_VALUE;
#endif

    return s;
}

1.2 释放(avformat_free_context)

函数的定义位于libavformat\avformat.c中,代码中看,会调用deinit进行format的释放,随后调用av_opt_free释放一些options,调用ff_free_stream释放stream,然后是一系列变量的释放。释放时会调用av_freep,av_opt_free和av_dict_free等函数执行,都是将free函数进行不同封装的

/**
 * Free an AVFormatContext and all its streams.
 * @param s context to free
 */
void avformat_free_context(AVFormatContext *s)
{
    FFFormatContext *si;

    if (!s)
        return;
    si = ffformatcontext(s);

    if (s->oformat && ffofmt(s->oformat)->deinit && si->initialized)
        ffofmt(s->oformat)->deinit(s);

    av_opt_free(s);
    if (s->iformat && s->iformat->priv_class && s->priv_data)
        av_opt_free(s->priv_data);
    if (s->oformat && s->oformat->priv_class && s->priv_data)
        av_opt_free(s->priv_data);

    for (unsigned i = 0; i < s->nb_streams; i++)
        ff_free_stream(&s->streams[i]);
    for (unsigned i = 0; i < s->nb_stream_groups; i++)
        ff_free_stream_group(&s->stream_groups[i]);
    s->nb_stream_groups = 0;
    s->nb_streams = 0;

    for (unsigned i = 0; i < s->nb_programs; i++) {
        av_dict_free(&s->programs[i]->metadata);
        av_freep(&s->programs[i]->stream_index);
        av_freep(&s->programs[i]);
    }
    s->nb_programs = 0;

    av_freep(&s->programs);
    av_freep(&s->priv_data);
    while (s->nb_chapters--) {
        av_dict_free(&s->chapters[s->nb_chapters]->metadata);
        av_freep(&s->chapters[s->nb_chapters]);
    }
    av_freep(&s->chapters);
    av_dict_free(&s->metadata);
    av_dict_free(&si->id3v2_meta);
    av_packet_free(&si->pkt);
    av_packet_free(&si->parse_pkt);
    av_freep(&s->streams);
    av_freep(&s->stream_groups);
    ff_flush_packet_queue(s);
    av_freep(&s->url);
    av_free(s);
}

这里的deinit会根据具体情况和具体格式进行,例如FLV格式,会调用flv_deinit()进行释放

const FFOutputFormat ff_flv_muxer = {
    .p.name         = "flv",
    .p.long_name    = NULL_IF_CONFIG_SMALL("FLV (Flash Video)"),
    .p.mime_type    = "video/x-flv",
    .p.extensions   = "flv",
    .priv_data_size = sizeof(FLVContext),
    .p.audio_codec  = CONFIG_LIBMP3LAME ? AV_CODEC_ID_MP3 : AV_CODEC_ID_ADPCM_SWF,
    .p.video_codec  = AV_CODEC_ID_FLV1,
    .init           = flv_init,
    .write_header   = flv_write_header,
    .write_packet   = flv_write_packet,
    .write_trailer  = flv_write_trailer,
    .deinit         = flv_deinit,
    .check_bitstream= flv_check_bitstream,
    .p.codec_tag    = (const AVCodecTag* const []) {
                          flv_video_codec_ids, flv_audio_codec_ids, 0
                      },
    .p.flags        = AVFMT_GLOBALHEADER | AVFMT_VARIABLE_FPS |
                      AVFMT_TS_NONSTRICT,
    .p.priv_class   = &flv_muxer_class,
};

flv_deinit()的定义如下

static void flv_deinit(AVFormatContext *s)
{
    FLVContext *flv = s->priv_data;
    FLVFileposition *filepos = flv->head_filepositions;

    while (filepos) {
        FLVFileposition *next = filepos->next;
        av_free(filepos);
        filepos = next;
    }
    flv->filepositions = flv->head_filepositions = NULL;
    flv->filepositions_count = 0;
}

ff_free_stream函数用于释放AVFormatContext中的stream,定义如下,进行一系列变量的释放

/**
 * Frees a stream without modifying the corresponding AVFormatContext.
 * Must only be called if the latter doesn't matter or if the stream
 * is not yet attached to an AVFormatContext.
 */
// 释放一个流而不修改相应的AVFormatContext
// 必须只调用,如果后者不重要,或者如果流还没有附加到AVFormatContext
void ff_free_stream(AVStream **pst)
{
    AVStream *st = *pst;
    FFStream *const sti = ffstream(st);

    if (!st)
        return;

#if FF_API_AVSTREAM_SIDE_DATA
FF_DISABLE_DEPRECATION_WARNINGS
    for (int i = 0; i < st->nb_side_data; i++)
        av_freep(&st->side_data[i].data);
    av_freep(&st->side_data);
FF_ENABLE_DEPRECATION_WARNINGS
#endif

    if (st->attached_pic.data)
        av_packet_unref(&st->attached_pic);
	// 如果使用了parser,则释放parser
    av_parser_close(sti->parser);
    avcodec_free_context(&sti->avctx);
    av_bsf_free(&sti->bsfc);
    av_freep(&sti->index_entries);
    av_freep(&sti->probe_data.buf);

    av_bsf_free(&sti->extract_extradata.bsf);

    if (sti->info) {
        av_freep(&sti->info->duration_error);
        av_freep(&sti->info);
    }

    av_dict_free(&st->metadata);
    avcodec_parameters_free(&st->codecpar);
    av_freep(&st->priv_data);

    av_freep(pst);
}

2.AVIOContext

2.1 初始化(avio_alloc_context)

函数的定义位于libavformat\aviobuf.c中

/**
 * Allocate and initialize an AVIOContext for buffered I/O. It must be later
 * freed with avio_context_free().
 *
 * @param buffer Memory block for input/output operations via AVIOContext.
 *        The buffer must be allocated with av_malloc() and friends.
 *        It may be freed and replaced with a new buffer by libavformat.
 *        AVIOContext.buffer holds the buffer currently in use,
 *        which must be later freed with av_free().
 * @param buffer_size The buffer size is very important for performance.
 *        For protocols with fixed blocksize it should be set to this blocksize.
 *        For others a typical size is a cache page, e.g. 4kb.
 * @param write_flag Set to 1 if the buffer should be writable, 0 otherwise.
 * @param opaque An opaque pointer to user-specific data.
 * @param read_packet  A function for refilling the buffer, may be NULL.
 *                     For stream protocols, must never return 0 but rather
 *                     a proper AVERROR code.
 * @param write_packet A function for writing the buffer contents, may be NULL.
 *        The function may not change the input buffers content.
 * @param seek A function for seeking to specified byte position, may be NULL.
 *
 * @return Allocated AVIOContext or NULL on failure.
 */
 // 为缓冲I/O分配并初始化AVIOContext。稍后必须使用avio_context_free释放它
 AVIOContext *avio_alloc_context(
                  unsigned char *buffer,
                  int buffer_size,
                  int write_flag,
                  void *opaque,
                  int (*read_packet)(void *opaque, uint8_t *buf, int buf_size),
                  int (*write_packet)(void *opaque, const uint8_t *buf, int buf_size),
                  int64_t (*seek)(void *opaque, int64_t offset, int whence))
{
    FFIOContext *s = av_malloc(sizeof(*s));
    if (!s)
        return NULL;
    ffio_init_context(s, buffer, buffer_size, write_flag, opaque,
                  read_packet, write_packet, seek);
    return &s->pub;
}

从代码看,先调用av_malloc分配内存空间,随后调用ffio_init_context进行初始化,ffio_init_context的定义如下,从代码中看,首先将ctx中的信息配置为0,随后进行一系列变量进行设置

void ffio_init_context(FFIOContext *ctx,
                  unsigned char *buffer,
                  int buffer_size,
                  int write_flag,
                  void *opaque,
                  int (*read_packet)(void *opaque, uint8_t *buf, int buf_size),
                  int (*write_packet)(void *opaque, const uint8_t *buf, int buf_size),
                  int64_t (*seek)(void *opaque, int64_t offset, int whence))
{
    AVIOContext *const s = &ctx->pub;

    memset(ctx, 0, sizeof(*ctx));

    s->buffer      = buffer;
    ctx->orig_buffer_size =
    s->buffer_size = buffer_size;
    s->buf_ptr     = buffer;
    s->buf_ptr_max = buffer;
    s->opaque      = opaque;
    s->direct      = 0;

    url_resetbuf(s, write_flag ? AVIO_FLAG_WRITE : AVIO_FLAG_READ);

    s->write_packet    = write_packet;
    s->read_packet     = read_packet;
    s->seek            = seek;
    s->pos             = 0;
    s->eof_reached     = 0;
    s->error           = 0;
    s->seekable        = seek ? AVIO_SEEKABLE_NORMAL : 0;
    s->min_packet_size = 0;
    s->max_packet_size = 0;
    s->update_checksum = NULL;
    ctx->short_seek_threshold = SHORT_SEEK_THRESHOLD;

    if (!read_packet && !write_flag) {
        s->pos     = buffer_size;
        s->buf_end = s->buffer + buffer_size;
    }
    s->read_pause = NULL;
    s->read_seek  = NULL;

    s->write_data_type       = NULL;
    s->ignore_boundary_point = 0;
    ctx->current_type        = AVIO_DATA_MARKER_UNKNOWN;
    ctx->last_time           = AV_NOPTS_VALUE;
    ctx->short_seek_get      = NULL;
}

2.2 释放(avio_context_free)

avio_context_free中调用av_freep直接释放AVIOContext

/**
 * Free the supplied IO context and everything associated with it.
 *
 * @param s Double pointer to the IO context. This function will write NULL
 * into s.
 */
void avio_context_free(AVIOContext **ps)
{
    av_freep(ps);
}

3. AVStream

3.1 初始化(avformat_new_stream)

函数的定义位于libavformat\options.c中,主要功能是为一个媒体文件创建一条新的流

/**
 * Add a new stream to a media file.
 *
 * When demuxing, it is called by the demuxer in read_header(). If the
 * flag AVFMTCTX_NOHEADER is set in s.ctx_flags, then it may also
 * be called in read_packet().
 *
 * When muxing, should be called by the user before avformat_write_header().
 *
 * User is required to call avformat_free_context() to clean up the allocation
 * by avformat_new_stream().
 *
 * @param s media file handle
 * @param c unused, does nothing
 *
 * @return newly created stream or NULL on error.
 */
// 向媒体文件中添加一条新的流
// 1.当解除时,它由read_header()中的解除器调用。如果在s.ctx_flags中设置了AVFMTCTX_NOHEADER标志
// 		那么它也可以在read_packet()中调用
// 2.在复用时,应在avformat_write_header()之前由用户调用
// 3.用户需要调用avformat_free_context()来清理avformat_new_stream()的分配
AVStream *avformat_new_stream(AVFormatContext *s, const AVCodec *c)
{
    FFFormatContext *const si = ffformatcontext(s);
    FFStream *sti;
    AVStream *st;
    AVStream **streams;
	// 当前流的数量大于了最大的流数量,报错
    if (s->nb_streams >= s->max_streams) {
        av_log(s, AV_LOG_ERROR, "Number of streams exceeds max_streams parameter"
               " (%d), see the documentation if you wish to increase it\n",
               s->max_streams);
        return NULL;
    }
    streams = av_realloc_array(s->streams, s->nb_streams + 1, sizeof(*streams));
    if (!streams)
        return NULL;
    s->streams = streams;
	// 和旧版本FFmpeg不同,这里分配了的是FFStream
	// FFStream是将AVStream封装起来的FFmpeg的内部结构体
    sti = av_mallocz(sizeof(*sti));
    if (!sti)
        return NULL;
    st = &sti->pub;

    st->av_class = &stream_class;
    // 分配codecpar的内存
    st->codecpar = avcodec_parameters_alloc();
    if (!st->codecpar)
        goto fail;

    sti->fmtctx = s;

    if (s->iformat) {
        sti->avctx = avcodec_alloc_context3(NULL);
        if (!sti->avctx)
            goto fail;

        sti->info = av_mallocz(sizeof(*sti->info));
        if (!sti->info)
            goto fail;

#if FF_API_R_FRAME_RATE
        sti->info->last_dts      = AV_NOPTS_VALUE;
#endif
        sti->info->fps_first_dts = AV_NOPTS_VALUE;
        sti->info->fps_last_dts  = AV_NOPTS_VALUE;

        /* default pts setting is MPEG-like */
        avpriv_set_pts_info(st, 33, 1, 90000);
        /* we set the current DTS to 0 so that formats without any timestamps
         * but durations get some timestamps, formats with some unknown
         * timestamps have their first few packets buffered and the
         * timestamps corrected before they are returned to the user */
        sti->cur_dts = RELATIVE_TS_BASE;
    } else {
        sti->cur_dts = AV_NOPTS_VALUE;
    }
	// 设置一些流的信息
    st->index      = s->nb_streams;
    st->start_time = AV_NOPTS_VALUE;
    st->duration   = AV_NOPTS_VALUE;
    sti->first_dts     = AV_NOPTS_VALUE;
    sti->probe_packets = s->max_probe_packets;
    sti->pts_wrap_reference = AV_NOPTS_VALUE;
    sti->pts_wrap_behavior  = AV_PTS_WRAP_IGNORE;

    sti->last_IP_pts = AV_NOPTS_VALUE;
    sti->last_dts_for_order_check = AV_NOPTS_VALUE;
    for (int i = 0; i < MAX_REORDER_DELAY + 1; i++)
        sti->pts_buffer[i] = AV_NOPTS_VALUE;

    st->sample_aspect_ratio = (AVRational) { 0, 1 };
    sti->transferred_mux_tb = (AVRational) { 0, 1 };;

#if FF_API_AVSTREAM_SIDE_DATA
    sti->inject_global_side_data = si->inject_global_side_data;
#endif

    sti->need_context_update = 1;

    s->streams[s->nb_streams++] = st;
    return st;
fail:
    ff_free_stream(&st); // 失败则释放流
    return NULL;
}

从代码中看,先调用av_mallocz分配新的FFStream,然后进行一系列变量的初始化。如果inputFormat已经被设置,则会调用avcodec_alloc_context3()初始化FFStream中的AVCodecContext

3.2 释放(ff_free_stream)

前面已记录

4.AVCodecContext

4.1 初始化(avcodec_alloc_context3)

函数的定义位于libavcodec\options.c中,功能是分配AVCodecContext的内存,并且将其中的值配置成为默认值

/**
 * Allocate an AVCodecContext and set its fields to default values. The
 * resulting struct should be freed with avcodec_free_context().
 *
 * @param codec if non-NULL, allocate private data and initialize defaults
 *              for the given codec. It is illegal to then call avcodec_open2()
 *              with a different codec.
 *              If NULL, then the codec-specific defaults won't be initialized,
 *              which may result in suboptimal default settings (this is
 *              important mainly for encoders, e.g. libx264).
 *
 * @return An AVCodecContext filled with default values or NULL on failure.
*/
// 分配AVCodecContext并将其字段设置为默认值。生成的结构体应该使用avcodec_free_context()释放
AVCodecContext *avcodec_alloc_context3(const AVCodec *codec)
{
    AVCodecContext *avctx= av_malloc(sizeof(AVCodecContext));

    if (!avctx)
        return NULL;

    if (init_context_defaults(avctx, codec) < 0) {
        av_free(avctx);
        return NULL;
    }

    return avctx;
}

代码中看,先调用av_malloc分配空间,随后调用init_context_defaults进行AVCodecContext中变量的初始化,该函数定义位于libavformat\options.c中

static int init_context_defaults(AVCodecContext *s, const AVCodec *codec)
{
    const FFCodec *const codec2 = ffcodec(codec);
    int flags=0;
    // 将AVCodecContext中的内容全部设置为0
    memset(s, 0, sizeof(AVCodecContext));

    s->av_class = &av_codec_context_class;

    s->codec_type = codec ? codec->type : AVMEDIA_TYPE_UNKNOWN;
    if (codec) {
        s->codec = codec;
        s->codec_id = codec->id;
    }

    if(s->codec_type == AVMEDIA_TYPE_AUDIO)
        flags= AV_OPT_FLAG_AUDIO_PARAM;
    else if(s->codec_type == AVMEDIA_TYPE_VIDEO)
        flags= AV_OPT_FLAG_VIDEO_PARAM;
    else if(s->codec_type == AVMEDIA_TYPE_SUBTITLE)
        flags= AV_OPT_FLAG_SUBTITLE_PARAM;
    // 将flgas中信息配置给s中
    av_opt_set_defaults2(s, flags, flags);
	// 释放通道布局中任何已分配的数据,并将通道计数重置为0
    av_channel_layout_uninit(&s->ch_layout);

    s->time_base           = (AVRational){0,1};
    s->framerate           = (AVRational){ 0, 1 };
    s->pkt_timebase        = (AVRational){ 0, 1 };
    // 通用函数,适用于任何类型的编码器缓冲区
    s->get_buffer2         = avcodec_default_get_buffer2;
    s->get_format          = avcodec_default_get_format;
    // 专门用于获取视频编码器的输出缓冲区
    s->get_encode_buffer   = avcodec_default_get_encode_buffer;
    s->execute             = avcodec_default_execute;
    s->execute2            = avcodec_default_execute2;
    // 音频的采样宽高比
    s->sample_aspect_ratio = (AVRational){0,1};
    // 音频声道顺序
    s->ch_layout.order     = AV_CHANNEL_ORDER_UNSPEC;
    s->pix_fmt             = AV_PIX_FMT_NONE;
    // 软编pix fmt
    s->sw_pix_fmt          = AV_PIX_FMT_NONE;
    s->sample_fmt          = AV_SAMPLE_FMT_NONE;

    if(codec && codec2->priv_data_size){
        s->priv_data = av_mallocz(codec2->priv_data_size);
        if (!s->priv_data)
            return AVERROR(ENOMEM);
        if(codec->priv_class){
            *(const AVClass**)s->priv_data = codec->priv_class;
            av_opt_set_defaults(s->priv_data);
        }
    }
    if (codec && codec2->defaults) {
        int ret;
        const FFCodecDefault *d = codec2->defaults;
        while (d->key) {
            ret = av_opt_set(s, d->key, d->value, 0);
            av_assert0(ret >= 0);
            d++;
        }
    }
    return 0;
}

4.2 释放(avcodec_free_context)

/**
 * Free the codec context and everything associated with it and write NULL to
 * the provided pointer.
*/
// 释放编解码器上下文和与之相关的所有内容,并将NULL写入所提供的指针
void avcodec_free_context(AVCodecContext **pavctx)
{
    AVCodecContext *avctx = *pavctx;

    if (!avctx)
        return;
	// 释放AVCodecContext中的codec
    ff_codec_close(avctx);

    av_freep(&avctx->extradata);
    av_freep(&avctx->subtitle_header);
    av_freep(&avctx->intra_matrix);
    av_freep(&avctx->inter_matrix);
    av_freep(&avctx->rc_override);
    av_channel_layout_uninit(&avctx->ch_layout);
    av_frame_side_data_free(
        &avctx->decoded_side_data, &avctx->nb_decoded_side_data);

    av_freep(pavctx);
}

从代码中看,在进行AVCodecContext释放时,会同时将其中的AVCodec一起释放掉,调用的函数是ff_codec_close,随后进行一系列变量的释放

5.AVCodec

AVCodec的初始化过程在avcodec_open2中进行,函数位于libavcodec\avcodec.c中

int attribute_align_arg avcodec_open2(AVCodecContext *avctx, const AVCodec *codec, 
									  AVDictionary **options)
{
    // ... 
    if (!(avctx->active_thread_type & FF_THREAD_FRAME) ||
        avci->frame_thread_encoder) {
        if (codec2->init) {
            lock_avcodec(codec2);
            // 调用init进行初始化
            ret = codec2->init(avctx);
            unlock_avcodec(codec2);
            if (ret < 0) {
                avci->needs_close = codec2->caps_internal & FF_CODEC_CAP_INIT_CLEANUP;
                goto free_and_end;
            }
        }
        avci->needs_close = 1;
    }
	// ...
}

释放函数的调用位置在ff_codec_close进行,函数位于libavcodec\avcodec.中

av_cold void ff_codec_close(AVCodecContext *avctx)
{
    int i;

    if (!avctx)
        return;

    if (avcodec_is_open(avctx)) {
        AVCodecInternal *avci = avctx->internal;

        if (CONFIG_FRAME_THREAD_ENCODER &&
            avci->frame_thread_encoder && avctx->thread_count > 1) {
            ff_frame_thread_encoder_free(avctx);
        }
        if (HAVE_THREADS && avci->thread_ctx)
            ff_thread_free(avctx);
        if (avci->needs_close && ffcodec(avctx->codec)->close)
            ffcodec(avctx->codec)->close(avctx);
        // ...
}

假设现在进行的是264格式的解码过程,如下所示

const FFCodec ff_h264_decoder = {
    .p.name                = "h264",
    CODEC_LONG_NAME("H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10"),
    .p.type                = AVMEDIA_TYPE_VIDEO,
    .p.id                  = AV_CODEC_ID_H264,
    .priv_data_size        = sizeof(H264Context),
    .init                  = h264_decode_init,
    .close                 = h264_decode_end,
    FF_CODEC_DECODE_CB(h264_decode_frame),
    .p.capabilities        = AV_CODEC_CAP_DR1 |
                             AV_CODEC_CAP_DELAY | AV_CODEC_CAP_SLICE_THREADS |
                             AV_CODEC_CAP_FRAME_THREADS,
    .hw_configs            = (const AVCodecHWConfigInternal *const []) {
						   // ...
                               NULL
                           },
    .caps_internal         = FF_CODEC_CAP_EXPORTS_CROPPING |
                             FF_CODEC_CAP_ALLOCATE_PROGRESS | FF_CODEC_CAP_INIT_CLEANUP,
    .flush                 = h264_decode_flush,
    UPDATE_THREAD_CONTEXT(ff_h264_update_thread_context),
    UPDATE_THREAD_CONTEXT_FOR_USER(ff_h264_update_thread_context_for_user),
    .p.profiles            = NULL_IF_CONFIG_SMALL(ff_h264_profiles),
    .p.priv_class          = &h264_class,
};

进行初始化时,使用h264_decode_init()进行初始化;进行释放时,使用h264_decode_end()进行释放

5.1 h264decoder的初始化(h264_decode_init)

h264_decode_init的定义位于libavcodec\h264.c中,如下所示

static av_cold int h264_decode_init(AVCodecContext *avctx)
{
    H264Context *h = avctx->priv_data;
    int ret;
	// h264初始化上下文
    ret = h264_init_context(avctx, h);
    if (ret < 0)
        return ret;
	// ff_h264_decode_init_vlc主要作用是为H264视频解码器准备必要的VLC表
	// VLC表被用来解析比特流中的编码数据,将其转换为可识别的语法元素和预测模式
    ret = ff_thread_once(&h264_vlc_init, ff_h264_decode_init_vlc);
    if (ret != 0) {
        av_log(avctx, AV_LOG_ERROR, "pthread_once has failed.");
        return AVERROR_UNKNOWN;
    }

#if FF_API_TICKS_PER_FRAME
FF_DISABLE_DEPRECATION_WARNINGS
    avctx->ticks_per_frame = 2;
FF_ENABLE_DEPRECATION_WARNINGS
#endif

    if (!avctx->internal->is_copy) {
        if (avctx->extradata_size > 0 && avctx->extradata) {
            ret = ff_h264_decode_extradata(avctx->extradata, avctx->extradata_size,
                                           &h->ps, &h->is_avc, &h->nal_length_size,
                                           avctx->err_recognition, avctx);
           if (ret < 0) {
               int explode = avctx->err_recognition & AV_EF_EXPLODE;
               av_log(avctx, explode ? AV_LOG_ERROR: AV_LOG_WARNING,
                      "Error decoding the extradata\n");
               if (explode) {
                   return ret;
               }
               ret = 0;
           }
        }
    }

    if (h->ps.sps && h->ps.sps->bitstream_restriction_flag &&
        h->avctx->has_b_frames < h->ps.sps->num_reorder_frames) {
        h->avctx->has_b_frames = h->ps.sps->num_reorder_frames;
    }

    ff_h264_flush_change(h);

    if (h->enable_er < 0 && (avctx->active_thread_type & FF_THREAD_SLICE))
        h->enable_er = 0;

    if (h->enable_er && (avctx->active_thread_type & FF_THREAD_SLICE)) {
        av_log(avctx, AV_LOG_WARNING,
               "Error resilience with slice threads is enabled. It is unsafe and unsupported and may crash. "
               "Use it at your own risk\n");
    }

    return 0;
}

从代码中看,主要内容是先调用h264_init_context()进行初始化,然后使用ff_h264_decode_init_vlc()进行VLC表初始化。h264_init_context的定义如下,进行了一系列变量的初始化,随后是slice_ctx初始化和pic的初始化

static int h264_init_context(AVCodecContext *avctx, H264Context *h)
{
    int i, ret;

    h->avctx                 = avctx;
    h->cur_chroma_format_idc = -1;

    h->width_from_caller     = avctx->width;
    h->height_from_caller    = avctx->height;

    h->workaround_bugs       = avctx->workaround_bugs;
    h->flags                 = avctx->flags;
    h->poc.prev_poc_msb      = 1 << 16;
    h->recovery_frame        = -1;
    h->frame_recovered       = 0;
    h->poc.prev_frame_num    = -1;
    h->sei.common.frame_packing.arrangement_cancel_flag = -1;
    h->sei.common.unregistered.x264_build = -1;

    h->next_outputed_poc = INT_MIN;
    for (i = 0; i < FF_ARRAY_ELEMS(h->last_pocs); i++)
        h->last_pocs[i] = INT_MIN;

    ff_h264_sei_uninit(&h->sei);

    if (avctx->active_thread_type & FF_THREAD_FRAME) {
        h->decode_error_flags_pool = ff_refstruct_pool_alloc(sizeof(atomic_int), 0);
        if (!h->decode_error_flags_pool)
            return AVERROR(ENOMEM);
    }

    h->nb_slice_ctx = (avctx->active_thread_type & FF_THREAD_SLICE) ? avctx->thread_count : 1;
    h->slice_ctx = av_calloc(h->nb_slice_ctx, sizeof(*h->slice_ctx));
    if (!h->slice_ctx) {
        h->nb_slice_ctx = 0;
        return AVERROR(ENOMEM);
    }
	// 初始化dpb
    for (i = 0; i < H264_MAX_PICTURE_COUNT; i++) {
        if ((ret = h264_init_pic(&h->DPB[i])) < 0)
            return ret;
    }
	// 初始化pic
    if ((ret = h264_init_pic(&h->cur_pic)) < 0)
        return ret;

    if ((ret = h264_init_pic(&h->last_pic_for_ec)) < 0)
        return ret;

    for (i = 0; i < h->nb_slice_ctx; i++)
        h->slice_ctx[i].h264 = h;

    return 0;
}

5.2 h264decoder的释放(h264_decode_end)

函数进行一系列变量的释放

static av_cold int h264_decode_end(AVCodecContext *avctx)
{
    H264Context *h = avctx->priv_data;
    int i;

    ff_h264_remove_all_refs(h);
    ff_h264_free_tables(h);
	// 释放dbp
    for (i = 0; i < H264_MAX_PICTURE_COUNT; i++) {
        h264_free_pic(h, &h->DPB[i]);
    }
    memset(h->delayed_pic, 0, sizeof(h->delayed_pic));

    h->cur_pic_ptr = NULL;

    ff_refstruct_pool_uninit(&h->decode_error_flags_pool);

    av_freep(&h->slice_ctx);
    h->nb_slice_ctx = 0;

    ff_h264_sei_uninit(&h->sei);
    ff_h264_ps_uninit(&h->ps);

    ff_h2645_packet_uninit(&h->pkt);

    h264_free_pic(h, &h->cur_pic);
    h264_free_pic(h, &h->last_pic_for_ec);

    return 0;
}

6.AVFrame

6.1 初始化(av_alloc_frame)

函数的定义位于libavutil\frame.c中,主要功能是分配AVFrame内存空间,并且将其设置为默认值

/**
 * Allocate an AVFrame and set its fields to default values.  The resulting
 * struct must be freed using av_frame_free().
 *
 * @return An AVFrame filled with default values or NULL on failure.
 *
 * @note this only allocates the AVFrame itself, not the data buffers. Those
 * must be allocated through other means, e.g. with av_frame_get_buffer() or
 * manually.
*/
// 分配一个AVFrame并将其字段设置为默认值。必须使用av_frame_free()释放生成的结构体
// @note: 这里需要注意的是av_frame_alloc仅分配AVFrame本身的空间,但不包括data buffer,需要通过
// 			其他的方式进行分配,例如av_frame_get_buffer
AVFrame *av_frame_alloc(void)
{
    AVFrame *frame = av_malloc(sizeof(*frame));

    if (!frame)
        return NULL;

    get_frame_defaults(frame);

    return frame;
}

函数中先调用av_malloc分配空间,随后使用get_frame_defaults进行frame的初始化,如下所示

static void get_frame_defaults(AVFrame *frame)
{
    memset(frame, 0, sizeof(*frame));

    frame->pts                   =
    frame->pkt_dts               = AV_NOPTS_VALUE;
    frame->best_effort_timestamp = AV_NOPTS_VALUE;
    frame->duration            = 0;
#if FF_API_FRAME_PKT
FF_DISABLE_DEPRECATION_WARNINGS
    frame->pkt_pos             = -1;
    frame->pkt_size            = -1;
FF_ENABLE_DEPRECATION_WARNINGS
#endif
    frame->time_base           = (AVRational){ 0, 1 };
    frame->sample_aspect_ratio = (AVRational){ 0, 1 };
    frame->format              = -1; /* unknown */
    frame->extended_data       = frame->data;
    frame->color_primaries     = AVCOL_PRI_UNSPECIFIED;
    frame->color_trc           = AVCOL_TRC_UNSPECIFIED;
    frame->colorspace          = AVCOL_SPC_UNSPECIFIED;
    frame->color_range         = AVCOL_RANGE_UNSPECIFIED;
    frame->chroma_location     = AVCHROMA_LOC_UNSPECIFIED;
    frame->flags               = 0;
}

从代码中看,av_frame_alloc()会进行一些变量的配置,但不会为其中的data buffer分配内存,需要使用其他函数进行分配,例如av_frame_get_buffer(),函数的定义位于libavutil\frame.c中

int av_frame_get_buffer(AVFrame *frame, int align)
{
    if (frame->format < 0)
        return AVERROR(EINVAL);
	// 获取video的buffer
    if (frame->width > 0 && frame->height > 0) 
        return get_video_buffer(frame, align);
    else if (frame->nb_samples > 0 &&
             (av_channel_layout_check(&frame->ch_layout)))
        return get_audio_buffer(frame, align);

    return AVERROR(EINVAL);
}

get_video_buffer的定义如下

static int get_video_buffer(AVFrame *frame, int align)
{
    const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(frame->format); // 获取pix fmt
    int ret, padded_height, total_size;
    int plane_padding = FFMAX(16 + 16/*STRIDE_ALIGN*/, align);
    ptrdiff_t linesizes[4];
    size_t sizes[4];

    if (!desc)
        return AVERROR(EINVAL);
	// 检查size
    if ((ret = av_image_check_size(frame->width, frame->height, 0, NULL)) < 0)
        return ret;

    if (!frame->linesize[0]) {
        if (align <= 0)
            align = 32; /* STRIDE_ALIGN. Should be av_cpu_max_align() */

        for (int i = 1; i <= align; i += i) {
        	// 填充plane的linesizes
            ret = av_image_fill_linesizes(frame->linesize, frame->format,
                                          FFALIGN(frame->width, i));
            if (ret < 0)
                return ret;
            if (!(frame->linesize[0] & (align-1)))
                break;
        }

        for (int i = 0; i < 4 && frame->linesize[i]; i++)
            frame->linesize[i] = FFALIGN(frame->linesize[i], align);
    }

    for (int i = 0; i < 4; i++)
        linesizes[i] = frame->linesize[i];

    padded_height = FFALIGN(frame->height, 32);
    if ((ret = av_image_fill_plane_sizes(sizes, frame->format,
                                         padded_height, linesizes)) < 0)
        return ret;

    total_size = 4*plane_padding;
    for (int i = 0; i < 4; i++) {
        if (sizes[i] > INT_MAX - total_size)
            return AVERROR(EINVAL);
        total_size += sizes[i];
    }
	// 根据前面计算的size来为buffer分配内存
    frame->buf[0] = av_buffer_alloc(total_size);
    if (!frame->buf[0]) {
        ret = AVERROR(ENOMEM);
        goto fail;
    }

    if ((ret = av_image_fill_pointers(frame->data, frame->format, padded_height,
                                      frame->buf[0]->data, frame->linesize)) < 0)
        goto fail;

    for (int i = 1; i < 4; i++) {
        if (frame->data[i])
            frame->data[i] += i * plane_padding;
    }

    frame->extended_data = frame->data;

    return 0;
fail:
    av_frame_unref(frame);
    return ret;
}

6.2 释放(av_frame_free)

/**
 * Free the frame and any dynamically allocated objects in it,
 * e.g. extended_data. If the frame is reference counted, it will be
 * unreferenced first.
 *
 * @param frame frame to be freed. The pointer will be set to NULL.
 */
void av_frame_free(AVFrame **frame)
{
    if (!frame || !*frame)
        return;

    av_frame_unref(*frame);
    av_freep(frame);
}

7.AVPacket

7.1 初始化(av_packet_alloc)

函数的作用是分配一个AVPacket的空间,并且初始化,最后释放时必须使用av_packet_free进行。如果分配空间失败,则返回NULL。需要注意的是,这里只会分配AVPacket本身而不会申请data buffer的空间,这些信息通过其他方式分配,例如在调用avcodec_receive_packet时,将已经解码的packet取出来,此时存储在data buffer之中

/**
 * Allocate an AVPacket and set its fields to default values.  The resulting
 * struct must be freed using av_packet_free().
 *
 * @return An AVPacket filled with default values or NULL on failure.
 *
 * @note this only allocates the AVPacket itself, not the data buffers. Those
 * must be allocated through other means such as av_new_packet.
 *
 * @see av_new_packet
 */
AVPacket *av_packet_alloc(void);

函数的定义位于libavcodec\avpkt.c中,先调用av_malloc申请分配空间,随后调用get_packet_defaults进行初始化

AVPacket *av_packet_alloc(void)
{
    AVPacket *pkt = av_malloc(sizeof(AVPacket));
    if (!pkt)
        return pkt;

    get_packet_defaults(pkt);

    return pkt;
}

get_packet_defaults的定义如下,初始化了几个最重要的信息,包括显示时间戳(play timestamp),解码时间戳(decode timestamp),位置pos和时间基time_base

static void get_packet_defaults(AVPacket *pkt)
{
    memset(pkt, 0, sizeof(*pkt));

    pkt->pts             = AV_NOPTS_VALUE;
    pkt->dts             = AV_NOPTS_VALUE;
    pkt->pos             = -1;
    pkt->time_base       = av_make_q(0, 1);
}

如果使用av_new_packet,会进行buf的内存分配,其中av_new_packet的定义如下,与av_packet_alloc的区别在于会分配buf的内存

/**
 * Allocate the payload of a packet and initialize its fields with
 * default values.
 *
 * @param pkt packet
 * @param size wanted payload size
 * @return 0 if OK, AVERROR_xxx otherwise
 */
int av_new_packet(AVPacket *pkt, int size)
{
    AVBufferRef *buf = NULL;
    int ret = packet_alloc(&buf, size);
    if (ret < 0)
        return ret;

    get_packet_defaults(pkt);
    pkt->buf      = buf;
    pkt->data     = buf->data;
    pkt->size     = size;

    return 0;
}

7.2 释放(av_packet_free)

/**
 * Free the packet, if the packet is reference counted, it will be
 * unreferenced first.
 *
 * @param pkt packet to be freed. The pointer will be set to NULL.
 * @note passing NULL is a no-op.
*/
void av_packet_free(AVPacket **pkt)
{
    if (!pkt || !*pkt)
        return;

    av_packet_unref(*pkt);
    av_freep(pkt);
}

8.写在最后的思考

不得不感慨FFmpeg随着发展,结构体和函数变得愈发的复杂,雷博记录的文章和新版本FFmpeg在大体上一致,但在细节部分还是有一些出入。有一些问题值得思考:
(1)结构体的初始化,是依靠自身进行初始化,还是结合别的信息进行初始化,怎么选择比较合适
(2)初始化时,有哪些变量没有完全初始化,需要在别的地方初始化,或者是手工初始化,或者是在别的函数中初始化,这其中的关联需要梳理
(3)在释放时,为了一些操作的简化和封装,有些结构体并没有展示出来其释放的过程,但是必须得熟悉这个释放的过程,否则容易出现内存泄漏的问题,这在音视频软件使用时是严重的问题,因为尝尝会使用视频软件进行长时间的播放,这样有可能导致系统整体卡顿,发热的问题

CSDN : https://blog.csdn.net/weixin_42877471
Github : https://github.com/DoFulangChen

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/769042.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

[leetcode hot 150]第三题,无重复字符的最长子串

题目&#xff1a; 给定一个字符串 s &#xff0c;请你找出其中不含有重复字符的 最长 子串的长度。 可以使用"滑动窗口"的方法来解决这个问题。基本思路如下: 使用两个指针(start和end)来定义一个窗口移动end指针来扩大窗口,直到遇到重复字符如果遇到重复字符,移动s…

Spring源码九:BeanFactoryPostProcessor

上一篇Spring源码八&#xff1a;容器扩展一&#xff0c;我们看到ApplicationContext容器通过refresh方法中的prepareBeanFactory方法对BeanFactory扩展的一些功能点&#xff0c;包括对SPEL语句的支持、添加属性编辑器的注册器扩展解决Bean属性只能定义基础变量的问题、以及一些…

每周题解:最大半连通子图

题目链接 最大半连通子图 题目描述 一个有向图 G ( V , E ) G\left(V,E\right) G(V,E) 称为半连通的 (Semi-Connected)&#xff0c;如果满足&#xff1a; ∀ u , v ∈ V \forall u,v\in V ∀u,v∈V&#xff0c;满足 u → v u\to v u→v 或 v → u v\to u v→u&#xff0…

Go语言实现钉钉机器人接入Dify工作流

go语言实现实现钉钉机器人接入dify工作流&#xff0c;完成ai 流式问答 代码地址 有用的话点个star github地址 效果 配置使用 修改.env_template文件 为.env 设置.env文件内的环境变量 API_KEY: dify的api_keyAPI_URL: dify 的api接口CLIENT_ID : 钉钉机器人应用的idCLIENT…

基于Java的家政预约系统设计与实现

作者介绍&#xff1a;计算机专业研究生&#xff0c;现企业打工人&#xff0c;从事Java全栈开发 主要内容&#xff1a;技术学习笔记、Java实战项目、项目问题解决记录、AI、简历模板、简历指导、技术交流、论文交流&#xff08;SCI论文两篇&#xff09; 上点关注下点赞 生活越过…

Docker-compose 实现Prometheus+Grafana监控MySQL及Linux主机

. ├── Grafana │ ├── data │ └── docker-compose.yaml ├── Mysql │ ├── conf │ ├── data │ ├── docker-compose.yaml │ └── logs ├── Mysqld_exporter │ ├── conf │ └── docker-compose.yaml ├── node-exporter │…

RPA 第一课

RPA 是 Robotic Process Automation 的简称&#xff0c;意思是「机器人流程自动化」。 顾名思义&#xff0c;它是一种以机器人&#xff08;软件&#xff09;来替代人&#xff0c;实现重复工作自动化的工具。 首先要说一句&#xff0c;RPA 不是 ChatGPT 出来之后的产物&#x…

推荐三款常用接口测试工具!

接口测试是软件开发中至关重要的一环&#xff0c;通过对应用程序接口进行测试&#xff0c;可以验证其功能、性能和稳定性。随着互联网和移动应用的快速发展&#xff0c;接口测试变得越来越重要。为了提高测试效率和质量&#xff0c;开发人员和测试人员需要使用专业的接口测试工…

自然语言处理学习(2)基本知识 文本预处理+文本数据分析+文本增强

conda activate DL conda deactivate课程链接 一 一些包的安装 1 stanfordcorenlp 在anoconda prompt 里面&#xff1a;进入自己的conda环境&#xff0c;pip install stanfordcorenlp 进入方式 相关包下载&#xff0c;Jar包我没有下载下来&#xff0c;太慢了&#xff0c;这个…

提高Python爬虫的匿名性:代理ip的配置策略

在数字化时代的今天&#xff0c;网络数据采集已成为获取信息的重要手段&#xff0c;尤其在竞争激烈的商业环境中。Python作为一种强大的编程语言&#xff0c;广泛应用于开发各种数据爬虫来自动化地抓取网络信息。然而&#xff0c;随着网站安全意识的提高&#xff0c;越来越多的…

牛客小白月赛97

A.三角形 判断等边三角形&#xff0c;题不难&#xff0c;代码如下&#xff1a; #include <iostream>using namespace std;int a[110];int main() {int n;cin >> n;int x;int mx 0;for(int i 1; i < n; i){cin >> x;mx max(mx, x);a[x];}for(int i 1…

Java OnVif应用PTZ控制

研究OnVif在Java程序中应用&#xff0c;在此作记录&#xff0c;onvif-java-lib/release at master milg0/onvif-java-lib GitHub&#xff0c;在此连接中下载jar&#xff0c;并在项目中引用&#xff0c;该jar封装很好&#xff0c;可以方便快速完成功能 1.登录OnVif 2.PTZ控制…

【大数据】—美国交通事故分析(2016 年 2 月至 2020 年 12 月)

引言 在当今快速发展的数字时代&#xff0c;大数据已成为我们理解世界、做出决策的重要工具。特别是在交通安全领域&#xff0c;大数据分析能够揭示事故模式、识别风险因素&#xff0c;并帮助制定预防措施&#xff0c;从而挽救生命。本文将深入探讨2016年2月至2020年12月期间&…

反射(通俗易懂)

一、反射(Reflection) 反射就是:加载类&#xff0c;并允许以编程的方式解剖类中的各种成分(成员变量、方法、构造器等) 动态语言&#xff0c;是一类在运行时可以改变其结构的语言&#xff1a;例如新的函数、对象、甚至代码可以被引进&#xff0c;已有的函数可以被删除或是其他…

强化学习的数学原理:值迭代与策略迭代

概述 从课程地图上可以看出来&#xff0c;这是本门课程中第一次正式的介绍强化学习的算法&#xff0c;并且是一个 model-based 的算法&#xff0c;而在下一节课将会介绍第一个 model-free 的算法&#xff08;在 chapter 5&#xff09;。而这两节和之前所学的 BOE 是密切相关的&…

笔记-python爬虫概述

目录 常用第三方库 爬虫框架 动态页面渲染1. url请求分析2. selenium3. phantomjs4. splash5. spynner 爬虫防屏蔽策略1. 修改User-Agent2. 禁止cookies3. 设置请求时间间隔4. 代理IP池5. 使用Selenium6. 破解验证码常用第三方库 对于爬虫初学者&#xff0c;建议在了解爬虫原…

DEX: Scalable Range Indexing on Disaggregated Memory——论文泛读

arXiv Paper 论文阅读笔记整理 问题 内存优化索引[2&#xff0c;3&#xff0c;18&#xff0c;27&#xff0c;42]对于加速OLTP至关重要&#xff0c;但随着数据大小&#xff08;以及索引大小&#xff09;的增长&#xff0c;对内存容量的需求可能会超过单个服务器所能提供的容量…

基于ADRC自抗扰算法的UAV飞行姿态控制系统simulink建模与仿真

目录 1.课题概述 2.系统仿真结果 3.核心程序与模型 4.系统原理简介 4.1 控制系统概述 4.2 ADRC基本框架 4.3 控制律设计 5.完整工程文件 1.课题概述 基于ADRC自抗扰算法的UAV飞行姿态控制系统simulink建模与仿真&#xff0c;分别对YAW&#xff0c;PITCH&#xff0c;ROL…

golang写的自动更新器

文件自动更新器&#xff0c;这个很多端游和软件都有用到的。 golang的rpc通信&#xff0c;是非常好用的一个东西&#xff0c;可以跟调用本地函数一样&#xff0c;调用远程服务端的函数&#xff0c;直接从远程服务端上拉取数据下来&#xff0c;简单便捷。 唯一的遗憾就是&#x…

互联网盲盒小程序的市场发展前景如何?

近几年来&#xff0c;盲盒成为了大众热衷的消费市场。盲盒是一个具有随机性和惊喜感&#xff0c;它能够激发消费者的好奇心&#xff0c;在拆盲盒的过程中给消费者带来巨大的愉悦感&#xff0c;在各种的吸引力下&#xff0c;消费者也愿意为各类盲盒买单。如今&#xff0c;随着盲…