资讯详情

音视频FLV合成实战

FFmpeg合成流程

例如,本程序将合成频率和视频流,并将其编码和包装输出到输出部分。输出格式是基于部分 扩展名动猜测。 示例流程图如下所示。 在这里插入图片描述

ffmpeg 的 Mux 主要分为 三步操作:

  1. avformat_write_header : 写?件头
  2. av_write_frame/av_interleaved_write_frame: 写packet
  3. av_write_trailer : 写?件尾

将AVCodecContext码流参数在结构体中复制到AVCodecParameters结构体,和avcodec_parameters_to_context刚好相反。

FFmpeg函数:avformat_write_header

int avformat_write_header(AVFormatContext *s, AVDictionary **options) { 
             int ret = 0;     int already_initialized = s->internal->initialized;     int streams_already_initialized = s->internal->streams_initialized;      if (!already_initialized)         if ((ret = avformat_init_output(s, options)) < 0)             return ret;      if (!(s->oformat->flags & AVFMT_NOFILE) && s->pb)         avio_write_marker(s->pb, AV_NOPTS_VALUE, AVIO_DATA_MARKER_HEADER);     if (s->oformat->write_header) { 
                 ret = s->oformat->write_header(s);         if (ret >= 0 && s->pb && s->pb->error < 0)             ret = s->pb->error; if (ret < 0) goto fail; flush_if_needed(s); } if (!(s->oformat->flags & AVFMT_NOFILE) && s->pb) avio_write_marker(s->pb, AV_NOPTS_VALUE, AVIO_DATA_MARKER_UNKNOWN); if (!s->internal->streams_initialized) { 
          if ((ret = init_pts(s)) < 0) goto fail; } return streams_already_initialized; fail: if (s->oformat->deinit) s->oformat->deinit(s); return ret; } 

最终调⽤到复⽤器的 write_header,⽐如

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

FFmpeg结构体:avformat_alloc_output_context2

函数在在libavformat.h⾥⾯的定义

/** * Allocate an AVFormatContext for an output format. * avformat_free_context() can be used to free the context and * everything allocated by the framework within it. * * @param *ctx is set to the created format context, or to NULL in * case of failure * @param oformat format to use for allocating the context, if NULL * format_name and filename are used instead * @param format_name the name of output format to use for allocating the * context, if NULL filename is used instead * @param filename the name of the filename to use for allocating the * context, may be NULL * @return >= 0 in case of success, a negative AVERROR code in case of * failure */
int avformat_alloc_output_context2(AVFormatContext **ctx, ff_const59 AVOutputFormat *oformat,
                                   const char *format_name, const char *filename);

函数参数的介绍:

  • ctx:需要创建的context,返回NULL表示失败。
  • oformat:指定对应的AVOutputFormat,如果不指定,可以通过后⾯format_name、filename两个参 数进⾏指定,让ffmpeg⾃⼰推断。
  • format_name: 指定⾳视频的格式,⽐如“flv”,“mpeg”等,如果设置为NULL,则由filename进⾏指 定,让ffmpeg⾃⼰推断。
  • filename: 指定⾳视频⽂件的路径,如果oformat、format_name为NULL,则ffmpeg内部根据 filename后缀名选择合适的复⽤器,⽐如xxx.flv则使⽤flv复⽤器。
int avformat_alloc_output_context2(AVFormatContext **avctx, ff_const59 AVOutputFormat *oformat,
                                   const char *format, const char *filename)
{ 
        
    AVFormatContext *s = avformat_alloc_context();
    int ret = 0;

    *avctx = NULL;
    if (!s)
        goto nomem;

    if (!oformat) { 
        
        if (format) { 
        
            oformat = av_guess_format(format, NULL, NULL);
            if (!oformat) { 
        
                av_log(s, AV_LOG_ERROR, "Requested output format '%s' is not a suitable output format\n", format);
                ret = AVERROR(EINVAL);
                goto error;
            }
        } else { 
        
            oformat = av_guess_format(NULL, filename, NULL);
            if (!oformat) { 
        
                ret = AVERROR(EINVAL);
                av_log(s, AV_LOG_ERROR, "Unable to find a suitable output format for '%s'\n",
                       filename);
                goto error;
            }
        }
    }

    s->oformat = oformat;
    if (s->oformat->priv_data_size > 0) { 
        
        s->priv_data = av_mallocz(s->oformat->priv_data_size);
        if (!s->priv_data)
            goto nomem;
        if (s->oformat->priv_class) { 
        
            *(const AVClass**)s->priv_data= s->oformat->priv_class;
            av_opt_set_defaults(s->priv_data);
        }
    } else
        s->priv_data = NULL;

    if (filename) { 
        
#if FF_API_FORMAT_FILENAME
FF_DISABLE_DEPRECATION_WARNINGS
        av_strlcpy(s->filename, filename, sizeof(s->filename));
FF_ENABLE_DEPRECATION_WARNINGS
#endif
        if (!(s->url = av_strdup(filename)))
            goto nomem;

    }
    *avctx = s;
    return 0;
nomem:
    av_log(s, AV_LOG_ERROR, "Out of memory\n");
    ret = AVERROR(ENOMEM);
error:
    avformat_free_context(s);
    return ret;
}

可以看出,⾥⾯最主要的就两个函数,avformat_alloc_context和av_guess_format,⼀个 是申请内存分配上下⽂,⼀个是通过后⾯两个参数获取AVOutputFormat。

出av_guess_format这个函数会通过filename和short_name来和所有的编码器进⾏⽐对,找 出最接近的编码器然后返回。

ff_const59 AVOutputFormat *av_guess_format(const char *short_name, const char *filename,
                                const char *mime_type)
{ 
        
    const AVOutputFormat *fmt = NULL;
    AVOutputFormat *fmt_found = NULL;
    void *i = 0;
    int score_max, score;

    /* specific test for image sequences */
#if CONFIG_IMAGE2_MUXER
    if (!short_name && filename &&
        av_filename_number_test(filename) &&
        ff_guess_image2_codec(filename) != AV_CODEC_ID_NONE) { 
        
        return av_guess_format("image2", NULL, NULL);
    }
#endif
    /* Find the proper file type. */
    score_max = 0;
    while ((fmt = av_muxer_iterate(&i))) { 
        
        score = 0;
        if (fmt->name && short_name && av_match_name(short_name, fmt->name))
            score += 100;
        if (fmt->mime_type && mime_type && !strcmp(fmt->mime_type, mime_type))
            score += 10;
        if (filename && fmt->extensions &&
            av_match_ext(filename, fmt->extensions)) { 
        
            score += 5;
        }
        if (score > score_max) { 
        
            score_max = score;
            fmt_found = (AVOutputFormat*)fmt;
        }
    }
    return fmt_found;
}

FFmpeg结构体:AVOutputFormat

1.描述

AVOutpufFormat表示输出⽂件容器格式,AVOutputFormat 结构主要包含的信息有:封装名称描述,编 码格式信息(video/audio 默认编码格式,⽀持的编码格式列表),⼀些对封装的操作函数 (write_header,write_packet,write_tailer等)。 ffmpeg⽀持各种各样的输出⽂件格式,MP4,FLV,3GP等等。⽽ AVOutputFormat 结构体则保存了这 些格式的信息和⼀些常规设置。 每⼀种封装对应⼀个 AVOutputFormat 结构,ffmpeg将AVOutputFormat 按照链表存储:

2.结构体定义

/** * @addtogroup lavf_encoding * @{ */
typedef struct AVOutputFormat { 
        
    const char *name;
    /** * Descriptive name for the format, meant to be more human-readable * than name. You should use the NULL_IF_CONFIG_SMALL() macro * to define it. */
    const char *long_name;
    const char *mime_type;
    const char *extensions; /**< comma-separated filename extensions */
    /* output support */
    enum AVCodecID audio_codec;    /**< default audio codec */
    enum AVCodecID video_codec;    /**< default video codec */
    enum AVCodecID subtitle_codec; /**< default subtitle codec */
    /** * can use flags: AVFMT_NOFILE, AVFMT_NEEDNUMBER, * AVFMT_GLOBALHEADER, AVFMT_NOTIMESTAMPS, AVFMT_VARIABLE_FPS, * AVFMT_NODIMENSIONS, AVFMT_NOSTREAMS, AVFMT_ALLOW_FLUSH, * AVFMT_TS_NONSTRICT, AVFMT_TS_NEGATIVE */
    int flags;

    /** * List of supported codec_id-codec_tag pairs, ordered by "better * choice first". The arrays are all terminated by AV_CODEC_ID_NONE. */
    const struct AVCodecTag * const *codec_tag;


    const AVClass *priv_class; ///< AVClass for the private context

    /***************************************************************** * No fields below this line are part of the public API. They * may not be used outside of libavformat and can be changed and * removed at will. * New public fields should be added right above. ***************************************************************** */
    /** * The ff_const59 define is not part of the public API and will * be removed without further warning. */
#if FF_API_AVIOFORMAT
#define ff_const59
#else
#define ff_const59 const
#endif
    ff_const59 struct AVOutputFormat *next;
    /** * size of private data so that it can be allocated in the wrapper */
    int priv_data_size;

    int (*write_header)(struct AVFormatContext *);
    /** * Write a packet. If AVFMT_ALLOW_FLUSH is set in flags, * pkt can be NULL in order to flush data buffered in the muxer. * When flushing, return 0 if there still is more data to flush, * or 1 if everything was flushed and there is no more buffered * data. */
    int (*write_packet)(struct AVFormatContext *, AVPacket *pkt);
    int (*write_trailer)(struct AVFormatContext *);
    /** * Currently only used to set pixel format if not YUV420P. */
    int (*interleave_packet)(struct AVFormatContext *, AVPacket *out,
                             AVPacket *in, int flush);
    /** * Test if the given codec can be stored in this container. * * @return 1 if the codec is supported, 0 if it is not. * A negative number if unknown. * MKTAG('A', 'P', 'I', 'C') if the codec is only supported as AV_DISPOSITION_ATTACHED_PIC */
    int (*query_codec)(enum AVCodecID id, int std_compliance);

    void (*get_output_timestamp)(struct AVFormatContext *s, int stream,
                                 int64_t *dts, int64_t *wall);
    /** * Allows sending messages from application to device. */
    int (*control_message)(struct AVFormatContext *s, int type,
                           void *data, size_t data_size);

    /** * Write an uncoded AVFrame. * * See av_write_uncoded_frame() for details. * * The library will free *frame afterwards, but the muxer can prevent it * by setting the pointer to NULL. */
    int (*write_uncoded_frame)(struct AVFormatContext *, int stream_index,
                               AVFrame **frame, unsigned flags);
    /** * Returns device list with it properties. * @see avdevice_list_devices() for more details. */
    int (*get_device_list)(struct AVFormatContext *s, struct AVDeviceInfoList *device_list);
    /** * Initialize device capabilities submodule. * @see avdevice_capabilities_create() for more details. */
    int (*create_device_capabilities)(struct AVFormatContext *s, struct AVDeviceCapabilitiesQuery *caps);
    /** * Free device capabilities submodule. * @see avdevice_capabilities_free() for more details. */
    int (*free_device_capabilities)(struct AVFormatContext *s, struct AVDeviceCapabilitiesQuery *caps);
    enum AVCodecID data_codec; /**< default data codec */
    /** * Initialize format. May allocate data here, and set any AVFormatContext or * AVStream parameters that need to be set before packets are sent. * This method must not write output. * * Return 0 if streams were fully configured, 1 if not, negative AVERROR on failure * * Any allocations made here must be freed in deinit(). */
    int (*init)(struct AVFormatContext *);
    /** * Deinitialize format. If present, this is called whenever the muxer is being * destroyed, regardless of whether or not the header has been written. * * If a trailer is being written, this is called after write_trailer(). * * This is called if init() fails as well. */
    void (*deinit)(struct AVFormatContext *);
    /** * Set up any necessary bitstream filtering and extract any extra data needed * for the global header. * Return 0 if more packets from this stream must be checked; 1 if not. */
    int (*check_bitstream)(struct AVFormatContext *, const AVPacket *pkt);
} AVOutputFormat;

3.常⻅变量及其作⽤

  • const char *name; // 复⽤器名称
  • const char *long_name;//格式的描述性名称,易于阅读。
  • enum AVCodecID subtitle_codec; //默认的字幕编解码器

⼤部分复⽤器都有默认的编码器,所以⼤家如果要调整编码器类型则需要⾃⼰⼿动指定。 比如

AVOutputFormat ff_flv_muxer = { 
        
    .name           = "flv",
    .long_name      = NULL_IF_CONFIG_SMALL("FLV (Flash Video)"),
    .mime_type      = "video/x-flv",
    .extensions     = "flv",
    .priv_data_size = sizeof(FLVContext),
    .audio_codec    = CONFIG_LIBMP3LAME ? AV_CODEC_ID_MP3 : AV_CODEC_ID_ADPCM_SWF,
    .video_codec    = AV_CODEC_ID_FLV1,
    .init           = flv_init,
    .write_header   = flv_write_header,
    .write_packet   = flv_write_packet,
    .write_trailer  = flv_write_trailer,
    .check_bitstream= flv_check_bitstream,
    .codec_tag      = (const AVCodecTag* const []) { 
        
                          flv_video_codec_ids, flv_audio_codec_ids, 0
                      },
    .flags          = AVFMT_GLOBALHEADER | AVFMT_VARIABLE_FPS |
                      AVFMT_TS_NONSTRICT,
    .priv_class     = &flv_muxer_class,
};
AVOutputFormat ff_mpegts_muxer = { 
        
    .name           = "mpegts",
    .long_name      = NULL_IF_CONFIG_SMALL("MPEG-TS (MPEG-2 Transport Stream)"),
    .mime_type      = "video/MP2T",
    .extensions     = "ts,m2t,m2ts,mts",
    .priv_data_size = sizeof(MpegTSWrite),
    .audio_codec    = AV_CODEC_ID_MP2,
    .video_codec    = AV_CODEC_ID_MPEG2VIDEO,
    .init           = mpegts_init,
    .write_packet   = mpegts_write_packet,
    .write_trailer  = mpegts_write_end,
    .deinit         = mpegts_deinit,
    .check_bitstream = mpegts_check_bitstream,
    .flags          = AVFMT_ALLOW_FLUSH | AVFMT_VARIABLE_FPS | AVFMT_NODIMENSIONS,
    .priv_class     = &mpegts_muxer_class,
};

int (*write_header)(struct AVFormatContext *); int (*write_packet)(struct AVFormatContext *, AVPacket *pkt);//写⼀个数据包。 如果在标志中设 置AVFMT_ALLOW_FLUSH,则pkt可以为NULL。 int (*write_trailer)(struct AVFormatContext *); int (*interleave_packet)(struct AVFormatContext *, AVPacket *out, AVPacket *in, int flush); int (*control_message)(struct AVFormatContext *s, int type, void *data, size_t data_size);//允 许从应⽤程序向设备发送消息。 int (*write_uncoded_frame)(struct AVFormatContext *, int stream_index, AVFrame **frame, unsigned flags);//写⼀个未编码的AVFrame。 int (*init)(struct AVFormatContext *);//初始化格式。 可以在此处分配数据,并设置在发送数据包之前 需要设置的任何AVFormatContext或AVStream参数。 void (*deinit)(struct AVFormatContext *);//取消初始化格式。 int (*check_bitstream)(struct AVFormatContext *, const AVPacket *pkt);//设置任何必要的⽐特流 过滤,并提取全局头部所需的任何额外数据。

FFmpeg函数:avformat_new_stream

AVStream 即是流通道。例如我们将 H264 和 AAC 码流存储为MP4⽂件的时候,就需要在 MP4⽂件中 增加两个流通道,⼀个存储Video:H264,⼀个存储Audio:AAC。(假设H264和AAC只包含单个流通道)。

/** * 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 avcodec_close() and avformat_free_context() to * clean up the allocation by avformat_new_stream(). * * @param s media file handle * @param c If non-NULL, the AVCodecContext corresponding to the new stream * will be initialized to use this codec. This is needed for e.g. codec-specific * defaults to be set, so codec should be provided if it is known. * * @return newly created stream or NULL on error. */
AVStream *avformat_new_stream(AVFormatContext *s, const AVCodec *c);

avformat_new_stream 在 AVFormatContext 中创建 Stream 通道。 AVFormatContext :

  • unsigned int nb_streams; 记录stream通道数⽬。
  • AVStream **streams; 存储stream通道。
  • AVStream : int index; 在AVFormatContext 中所处的通道索引

avformat_new_stream之后便在 AVFormatContext ⾥增加了 AVStream 通道(相关的index已经被设 置了)。之后,我们就可以⾃⾏设置 AVStream 的⼀些参数信息。例如 : codec_id , format ,bit_rate ,width , height

FFmpeg函数:av_interleaved_write_frame

函数原型:int av_interleaved_write_frame(AVFormatContext *s, AVPacket *pkt); 说明:将数据包写⼊输出媒体⽂件,并确保正确的交织(保持packet dts的增⻓性)。 该函数会在内部根据需要缓存packet,以确保输出⽂件中的packet按dts递增的顺序正确交织。如果⾃⼰ 进⾏交织则应调⽤av_write_frame()。 参数:

s 媒体⽂件句柄
pkt 要写⼊的packet。 如果packet使⽤引⽤参考计数的内存⽅式,则此函数将获取此引⽤权(可以理解为 move了reference),并在内部在合适的时候进⾏释放。此函数返回后,调⽤者不得 通过此引⽤访问数据。如果packet没有引⽤计数,libavformat将进⾏复制。 此参数可以为NULL(在任何时候,不仅在结尾),以刷新交织队列。 Packet的stream_index字段必须设置为s-> streams中相应流的索引。 时间戳记(pts,dts)必须设置为stream’s timebase中的正确值(除⾮输出格式⽤ AVFMT_NOTIMESTAMPS标志标记,然后可以将其设置为AV_NOPTS_VALUE)。 同⼀stream后续packet的dts必须严格递增(除⾮输出格式⽤ AVFMT_TS_NONSTRICT标记,则它们只必须不减少)。duration也应设置(如果已 知)。

返回值:成功时为0,错误时为负AVERROR。即使此函数调⽤失败,Libavformat仍将始终释放该 packet。

FFmpeg函数:av_compare_ts

/** * Compare two timestamps each in its own time base. * * @return One of the following values: * - -1 if `ts_a` is before `ts_b` * - 1 if `ts_a` is after `ts_b` * - 0 if they represent the same position * * @warning * The result of the function is undefined if one of the timestamps is outside * the `int64_t` range when represented in the other's timebase. */
int av_compare_ts(int64_t ts_a, AVRational tb_a, int64_t ts_b, AVRational tb_b);

返回值:

  • -1 ts_a 在ts_b之前
  • 1 ts_a 在ts_b之后
  • 0 ts_a 在ts_b同⼀位置

⽤伪代码:return ts_a == ts_b ? 0 : ts_a < ts_b ? -1 : 1

MediaInfo分析⽂件写⼊

这⾥只是分析avformat_write_header和av_write_trailer的作⽤。

flv

只写avformat_write_header

000 File Header (9 bytes) 
000 FLV header (9 bytes) 
000 Signature: FLV 
003 Version: 1 (0x01) 
004 Flags: 5 (0x05) 
005 Video: Yes 
005 Audio: Yes 
005 Size: 9 (0x00000009) 
009 ------------------------- 
009 -- 

标签: ed灯c显示器d传感器

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台