Android平台轻量级RTSP服务模块编码前后数据源对接探究

​技术背景

好多开发者可能有个疑惑,什么时候轻量级RTSP服务?为什么需要有轻量级RTSP服务模块?实际上,轻量级RTSP服务解决的核心痛点是不需要用户额外部署RTSP或者RTMP流媒体服务,实现本地的音视频(如摄像头、麦克风)或编码后数据,汇聚到内置RTSP服务,对外提供可供拉流的RTSP URL。

轻量级RTSP服务,适用于内网环境下,对并发要求不高的场景,支持H.264/H.265,支持RTSP鉴权、单播、组播模式,考虑到单个服务承载能力,我们支持同时创建多个RTSP服务,并支持获取当前RTSP服务会话连接数。

技术实现

以大牛直播SDK的Android平台轻量级RTSP服务为例,我们大概介绍下设计的常用的数据源对接接口。

标准功能

  • 音频编码:AAC;
  • 视频编码:H.264、H.265;
  • 协议:RTSP;
  • [音视频]支持纯音频/纯视频/音视频;
  • [摄像头]支持采集过程中,前后摄像头实时切换;
  • 支持帧率、关键帧间隔(GOP)、码率(bit-rate)设置;
  • 支持软编码、特定机型硬编码;
  • 支持横屏、竖屏采集;
  • 支持Android屏幕采集;
  • 支持RTSP端口设置;
  • 支持RTSP鉴权用户名、密码设置;
  • 支持获取当前RTSP服务会话连接数;
  • 支持Android 5.1及以上版本。

YV12的数据接口

YV12的数据接口,主要是用于第三方的设备对接居多,这个接口的u_stride, v_stride分别是(width+1)/2,如果出来的数据需要旋转,通过rotation_degree来控制旋转角度即可。

java 复制代码
    /**
     * YV12数据接口
     *
     * @param data: YV12 data
     *
     * @param width: 图像宽
     *
     * @param height: 图像高
     *
     * @param y_stride:  y面步长
     *
     * @param v_stride: v面步长
     *
     * @param u_stride: u面步长
     *
     * rotation_degree: 顺时针旋转, 必须是0, 90, 180, 270
     *
     * @return {0} if successful
     */
    public native int SmartPublisherOnYV12Data(long handle, byte[] data, int width, int height, int y_stride,  int v_stride, int u_stride, int rotation_degree);

YUV数据接口

支持标准的I420数据接口对接,不再赘述:

java 复制代码
  /**
	 * 投递层I420图像
	 *
	 * @param index: 层索引, 必须大于等于0
	 *
	 * @param left: 层叠加的左上角坐标, 对于第0层的话传0
	 *
	 * @param top: 层叠加的左上角坐标, 对于第0层的话传0
	 *
	 * @param y_plane: y平面图像数据
	 *
	 * @param y_offset: 图像偏移, 这个主要目的是用来做clip的,一般传0
	 *
	 * @param y_row_stride: stride information
	 *
	 * @param u_plane: u平面图像数据
	 *
	 * @param u_offset: 图像偏移, 这个主要目的是用来做clip的,一般传0
	 *
	 * @param u_row_stride: stride information
	 *                    *
	 * @param v_plane: v平面图像数据
	 *
	 * @param v_offset: 图像偏移, 这个主要目的是用来做clip的,一般传0
	 *
	 * @param v_row_stride: stride information
	 *
	 * @param width: width, 必须大于1, 且必须是偶数
	 *
	 * @param height: height, 必须大于1, 且必须是偶数
	 *
	 * @param  is_vertical_flip: 是否垂直翻转, 0不翻转, 1翻转
	 *
	 * @param  is_horizontal_flip:是否水平翻转, 0不翻转, 1翻转
	 *
	 * @param  scale_width: 缩放宽,必须是偶数, 0或负数不缩放
	 *
	 * @param  scale_height: 缩放高, 必须是偶数, 0或负数不缩放
	 *
	 * @param  scale_filter_mode: 缩放质量, 传0使用默认速度,可选等级范围是:[1,3],值越大缩放质量越好, 但速度越慢
	 *
	 * @param  rotation_degree: 顺时针旋转, 必须是0, 90, 180, 270, 注意:旋转是在缩放, 垂直/水品反转之后再做, 请留意顺序
	 *
	 * @return {0} if successful
	 */
	public native int PostLayerImageI420ByteBuffer(long handle, int index, int left, int top,
												   ByteBuffer y_plane, int y_offset, int y_row_stride,
												   ByteBuffer u_plane, int u_offset, int u_row_stride,
												   ByteBuffer v_plane, int v_offset, int v_row_stride,
												   int width, int height, int is_vertical_flip,  int is_horizontal_flip,
												   int scale_width,  int scale_height, int scale_filter_mode,
												   int rotation_degree);

NV21转I420并旋转接口

这个接口也是主要用于特定的数据类型对接,NV21的数据,直接转I420后,对接即可,接口参数比较简单,不再赘述。

java 复制代码
  /**
	 * NV21转换到I420并旋转
	 *
	 * @param src: nv21 data
	 *
	 * @param dst: 输出I420 data
	 *
	 * @param width: 图像宽
	 *
	 * @param height: 图像高
	 *
	 * rotation_degree: 顺时针旋转, 必须是0, 90, 180, 270
	 *
	 * @return {0} if successful
	 */
	public native int SmartPublisherNV21ToI420Rotate(long handle, byte[] src, int src_y_stride, int src_uv_stride, byte[] dst,
													 int dst_y_stride, int dst_u_stride, int dst_v_stride,
													 int width, int height,
													 int rotation_degree);

支持RGBA数据接入

RGBA的主要用于屏幕共享场景下。

java 复制代码
   /**
    * Set live video data(no encoded data).
    *
    * @param data: RGBA data
    * 
    * @param rowStride: stride information
    * 
    * @param width: width
    * 
    * @param height: height
    *
    * @return {0} if successful
    */
    public native int SmartPublisherOnCaptureVideoRGBAData(long handle,  ByteBuffer data, int rowStride, int width, int height);

    /**
     * 投递裁剪过的RGBA数据
     *
     * @param data: RGBA data
     *
     * @param rowStride: stride information
     *
     * @param width: width
     *
     * @param height: height
     *
     * @param clipedLeft: 左;  clipedTop: 上; clipedwidth: 裁剪后的宽; clipedHeight: 裁剪后的高; 确保传下去裁剪后的宽、高均为偶数
     *
     * @return {0} if successful
     */
    public native int SmartPublisherOnCaptureVideoClipedRGBAData(long handle,  ByteBuffer data, int rowStride, int width, int height, int clipedLeft, int clipedTop, int clipedWidth, int clipedHeight);

    /**
     * Set live video data(no encoded data).
     *
     * @param data: ABGR flip vertical(垂直翻转) data
     *
     * @param rowStride: stride information
     *
     * @param width: width
     *
     * @param height: height
     *
     * @return {0} if successful
     */
    public native int SmartPublisherOnCaptureVideoABGRFlipVerticalData(long handle,  ByteBuffer data, int rowStride, int width, int height);

支持RGB565数据接入(主要用于同屏场景)

RGB565数据类型也主要用于屏幕采集这块。

java 复制代码
    /**
     * Set live video data(no encoded data).
     *
     * @param data: RGB565 data
     *
     * @param row_stride: stride information
     *
     * @param width: width
     *
     * @param height: height
     *
     * @return {0} if successful
     */
    public native int SmartPublisherOnCaptureVideoRGB565Data(long handle,ByteBuffer data, int row_stride, int width, int height);

NV12、NV21格式

java 复制代码
  /**
	 * 投递层NV21图像
	 *
	 * @param index: 层索引, 必须大于等于0
	 *
	 * @param left: 层叠加的左上角坐标, 对于第0层的话传0
	 *
	 * @param top: 层叠加的左上角坐标, 对于第0层的话传0
	 *
	 * @param y_plane: y平面图像数据
	 *
	 * @param y_offset: 图像偏移, 这个主要目的是用来做clip的,一般传0
	 *
	 * @param y_row_stride: stride information
	 *
	 * @param uv_plane: uv平面图像数据
	 *
	 * @param uv_offset: 图像偏移, 这个主要目的是用来做clip的,一般传0
	 *
	 * @param uv_row_stride: stride information
	 *
	 * @param width: width, 必须大于1, 且必须是偶数
	 *
	 * @param height: height, 必须大于1, 且必须是偶数
	 *
	 * @param  is_vertical_flip: 是否垂直翻转, 0不翻转, 1翻转
	 *
	 * @param  is_horizontal_flip:是否水平翻转, 0不翻转, 1翻转
	 *
	 * @param  scale_width: 缩放宽,必须是偶数, 0或负数不缩放
	 *
	 * @param  scale_height: 缩放高, 必须是偶数, 0或负数不缩放
	 *
	 * @param  scale_filter_mode: 缩放质量, 传0使用默认速度,可选等级范围是:[1,3],值越大缩放质量越好, 但速度越慢
	 *
	 * @param  rotation_degree: 顺时针旋转, 必须是0, 90, 180, 270, 注意:旋转是在缩放, 垂直/水品反转之后再做, 请留意顺序
	 *
	 * @return {0} if successful
	 */
	public native int PostLayerImageNV21ByteBuffer(long handle, int index, int left, int top,
													   ByteBuffer y_plane, int y_offset, int y_row_stride,
												       ByteBuffer uv_plane, int uv_offset, int uv_row_stride,
												       int width, int height, int is_vertical_flip,  int is_horizontal_flip,
													   int scale_width,  int scale_height, int scale_filter_mode,
													   int rotation_degree);


	/**
	 * 投递层NV21图像, 详细说明请参考PostLayerImageNV21ByteBuffer
	 *
	 * @return {0} if successful
	 */
	public native int PostLayerImageNV21ByteArray(long handle, int index, int left, int top,
												   byte[] y_plane, int y_offset, int y_row_stride,

	/**
	 * 投递层NV12图像, 详细说明请参考PostLayerImageNV21ByteBuffer
	 *
	 * @return {0} if successful
	 */
	public native int PostLayerImageNV12ByteBuffer(long handle, int index, int left, int top,


	/**
	 * 投递层NV12图像, 详细说明请参考PostLayerImageNV21ByteBuffer
	 *
	 * @return {0} if successful
	 */
	public native int PostLayerImageNV12ByteArray(long handle, int index, int left, int top,
												  byte[] y_plane, int y_offset, int y_row_stride,
												  byte[] uv_plane, int uv_offset, int uv_row_stride,
												  int width, int height, int is_vertical_flip,  int is_horizontal_flip,
												  int scale_width,  int scale_height, int scale_filter_mode,
												  int rotation_degree);

RGBA8888、RGBX8888接口

perl 复制代码
  /**
	 * 投递层RGBA8888图像,如果不需要Aplpha通道的话, 请使用RGBX8888接口, 效率高
	 *
	 * @param index: 层索引, 必须大于等于0, 注意:如果index是0的话,将忽略Alpha通道
	 *
	 * @param left: 层叠加的左上角坐标, 对于第0层的话传0
	 *
	 * @param top: 层叠加的左上角坐标, 对于第0层的话传0
	 *
	 * @param rgba_plane: rgba 图像数据
	 *
	 * @param offset: 图像偏移, 这个主要目的是用来做clip的, 一般传0
	 *
	 * @param row_stride: stride information
	 *
	 * @param width: width, 必须大于1, 如果是奇数, 将减1
	 *
	 * @param height: height, 必须大于1, 如果是奇数, 将减1
	 *
	 * @param  is_vertical_flip: 是否垂直翻转, 0不翻转, 1翻转
	 *
	 * @param  is_horizontal_flip:是否水平翻转, 0不翻转, 1翻转
	 *
	 * @param  scale_width: 缩放宽,必须是偶数, 0或负数不缩放
	 *
	 * @param  scale_height: 缩放高, 必须是偶数, 0或负数不缩放
	 *
	 * @param  scale_filter_mode: 缩放质量, 传0使用默认速度,可选等级范围是:[1,3],值越大缩放质量越好, 但速度越慢
	 *
	 * @param  rotation_degree: 顺时针旋转, 必须是0, 90, 180, 270, 注意:旋转是在缩放, 垂直/水品反转之后再做, 请留意顺序
	 *
	 * @return {0} if successful
	 */
	public native int PostLayerImageRGBA8888ByteBuffer(long handle, int index, int left, int top,
											 ByteBuffer rgba_plane, int offset, int row_stride, int width, int height,
											 int is_vertical_flip,  int is_horizontal_flip,
											 int scale_width,  int scale_height, int scale_filter_mode,
											 int rotation_degree);


	/**
	 * 投递层RGBA8888图像, 详细说明请参考PostLayerImageRGBA8888ByteBuffer
	 *
	 * @return {0} if successful
	 */
	public native int PostLayerImageRGBA8888ByteArray(long handle, int index, int left, int top,											  int rotation_degree);


	/**
	 * 投递层RGBA8888图像, 详细说明请参考PostLayerImageRGBA8888ByteBuffer
	 *
	 * @return {0} if successful
	 */
	public native int PostLayerImageRGBA8888Native(long handle, int index, int left, int top,
	
	/**
	 * 投递层RGBX8888图像
	 *
	 * @param index: 层索引, 必须大于等于0
	 *
	 * @param left: 层叠加的左上角坐标, 对于第0层的话传0
	 *
	 * @param top: 层叠加的左上角坐标, 对于第0层的话传0
	 *
	 * @param rgbx_plane: rgbx 图像数据
	 *
	 * @param offset: 图像偏移, 这个主要目的是用来做clip的,一般传0
	 *
	 * @param row_stride: stride information
	 *
	 * @param width: width, 必须大于1, 如果是奇数, 将减1
	 *
	 * @param height: height, 必须大于1, 如果是奇数, 将减1
	 *
	 * @param  is_vertical_flip: 是否垂直翻转, 0不翻转, 1翻转
	 *
	 * @param  is_horizontal_flip:是否水平翻转, 0不翻转, 1翻转
	 *
	 * @param  scale_width: 缩放宽,必须是偶数, 0或负数不缩放
	 *
	 * @param  scale_height: 缩放高, 必须是偶数, 0或负数不缩放
	 *
	 * @param  scale_filter_mode: 缩放质量, 传0使用默认速度,可选等级范围是:[1,3],值越大缩放质量越好, 但速度越慢
	 *
	 * @param  rotation_degree: 顺时针旋转, 必须是0, 90, 180, 270, 注意:旋转是在缩放, 垂直/水品反转之后再做, 请留意顺序
	 *
	 * @return {0} if successful
	 */
	public native int PostLayerImageRGBX8888ByteBuffer(long handle, int index, int left, int top,


	/**
	 * 投递层RGBX8888图像, 详细说明请参考PostLayerImageRGBX8888ByteBuffer
	 *
	 * @return {0} if successful
	 */
	public native int PostLayerImageRGBX8888ByteArray(long handle, int index, int left, int top,
													  byte[] rgbx_plane, int offset, int row_stride, int width, int height,


	/**
	 * 投递层RGBX8888图像, 详细说明请参考PostLayerImageRGBX8888ByteBuffer
	 *
	 * @return {0} if successful
	 */
	public native int PostLayerImageRGBX8888Native(long handle, int index, int left, int top,


	/**
	 * 投递层RGB888图像
	 *
	 * @param index: 层索引, 必须大于等于0
	 *
	 * @param left: 层叠加的左上角坐标, 对于第0层的话传0
	 *
	 * @param top: 层叠加的左上角坐标, 对于第0层的话传0
	 *
	 * @param rgb_plane: rgb888 图像数据
	 *
	 * @param offset: 图像偏移, 这个主要目的是用来做clip的,一般传0
	 *
	 * @param row_stride: stride information
	 *
	 * @param width: width, 必须大于1, 如果是奇数, 将减1
	 *
	 * @param height: height, 必须大于1, 如果是奇数, 将减1
	 *
	 * @param  is_vertical_flip: 是否垂直翻转, 0不翻转, 1翻转
	 *
	 * @param  is_horizontal_flip:是否水平翻转, 0不翻转, 1翻转
	 *
	 * @param  scale_width: 缩放宽,必须是偶数, 0或负数不缩放
	 *
	 * @param  scale_height: 缩放高, 必须是偶数, 0或负数不缩放
	 *
	 * @param  scale_filter_mode: 缩放质量, 传0使用默认速度,可选等级范围是:[1,3],值越大缩放质量越好, 但速度越慢
	 *
	 * @param  rotation_degree: 顺时针旋转, 必须是0, 90, 180, 270, 注意:旋转是在缩放, 垂直/水品反转之后再做, 请留意顺序
	 *
	 * @return {0} if successful
	 */
	public native int PostLayerImageRGB888Native(long handle, int index, int left, int top,
													   long rgb_plane, int offset, int row_stride, int width, int height,
													   int is_vertical_flip,  int is_horizontal_flip,
													   int scale_width,  int scale_height, int scale_filter_mode,
													   int rotation_degree);

编码后音视频数据

像前文所说,如无人机等264/HEVC数据,或者本地解析的MP4音视频数据,均可通过实时流的形式,接入到GB28181平台,设计接口如下:

java 复制代码
/**
     * 设置编码后视频数据(H.264)
     *
     * @param codec_id, H.264对应 1
     *
     * @param data 编码后的video数据
     *
     * @param size data length
     *
     * @param is_key_frame 是否I帧, if with key frame, please set 1, otherwise, set 0.
     *
     * @param timestamp video timestamp
     *
     * @param pts Presentation Time Stamp, 显示时间戳
     *
     * @return {0} if successful
     */
    public native int SmartPublisherPostVideoEncodedData(long handle, int codec_id, ByteBuffer data, int size, int is_key_frame, long timestamp, long pts);

    /**
     * 设置编码后视频数据(H.264)
     *
     * @param codec_id, H.264对应 1
     *
     * @param data 编码后的video数据
     *
     *@param offset data的偏移
     *
     * @param size data length
     *
     * @param is_key_frame 是否I帧, if with key frame, please set 1, otherwise, set 0.
     *
     * @param timestamp video timestamp
     *
     * @param pts Presentation Time Stamp, 显示时间戳
     *
     * @return {0} if successful
     */
    public native int SmartPublisherPostVideoEncodedDataV2(long handle, int codec_id,
                                                           ByteBuffer data, int offset, int size,
                                                           int is_key_frame, long timestamp, long pts,
                                                           byte[] sps, int sps_len,
                                                           byte[] pps, int pps_len);

    /**
     * 设置编码后视频数据(H.264),如需录制编码后的数据,用此接口,且设置实际宽高
     *
     * @param codec_id, H.264对应 1
     *
     * @param data 编码后的video数据
     *
     *@param offset data的偏移
     *
     * @param size data length
     *
     * @param is_key_frame 是否I帧, if with key frame, please set 1, otherwise, set 0.
     *
     * @param timestamp video timestamp
     *
     * @param pts Presentation Time Stamp, 显示时间戳
     *
     * @param width, height: 编码后视频宽高
     *
     * @return {0} if successful
     */
    public native int SmartPublisherPostVideoEncodedDataV3(long handle, int codec_id,
                                                           ByteBuffer data, int offset, int size,
                                                           int is_key_frame, long timestamp, long pts,
                                                           byte[] sps, int sps_len,
                                                           byte[] pps, int pps_len,
                                                           int width, int height);

    /**
     * 设置音频数据(AAC/PCMA/PCMU/SPEEX)
     *
     * @param codec_id:
     *
     *  NT_MEDIA_CODEC_ID_AUDIO_BASE = 0x10000,
     *   NT_MEDIA_CODEC_ID_PCMA = NT_MEDIA_CODEC_ID_AUDIO_BASE,
     *   NT_MEDIA_CODEC_ID_PCMU,
     *   NT_MEDIA_CODEC_ID_AAC,
     *   NT_MEDIA_CODEC_ID_SPEEX,
     *   NT_MEDIA_CODEC_ID_SPEEX_NB,
     *   NT_MEDIA_CODEC_ID_SPEEX_WB,
     *   NT_MEDIA_CODEC_ID_SPEEX_UWB,
     *
     * @param data audio数据
     *
     * @param size data length
     *
     * @param is_key_frame 是否I帧, if with key frame, please set 1, otherwise, set 0, audio忽略
     *
     * @param timestamp video timestamp
     *
     * @param parameter_info 用于AAC special config信息填充
     *
     * @param parameter_info_size parameter info size
     *
     * @return {0} if successful
     */
    public native int SmartPublisherPostAudioEncodedData(long handle, int codec_id, ByteBuffer data, int size, int is_key_frame, long timestamp,ByteBuffer parameter_info, int parameter_info_size);

    /**
     * 设置音频数据(AAC/PCMA/PCMU/SPEEX)
     *
     * @param codec_id:
     *
     *  NT_MEDIA_CODEC_ID_AUDIO_BASE = 0x10000,
     *   NT_MEDIA_CODEC_ID_PCMA = NT_MEDIA_CODEC_ID_AUDIO_BASE,
     *   NT_MEDIA_CODEC_ID_PCMU,
     *   NT_MEDIA_CODEC_ID_AAC,
     *   NT_MEDIA_CODEC_ID_SPEEX,
     *   NT_MEDIA_CODEC_ID_SPEEX_NB,
     *   NT_MEDIA_CODEC_ID_SPEEX_WB,
     *   NT_MEDIA_CODEC_ID_SPEEX_UWB,
     *
     * @param data audio数据
     *
     * @param offset data的偏移
     *
     * @param size data length
     *
     * @param is_key_frame 是否I帧, if with key frame, please set 1, otherwise, set 0, audio忽略
     *
     * @param timestamp video timestamp
     *
     * @param parameter_info 用于AAC special config信息填充
     *
     * @param parameter_info_size parameter info size
     *
     * @return {0} if successful
     */
    public native int SmartPublisherPostAudioEncodedDataV2(long handle, int codec_id,
                                                           ByteBuffer data, int offset, int size,
                                                           int is_key_frame, long timestamp,
                                                           byte[] parameter_info, int parameter_info_size);


    /**
     * 设置音频数据(AAC/PCMA/PCMU/SPEEX)
     *
     * @param codec_id:
     *
     *  NT_MEDIA_CODEC_ID_AUDIO_BASE = 0x10000,
     *   NT_MEDIA_CODEC_ID_PCMA = NT_MEDIA_CODEC_ID_AUDIO_BASE,
     *   NT_MEDIA_CODEC_ID_PCMU,
     *   NT_MEDIA_CODEC_ID_AAC,
     *   NT_MEDIA_CODEC_ID_SPEEX,
     *   NT_MEDIA_CODEC_ID_SPEEX_NB,
     *   NT_MEDIA_CODEC_ID_SPEEX_WB,
     *   NT_MEDIA_CODEC_ID_SPEEX_UWB,
     *
     * @param data audio数据
     *
     * @param offset data的偏移
     *
     * @param size data length
     *
     * @param is_key_frame 是否I帧, if with key frame, please set 1, otherwise, set 0, audio忽略
     *
     * @param timestamp video timestamp
     *
     * @param parameter_info 用于AAC special config信息填充
     *
     * @param parameter_info_size parameter info size
     *
     * @param sample_rate 采样率,如果需要录像的话必须传正确的值
     *
     *@param channels 通道数, 如果需要录像的话必须传正确的值, 一般是1或者2
     *
     * @return {0} if successful
     */
    public native int SmartPublisherPostAudioEncodedDataV3(long handle, int codec_id,
                                                           ByteBuffer data, int offset, int size,
                                                           int is_key_frame, long timestamp,
                                                           byte[] parameter_info, int parameter_info_size,
                                                           int sample_rate, int channels);

相关推荐
CHEtuzki1 天前
录播?无人直播?半无人直播?
ai·直播·抖音·电商
WilliamLuo2 天前
MP4结构初识-第一篇
前端·javascript·音视频开发
启明智显5 天前
视频直播5G CPE解决方案:ZX7981PG/ZX7981PMWIFI6网络覆盖
5g·直播·wifi6·5g cpe·无线路由器
音视频牛哥8 天前
Android平台如何拉取RTSP|RTMP流并转发至轻量级RTSP服务?
音视频开发·视频编码·直播
声知视界9 天前
音视频基础能力之 iOS 视频篇(一):视频采集
音视频开发
fareast_mzh9 天前
Setting Up a Simple Live Streaming Server on Debian 11
运维·debian·直播
关键帧Keyframe11 天前
音视频面试题集锦第 15 期 | 编辑 SDK 架构 | 直播回声 | 播放器架构
音视频开发·视频编码·客户端
伊织code14 天前
[2024最新] macOS 发起 Bilibili 直播(不使用 OBS)
macos·mac·web·直播·b站·bilibili
音视频开发技术15 天前
cannot locate symbol _ZTVNSt6__ndk119basic_ostringstreamIcNS_
android·直播
关键帧Keyframe16 天前
iOS 不用 libyuv 也能高效实现 RGB/YUV 数据转换丨音视频工业实战
音视频开发·视频编码·客户端