作者:叶余
来源:https://www.cnblogs.com/leisure_chn/p/10404502.html
1. 数据结构定义
1.1 相关基础概念
在阅读 AVFrame 数据结构之前,需要先了解与之相关的几个基础概念(此处只考虑视频相关):
pixel_format:像素格式,图像像素在内存中的排列格式。一种像素格式包含有色彩空间、采样方式、存储模式、位深等信息。
bit_depth: 位深,指每个分量(Y、U、V、R、G、B 等)单个采样点所占的位宽度。
plane: 存储图像中一个或多个分量的一片内存区域。planar 存储模式中,至少有一个分量占用单独的一个 plane,planar 存储模式至少有两个 plane,具体到 yuv420p 格式有 Y、U、V 三个 plane,nv12 格式有 Y、UV 两个 plane,gbrap 格式有 G、B、R、A 四个 plane。packed 存储模式中,因为所有分量的像素是交织存放的,所以 packed 存储模式只有一个 plane。
slice: 图像中一片连续的行,必须是连续的,顺序由顶部到底部或由底部到顶部
stride/pitch: 一行图像中某个分量(如亮度分量或色度分量)所占的字节数, 也就是一个 plane 中一行数据的宽度。有对齐要求,计算公式如下:
stride 值 = 图像宽度 * 分量数 * 单样本位宽度 / 水平子采样因子 / 8
其中,图像宽度表示图像宽度是多少个像素,分量数指当前 plane 包含多少个分量(如 rgb24 格式一个 plane 有 R、G、B 三个分量),单位本位宽度指某分量的一个样本在考虑对齐后在内存中占用的实际位数(例如位深 8 占 8 位宽,位深 10 实际占 16 位宽,对齐值与平台相关),水平子采样因子指在水平方向上每多少个像素采样出一个色度样本(亮度样本不进行下采样,所以采样因子总是 1)。
上述概念的详细说明可参考“色彩空间与像素格式”一文第 4 节。
1.2 AVFrame 数据结构
struct AVFrame 定义于 <libavutil/frame.h>
struct AVFrame frame;
AVFrame 中存储的是经过解码后的原始数据。在解码中,AVFrame 是解码器的输出;在编码中,AVFrame 是编码器的输入。下图中,“decoded frames”的数据类型就是 AVFrame:
_______ ______________ | | | | | input | demuxer | encoded data | decoder | file | ---------> | packets | -----+ |_______| |______________| | v _________ | | | decoded | | frames | |_________| ________ ______________ | | | | | | | output | <-------- | encoded data | <----+ | file | muxer | packets | encoder |________| |______________|
AVFrame 数据结构非常重要,它的成员非常多,导致数据结构定义篇幅很长。下面引用的数据结构定义中省略冗长的注释以及大部分成员,先总体说明 AVFrame 的用法,然后再将一些重要成员摘录出来单独进行说明:
/** * This structure describes decoded (raw) audio or video data. * * AVFrame must be allocated using av_frame_alloc(). Note that this only * allocates the AVFrame itself, the buffers for the data must be managed * through other means (see below). * AVFrame must be freed with av_frame_free(). * * AVFrame is typically allocated once and then reused multiple times to hold * different data (e.g. a single AVFrame to hold frames received from a * decoder). In such a case, av_frame_unref() will free any references held by * the frame and reset it to its original clean state before it * is reused again. * * The data described by an AVFrame is usually reference counted through the * AVBuffer API. The underlying buffer references are stored in AVFrame.buf / * AVFrame.extended_buf. An AVFrame is considered to be reference counted if at * least one reference is set, i.e. if AVFrame.buf[0] != NULL. In such a case, * every single data plane must be contained in one of the buffers in * AVFrame.buf or AVFrame.extended_buf. * There may be a single buffer for all the data, or one separate buffer for * each plane, or anything in between. * * sizeof(AVFrame) is not a part of the public ABI, so new fields may be added * to the end with a minor bump. * * Fields can be accessed through AVOptions, the name string used, matches the * C structure field name for fields accessible through AVOptions. The AVClass * for AVFrame can be obtained from avcodec_get_frame_class() */ typedef struct AVFrame { uint8_t *data[AV_NUM_DATA_POINTERS]; int linesize[AV_NUM_DATA_POINTERS]; uint8_t **extended_data; int width, height; int nb_samples; int format; int key_frame; enum AVPictureType pict_type; AVRational sample_aspect_ratio; int64_t pts; ...... } AVFrame;
AVFrame 的用法:
- AVFrame 对象必须调用 av_frame_alloc() 在堆上分配,注意此处指的是 AVFrame 对象本身,AVFrame 对象必须调用 av_frame_free() 进行销毁。
- AVFrame 中包含的数据缓冲区是
- AVFrame 通常只需分配一次,然后可以多次重用,每次重用前应调用 av_frame_unref() 将 frame 复位到原始的干净可用的状态。
下面将一些重要的成员摘录出来进行说明:
data
/** * pointer to the picture/channel planes. * This might be different from the first allocated byte * * Some decoders access areas outside 0,0 - width,height, please * see avcodec_align_dimensions2(). Some filters and swscale can read * up to 16 bytes beyond the planes, if these filters are to be used, * then 16 extra bytes must be allocated. * * NOTE: Except for hwaccel formats, pointers not needed by the format * MUST be set to NULL. */ uint8_t *data[AV_NUM_DATA_POINTERS];
存储原始帧数据(未编码的原始图像或音频格式,作为解码器的输出或编码器的输入)。
data 是一个指针数组,数组的每一个元素是一个指针,指向视频中图像的某一 plane 或音频中某一声道的 plane。
关于图像 plane 的详细说明参考“色彩空间与像素格式”,音频 plane 的详细说明参数“ffplay 源码解析 6-音频重采样 6.1.1 节”。下面简单说明:
对于 packet 格式,一幅 YUV 图像的 Y、U、V 交织存储在一个 plane 中,形如 YUVYUV...,data[0] 指向这个 plane;
一个双声道的音频帧其左声道 L、右声道 R 交织存储在一个 plane 中,形如 LRLRLR...,data[0] 指向这个 plane。
对于 planar 格式,一幅 YUV 图像有 Y、U、V 三个 plane,data[0] 指向 Y plane,data[1] 指向 U plane,data[2] 指向 V plane;
一个双声道的音频帧有左声道 L 和右声道 R 两个 plane,data[0] 指向 L plane,data[1] 指向 R plane。
linesize
/** * For video, size in bytes of each picture line. * For audio, size in bytes of each plane. * * For audio, only linesize[0] may be set. For planar audio, each channel * plane must be the same size. * * For video the linesizes should be multiples of the CPUs alignment * preference, this is 16 or 32 for modern desktop CPUs. * Some code requires such alignment other code can be slower without * correct alignment, for yet other it makes no difference. * * @note The linesize may be larger than the size of usable data -- there * may be extra padding present for performance reasons. */ int linesize[AV_NUM_DATA_POINTERS];
linesize 是一个数组。
对于视频来说,linesize 每个元素是一个图像 plane 中一行图像的大小(字节数)。注意有对齐要求。 linesize 值同 1.1 节中 stride 值。对于 planar 格式视频,有多个 plane,每个 plane 的 linesize 表示一行图像在当前 plane 中所占的存储空间大小。对于 packed 格式视频,只有一个 plane,linesize 表示一行图像所占的存储空间大小。
对于音频来说,linesize 每个元素是一个音频 plane 的大小(字节数)。packed 格式多声道音频只有一个 plane,planar 格式多声道音频有多个 plane。音频只使用 linesize[0],即使有多个 plane。对于 planar 音频来说,每个 plane 的大小必须一样。
linesize 可能会因性能上的考虑而填充一些额外的数据,因此 linesize 可能比实际对应的音视频数据尺寸要大。
extended_data
/** * pointers to the data planes/channels. * * For video, this should simply point to data[]. * * For planar audio, each channel has a separate data pointer, and * linesize[0] contains the size of each channel buffer. * For packed audio, there is just one data pointer, and linesize[0] * contains the total size of the buffer for all channels. * * Note: Both data and extended_data should always be set in a valid frame, * but for planar audio with more channels that can fit in data, * extended_data must be used in order to access all channels. */ uint8_t **extended_data;
????extended_data 是干啥的????
对于视频来说,直接指向 data[]成员。
对于音频来说,packet 格式音频只有一个 plane,一个音频帧中各个声道的采样点交织存储在此 plane 中;planar 格式音频每个声道一个 plane。在多声道 planar 格式音频中,必须使用 extended_data 才能访问所有声道,什么意思?
在有效的视频/音频 frame 中,data 和 extended_data 两个成员都必须设置有效值。
width, height
/** * @name Video dimensions * Video frames only. The coded dimensions (in pixels) of the video frame, * i.e. the size of the rectangle that contains some well-defined values. * * @note The part of the frame intended for display/presentation is further * restricted by the @ref cropping "Cropping rectangle". * @{ */ int width, height;
视频帧宽和高(像素)。
nb_samples
/** * number of audio samples (per channel) described by this frame */ int nb_samples;
音频帧中单个声道中包含的采样点数。
format
/** * format of the frame, -1 if unknown or unset * Values correspond to enum AVPixelFormat for video frames, * enum AVSampleFormat for audio) */ int format;
帧格式。如果是未知格式或未设置,则值为-1。
对于视频帧,此值对应于“enum AVPixelFormat”结构:
enum AVPixelFormat { AV_PIX_FMT_NONE = -1, AV_PIX_FMT_YUV420P, ///< planar YUV 4:2:0, 12bpp, (1 Cr & Cb sample per 2x2 Y samples) AV_PIX_FMT_YUYV422, ///< packed YUV 4:2:2, 16bpp, Y0 Cb Y1 Cr AV_PIX_FMT_RGB24, ///< packed RGB 8:8:8, 24bpp, RGBRGB... AV_PIX_FMT_BGR24, ///< packed RGB 8:8:8, 24bpp, BGRBGR... ...... }
对于音频帧,此值对应于“enum AVSampleFormat”格式:
enum AVSampleFormat { AV_SAMPLE_FMT_NONE = -1, AV_SAMPLE_FMT_U8, ///< unsigned 8 bits AV_SAMPLE_FMT_S16, ///< signed 16 bits AV_SAMPLE_FMT_S32, ///< signed 32 bits AV_SAMPLE_FMT_FLT, ///< float AV_SAMPLE_FMT_DBL, ///< double AV_SAMPLE_FMT_U8P, ///< unsigned 8 bits, planar AV_SAMPLE_FMT_S16P, ///< signed 16 bits, planar AV_SAMPLE_FMT_S32P, ///< signed 32 bits, planar AV_SAMPLE_FMT_FLTP, ///< float, planar AV_SAMPLE_FMT_DBLP, ///< double, planar AV_SAMPLE_FMT_S64, ///< signed 64 bits AV_SAMPLE_FMT_S64P, ///< signed 64 bits, planar AV_SAMPLE_FMT_NB ///< Number of sample formats. DO NOT USE if linking dynamically };
key_frame
/** * 1 -> keyframe, 0-> not */ int key_frame;
视频帧是否是关键帧的标识,1->关键帧,0->非关键帧。
pict_type
/** * Picture type of the frame. */ enum AVPictureType pict_type;
视频帧类型(I、B、P 等)。如下:
/** * @} * @} * @defgroup lavu_picture Image related * * AVPicture types, pixel formats and basic image planes manipulation. * * @{ */ enum AVPictureType { AV_PICTURE_TYPE_NONE = 0, ///< Undefined AV_PICTURE_TYPE_I, ///< Intra AV_PICTURE_TYPE_P, ///< Predicted AV_PICTURE_TYPE_B, ///< Bi-dir predicted AV_PICTURE_TYPE_S, ///< S(GMC)-VOP MPEG-4 AV_PICTURE_TYPE_SI, ///< Switching Intra AV_PICTURE_TYPE_SP, ///< Switching Predicted AV_PICTURE_TYPE_BI, ///< BI type };
sample_aspect_ratio
/** * Sample aspect ratio for the video frame, 0/1 if unknown/unspecified. */ AVRational sample_aspect_ratio;
视频帧的宽高比。
pts
/** * Presentation timestamp in time_base units (time when frame should be shown to user). */ int64_t pts;
显示时间戳。单位是 time_base。
pkt_pts
#if FF_API_PKT_PTS /** * PTS copied from the AVPacket that was decoded to produce this frame. * @deprecated use the pts field instead */ attribute_deprecated int64_t pkt_pts; #endif
此 frame 对应的 packet 中的显示时间戳。是从对应 packet(解码生成此 frame)中拷贝 PTS 得到此值。
pkt_dts
/** * DTS copied from the AVPacket that triggered returning this frame. (if frame threading isn't used) * This is also the Presentation time of this AVFrame calculated from * only AVPacket.dts values without pts values. */ int64_t pkt_dts;
此 frame 对应的 packet 中的解码时间戳。是从对应 packet(解码生成此 frame)中拷贝 DTS 得到此值。
如果对应的 packet 中只有 dts 而未设置 pts,则此值也是此 frame 的 pts。
coded_picture_number
1 2 3 4 /** * picture number in bitstream order */ int coded_picture_number;
在编码流中当前图像的序号。
display_picture_number
/** * picture number in display order */ int display_picture_number;
在显示序列中当前图像的序号。
interlaced_frame
/** * The content of the picture is interlaced. */ int interlaced_frame;
图像逐行/隔行模式标识。
sample_rate
/** * Sample rate of the audio data. */ int sample_rate;
音频采样率。
channel_layout
/** * Channel layout of the audio data. */ uint64_t channel_layout;
音频声道布局。每 bit 代表一个特定的声道,参考 channel_layout.h 中的定义,一目了然:
/** * @defgroup channel_masks Audio channel masks * * A channel layout is a 64-bits integer with a bit set for every channel. * The number of bits set must be equal to the number of channels. * The value 0 means that the channel layout is not known. * @note this data structure is not powerful enough to handle channels * combinations that have the same channel multiple times, such as * dual-mono. * * @{ */ #define AV_CH_FRONT_LEFT 0x00000001 #define AV_CH_FRONT_RIGHT 0x00000002 #define AV_CH_FRONT_CENTER 0x00000004 #define AV_CH_LOW_FREQUENCY 0x00000008 ...... /** * @} * @defgroup channel_mask_c Audio channel layouts * @{ * */ #define AV_CH_LAYOUT_MONO (AV_CH_FRONT_CENTER) #define AV_CH_LAYOUT_STEREO (AV_CH_FRONT_LEFT|AV_CH_FRONT_RIGHT) #define AV_CH_LAYOUT_2POINT1 (AV_CH_LAYOUT_STEREO|AV_CH_LOW_FREQUENCY)
buf
/** * AVBuffer references backing the data for this frame. If all elements of * this array are NULL, then this frame is not reference counted. This array * must be filled contiguously -- if buf[i] is non-NULL then buf[j] must * also be non-NULL for all j < i. * * There may be at most one AVBuffer per data plane, so for video this array * always contains all the references. For planar audio with more than * AV_NUM_DATA_POINTERS channels, there may be more buffers than can fit in * this array. Then the extra AVBufferRef pointers are stored in the * extended_buf array. */ AVBufferRef *buf[AV_NUM_DATA_POINTERS];
此帧的数据可以由 AVBufferRef 管理,AVBufferRef 提供 AVBuffer 引用机制。这里涉及到缓冲区引用计数概念:
AVBuffer 是 FFmpeg 中很常用的一种缓冲区,缓冲区使用引用计数(reference-counted)机制。
AVBufferRef 则对 AVBuffer 缓冲区提供了一层封装,最主要的是作引用计数处理,实现了一种安全机制。用户不应直接访问 AVBuffer,应通过 AVBufferRef 来访问 AVBuffer,以保证安全。
FFmpeg 中很多基础的数据结构都包含了 AVBufferRef 成员,来间接使用 AVBuffer 缓冲区。
相关内容参考“FFmpeg 数据结构 AVBuffer”
????帧的数据缓冲区 AVBuffer 就是前面的 data 成员,用户不应直接使用 data 成员,应通过 buf 成员间接使用 data 成员。那 extended_data 又是做什么的呢????
如果 buf[]的所有元素都为 NULL,则此帧不会被引用计数。必须连续填充 buf[] - 如果 buf[i]为非 NULL,则对于所有 j<i,buf[j]也必须为非 NULL。
每个 plane 最多可以有一个 AVBuffer,一个 AVBufferRef 指针指向一个 AVBuffer,一个 AVBuffer 引用指的就是一个 AVBufferRef 指针。
对于视频来说,buf[]包含所有 AVBufferRef 指针。对于具有多于 AV_NUM_DATA_POINTERS 个声道的 planar 音频来说,可能 buf[]存不下所有的 AVBbufferRef 指针,多出的 AVBufferRef 指针存储在 extended_buf 数组中。
extended_buf&nb_extended_buf
/** * For planar audio which requires more than AV_NUM_DATA_POINTERS * AVBufferRef pointers, this array will hold all the references which * cannot fit into AVFrame.buf. * * Note that this is different from AVFrame.extended_data, which always * contains all the pointers. This array only contains the extra pointers, * which cannot fit into AVFrame.buf. * * This array is always allocated using av_malloc() by whoever constructs * the frame. It is freed in av_frame_unref(). */ AVBufferRef **extended_buf; /** * Number of elements in extended_buf. */ int nb_extended_buf;
对于具有多于 AV_NUM_DATA_POINTERS 个声道的 planar 音频来说,可能 buf[]存不下所有的 AVBbufferRef 指针,多出的 AVBufferRef 指针存储在 extended_buf 数组中。
注意此处的 extended_buf 和 AVFrame.extended_data 的不同,AVFrame.extended_data 包含所有指向各 plane 的指针,而 extended_buf 只包含 AVFrame.buf 中装不下的指针。
extended_buf 是构造 frame 时 av_frame_alloc()中自动调用 av_malloc()来分配空间的。调用 av_frame_unref 会释放掉 extended_buf。
nb_extended_buf 是 extended_buf 中的元素数目。
best_effort_timestamp
/** * frame timestamp estimated using various heuristics, in stream time base * - encoding: unused * - decoding: set by libavcodec, read by user. */ int64_t best_effort_timestamp;
????
pkt_pos
/** * reordered pos from the last AVPacket that has been input into the decoder * - encoding: unused * - decoding: Read by user. */ int64_t pkt_pos;
记录最后一个扔进解码器的 packet 在输入文件中的位置偏移量。
pkt_duration
/** * duration of the corresponding packet, expressed in * AVStream->time_base units, 0 if unknown. * - encoding: unused * - decoding: Read by user. */ int64_t pkt_duration;
对应 packet 的时长,单位是 AVStream->time_base。
channels
/** * number of audio channels, only used for audio. * - encoding: unused * - decoding: Read by user. */ int channels;
音频声道数量。
pkt_size
/** * size of the corresponding packet containing the compressed * frame. * It is set to a negative value if unknown. * - encoding: unused * - decoding: set by libavcodec, read by user. */ int pkt_size;
对应 packet 的大小。
crop_
/** * @anchor cropping * @name Cropping * Video frames only. The number of pixels to discard from the the * top/bottom/left/right border of the frame to obtain the sub-rectangle of * the frame intended for presentation. * @{ */ size_t crop_top; size_t crop_bottom; size_t crop_left; size_t crop_right; /** * @} */
用于视频帧图像裁切。四个值分别为从 frame 的上/下/左/右边界裁切的像素数。
2. 相关函数使用说明
2.1 av_frame_alloc()
/** * 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_alloc(void);
构造一个 frame,对象各成员被设为默认值。
此函数只分配 AVFrame 对象本身,而不分配 AVFrame 中的数据缓冲区。
2.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);
释放一个 frame。
2.3 av_frame_ref()
/** * Set up a new reference to the data described by the source frame. * * Copy frame properties from src to dst and create a new reference for each * AVBufferRef from src. * * If src is not reference counted, new buffers are allocated and the data is * copied. * * @warning: dst MUST have been either unreferenced with av_frame_unref(dst), * or newly allocated with av_frame_alloc() before calling this * function, or undefined behavior will occur. * * @return 0 on success, a negative AVERROR on error */ int av_frame_ref(AVFrame *dst, const AVFrame *src);
为 src 中的数据建立一个新的引用。
将 src 中帧的各属性拷到 dst 中,并且为 src 中每个 AVBufferRef 创建一个新的引用。
如果 src 未使用引用计数,则 dst 中会分配新的数据缓冲区,将将 src 中缓冲区的数据拷贝到 dst 中的缓冲区。
2.4 av_frame_clone()
/** * Create a new frame that references the same data as src. * * This is a shortcut for av_frame_alloc()+av_frame_ref(). * * @return newly created AVFrame on success, NULL on error. */ AVFrame *av_frame_clone(const AVFrame *src);
创建一个新的 frame,新的 frame 和 src 使用同一数据缓冲区,缓冲区管理使用引用计数机制。
本函数相当于 av_frame_alloc()+av_frame_ref()
2.5 av_frame_unref()
/** * Unreference all the buffers referenced by frame and reset the frame fields. */ void av_frame_unref(AVFrame *frame);
解除本 frame 对本 frame 中所有缓冲区的引用,并复位 frame 中各成员。
2.6 av_frame_move_ref()
/** * Move everything contained in src to dst and reset src. * * @warning: dst is not unreferenced, but directly overwritten without reading * or deallocating its contents. Call av_frame_unref(dst) manually * before calling this function to ensure that no memory is leaked. */ void av_frame_move_ref(AVFrame *dst, AVFrame *src);
将 src 中所有数据拷贝到 dst 中,并复位 src。
为避免内存泄漏,在调用av_frame_move_ref(dst, src)
之前应先调用av_frame_unref(dst)
。
2.7 av_frame_get_buffer()
/** * Allocate new buffer(s) for audio or video data. * * The following fields must be set on frame before calling this function: * - format (pixel format for video, sample format for audio) * - width and height for video * - nb_samples and channel_layout for audio * * This function will fill AVFrame.data and AVFrame.buf arrays and, if * necessary, allocate and fill AVFrame.extended_data and AVFrame.extended_buf. * For planar formats, one buffer will be allocated for each plane. * * @warning: if frame already has been allocated, calling this function will * leak memory. In addition, undefined behavior can occur in certain * cases. * * @param frame frame in which to store the new buffers. * @param align Required buffer size alignment. If equal to 0, alignment will be * chosen automatically for the current CPU. It is highly * recommended to pass 0 here unless you know what you are doing. * * @return 0 on success, a negative AVERROR on error. */ int av_frame_get_buffer(AVFrame *frame, int align);
为音频或视频数据分配新的缓冲区。
调用本函数前,帧中的如下成员必须先设置好:
- format(视频像素格式或音频采样格式)
- width、height(视频画面和宽和高)
- nb_samples、channel_layout(音频单个声道中的采样点数目和声道布局)
本函数会填充 AVFrame.data 和 AVFrame.buf 数组,如果有需要,还会分配和填充 AVFrame.extended_data 和 AVFrame.extended_buf。
对于 planar 格式,会为每个 plane 分配一个缓冲区。
2.8 av_frame_copy()
/** * Copy the frame data from src to dst. * * This function does not allocate anything, dst must be already initialized and * allocated with the same parameters as src. * * This function only copies the frame data (i.e. the contents of the data / * extended data arrays), not any other properties. * * @return >= 0 on success, a negative AVERROR on error. */ int av_frame_copy(AVFrame *dst, const AVFrame *src);
将 src 中的帧数据拷贝到 dst 中。
本函数并不会有任何分配缓冲区的动作,调用此函数前 dst 必须已经使用了和 src 同样的参数完成了初始化。
本函数只拷贝帧中的数据缓冲区的内容(data/extended_data 数组中的内容),而不涉及帧中任何其他的属性。
3. 参考资料
[1] FFMPEG 结构体分析:AVFrame, https://blog.csdn.net/leixiaohua1020/article/details/14214577
[2] 色彩空间与像素格式, https://www.cnblogs.com/leisure_chn/p/10290575.html
4. 修改记录
2019-01-13 V1.0 初稿
2021-01-06 V1.1 增加 1.1 节,修复 linesize 描述不清晰的问题
2021-01-16 V1.1 更新 1.1 节内容,详细解释放到“色彩空间与像素格式”文章中
「视频云技术」你最值得关注的音视频技术公众号,每周推送来自阿里云一线的实践技术文章,在这里与音视频领域一流工程师交流切磋。