开放多媒体加快层(英语:Open Media Acceleration,缩写为OpenMAX),一个不需求授权、跨平台的软件抽象层,以C语言实现的软件接口,用来处理多媒体。它是由Khronos Group提出的规范,也由他们来保持,方针在于发明一个一致的接口,加快大量多媒体材料的处理。
1、数据格式和OMX输入缓冲
1.1 帧开端代码
一般不必,H.264或许运用。
1.2 OMX缓冲区
三个值得信任的关键参数 nFilledLen 缓冲区长度 nTimestamp 缓冲区时间戳 OMX_BUFFERLAG_ENDOFFRAME 缓冲区完毕标志位
1.3多帧合并输入缓冲
一些音频信息,单帧过小(eg ARM),将其合并作为一个缓冲区处理。 nFilledLen为一切帧总长度,nTimestamp指向缓冲区第一帧时间。
1.4部分帧
视频解码单帧过大情况下,或许将单帧拆分后传递给缓冲区。 部分帧情况下,只要终究一帧的缓冲区才具有OMX_BUFFERLAG_ENDOFFRAME。 部分帧缓冲区不会包含两帧信息。 流媒体或许包含多帧。 部分帧的nTimestamp应当相同。
总结:OMX输出缓冲区或许包含 ——完好多帧 ——完好单帧 ——部分帧
1.5 过错的数据封装
多帧的部分帧封装 eg wrong(Frame1+Frame2 part)
1.6 Codec装备数据
Codec装备缓冲区运用OMX_BUFFERLAG_ENDOFFRAME 和OMX_BUFFERFLAG_CODECCONFIG标志位。 H.264的SPS和PPS运用独立的OMX输入缓冲区。
2、H264/AVC 解码器格式
Codec装备头部: SPS和PPS NAL单元坐落开端的OMX输入缓冲区。 SPS和PPS NALs运用独立的OMX输入缓冲区,并运用OMX_BUFFERLAG_ENDOFFRAME 和OMX_BUFFERFLAG_CODECCONFIG符号。
2.1 AVC NAL形式与AVC Frame形式
经过设置iOMXComponentUsesFullAVCFrame标志位,能够决定AVC数据运用哪种形式解码。 默认运用NAL形式,此种形式下OpenCORE结构一起供给完好单帧和部分帧输入缓冲区。 在Frame形式下,OpenCORE结构积累NALs并供给完好单帧给输入缓冲区。 OMX_OTHER_EXTRADATA结构体用来区分NAL鸿沟。 假如iOMXComponentUsesFullAVCFrame和iOMXComponentUsesNALStratCodes都被置为OMX_TRUE, NAL鸿沟可被start codes区分,此刻OMX_OTHER_EXTRADATA无用。
数据结构——NAL形式: 输入缓冲区包含一个或多个NAL,但只包含同一帧的NAL,一帧终究一个NAL才含有OMX_BUFFERLAG_ENDOFFRAME标志位。
数据结构——Frame形式: 每个输入缓冲区包含完好帧。 假如运用NAL start codes,可经过读取NAL start codes区分NAL鸿沟。 不然运用OMX_OTHER_EXTRADATA结构体区分NAL鸿沟。 在Frame形式中,每个缓冲区都含有OMX_BUFFERLAG_ENDOFFRAME标志位。 在Frame形式中,每个缓冲区都含有坐落OMX_BUFFERLAGHEADERTYPE结构体nFlags区域的OMX_BUFFERLAG_EXTRADATA标志位。
缓冲区终究包含AVC frame,追加以下数据: OMX_OTHER_EXTRADATATYPE extra; OMX_OTHER_EXTRADATATYPE terminator;
extra.eType = OMX_ExtraDataNALSizeArray; extra.nSize = 20+4*(number of NALs in the frame); // 20 is the size of OMX_OTHER_EXTRADATATYPE structure + 4 bytes per NAL size extra.nDataSize = 4 * (number of NALs in the frame) extra.data[4i] = size of the i-th NAL (data is declared as byte array – so offset is 4i, since 4 bytes is assigned to signal the size of each NAL unit) terminator.eType = OMX_ExtraDataNone; terminator.nSize = 20; terminator.nDataSize = 0;
#define OMX_ExtraDataNALSizeArray 0x7F123321
经过获取OMX_OTHER_EXTRADATA结构体信息,能够得知每一帧包含NAL单元的数目并确定NAL鸿沟。
一个比如:AVC Frame形式,包含2个NAL,包含extra数据结构 总结: 1)每个缓冲区都含有坐落OMX_BUFFERLAGHEADERTYPE结构体nFlags区域的OMX_BUFFERLAG_EXTRADATA标志位 2)每个NAL的长度应当运用独立的4byte无符号整型数表示(eg OMX_U32) 3)一切NAL的长度被编码成OMX_U32的数组存放在buffer终究。 4)包含完好帧的缓冲区有必要含有坐落OMX_BUFFERLAGHEADERTYPE结构体nFlags区域的OMX_BUFFERLAG_ENDOFFRAME标志位。 5)一个独立的缓冲区不包含多帧数据。
3、YUV和RGB数据格式
OMX编码组件中,生肉供给YUV或者RGB格式,OpenCORE结构将供给一帧完结的YUR或RGB数据给OMX组件。
OpenMax 调用次序(OpenMax Call Sequences) 1 OMX 中心初始化 _OMX_MasterInit
1)调用OMX_Init函数
->OsclInit::Init(error, &select); //init all Oscl layers except Oscl scheduler. ->Try_OMX_Create(error, data); //create the OMX singleton ->OsclSingletonRegistry::registerInstanceAndUnlock(data, OSCL_SINGLETON_ID_OMX, error); //Release the singleton. ->Try_OMX_Init(error, status); //If create succeeded, then init the OMX globals.
2)PV结构列举一切OMX
OMX_ComponentNameEnum //列举一切组件的名称 ->OsclSingletonRegistry::getInstance(OSCL_SINGLETON_ID_OMX, error); ->oscl_strncpy(cComponentName, (data->ipRegTemplateList[Index])->ComponentName, nNameLength); OMX_GetRolesOfComponent // 经过组件名称找到组件,回来其人物(role) ->OsclSingletonRegistry::getInstance(OSCL_SINGLETON_ID_OMX, error); ->data->ipRegTemplateList[ii])->GetRolesOfComponent(RoleString) ->oscl_strncpy((OMX_STRING) roles[ii], (OMX_STRING)RoleString[ii], oscl_strlen((OMX_STRING)RoleString[ii]) + 1);
2 OMX组件实例、功用及端口 OMX中心初始化后,下一步为列举每个组件的功用和端口。
1)调用OMX_GetHandle获取所需的OMX组件信息。
2)调用OMX_GetParameter
及“PV_OMX_CAPABILITY_TYPE_INDEX”这个index去获取组件的功用。 万一组件不是OpenMax全兼容或者OpenMax的特性不明确,以上获取的功用决定了OMX是否支撑输入/输出端口“UseBufeer”和“AllocateBuffer”调用,以及OMX是否支撑部分帧等等。
留意:假如OMX组件回来“OMX_ErrorUnsupportedIndex”给index(或其他比方“OMX_ErrorNone”),PV结构将为组件功用赋默认值。
3)调用OMX_GetParameter,针对视频组件再调用“OMX_IndexVideoInit”,针对音频组件则调用“OMX_IndexAudioIni”以获取可用的端口号。
4)循环查找可用的端口号以找到输入端口。
5)循环查找可用的端口号以找到输出端口。
留意: Index “PV_OMX_CAPABILITY_TYPE_INDEX” is defined as: #define PV_OMX_COMPONENT_CAPABILITY_TYPE_INDEX 0xFF7A347 The OMX_GetParameter call expects the following structure to be filled for this index: typedef struct PV_OMXComponentCapabilityFlagsType { ////////////////// OMX COMPONENT CAPABILITY RELATED MEMBERS OMX_BOOL iIsOMXComponentMultiThreaded; OMX_BOOL iOMXComponentSupportsExternalOutputBufferAlloc; OMX_BOOL iOMXComponentSupportsExternalInputBufferAlloc; OMX_BOOL iOMXComponentSupportsMovableInputBuffers; OMX_BOOL iOMXComponentSupportsPartialFrames; OMX_BOOL iOMXComponentUsesNALStartCode; OMX_BOOL iOMXComponentCanHandleIncompleteFrames; OMX_BOOL iOMXComponentUsesFullAVCFrames; } PV_OMXComponentCapabilityFlagsType;
功用参数的默认值:
1)iIsOMXComponentMultiThreaded ——默认值OMX_TRUE。
OMX组件一般运转与独立的线程(与PV结构线程不同),有或许将OMX组件集成进PV结构线程(e.g.,经过同步调用)。
2)iOMXComponentSupportsExternalOutputBufferAlloc ——默认值OMX_TRUE。
OMX规范要求OMX组件支撑外部分配输出缓冲(便是输出缓冲的OMX_UseBuffer调用)。 假如组件不支撑,有必要告诉PV结构,以便其调用“OMX_AllocateBuffer”替代。
3)iOMXComponentSupportsExternalInputBufferAlloc ——默认值OMX_TRUE。
OMX规范要求OMX组件支撑外部分配输入缓冲(便是输入缓冲的OMX_UseBuffer调用)。 假如组件不支撑,有必要告诉PV结构,以便其调用“OMX_AllocateBuffer”替代。
4)iOMXComponentSupportsMovableInputBuffers ——默认值OMX_TRUE。
假如OMX缓冲是外部分配的,为了进步稳定性和优化功用,能够别离OMX缓冲头部信息(OMX_BUFFERHEADERTYPE)与数据区(“pBuffer”)。换句话说,使OMX缓冲更有“移动性”,当传递一个输入缓冲到OMX组件时,“pBuffer”区域和头部信息不一定指向相同的缓冲区。因而,能够分配更多的数据缓冲在结构中循环作业,只要在需求把缓冲传递到OMX组件时才附加到头部信息去。假如OMX组件要求头部和数据一向指向相同的缓冲,则iOMXComponentSupportsMovableInputBuffers应该被置为OMX_FALSE。
5)iOMXComponentSupportsPartialFrames ——默认值OMX_TRUE。
OMX规范要求OMX组件支撑将恣意数据打包进OMX缓冲,包含独立单帧、NAL和被拆分到多个缓冲区的解码单元。PV结构支撑“OMX_BUFFERFLAG_ENDOFFRAME”符号去拼装部分帧。但是,假如OMX组件不支撑拼装部分帧,也便是OMX组件要求PV结构来拼装部分帧并供给给OMX组件完好单帧或NAL,此刻iOMXComponentSupportsPartialFrames需求被设置为OMX_FALSE。 留意:设置为OMX_FALSE将影响功用。
6)iOMXComponentUsesNALStartCode ——默认值OMX_FALSE。
这个标志位将影响H264解码。PV结构供给一切信息以重树立H264 NALs(经过OMX缓冲的巨细和OMX_BUFFERFLAG_ENDOFFRAME的巨细)。因而没有必要由OMX组件解码输出流来获取0x0001的NAL初始代码。假如OMX的H264组件及解码单元需求NAL开端代码,可将iOMXComponentUsesNALStartCode置为OMX_TRUE。
7)iOMXComponentCanHandleIncompleteFrames ——默认值OMX_TRUE。
假如丢掉部分数据流(比方数据包丢掉),假定OMX组件及解码单元能够处理这个问题。假如不能够,则将iOMXComponentCanHandleIncompleteFrames 设置为OMX_FALSE。当“iOMXComponentSupportsPartialFrames”也被设置为OMX_FALSE时,这点恰当重要,因为OMX组件不供给拼装部分帧,也便是说PV结构有必要供给拼装好的frame/NALs,因而需求告诉OMX组件不完好的帧数据是否能够传递进来。
8)iOMXComponentUsesFullAVCFrames ——默认值OMX_FALSE。
这个标志位决定AVC解码中运用NAL形式还是frame形式。AVC数据可由以上两种形式供给给OMX组件。默认为NAL形式(设置为OMX_FALSE),此形式下OpenCore结构能够为OMX输入缓冲一起供给完好或者部分AVC NAL。Frame形式下(OMX_TRUE),OpenCore结构积累NAL,并供给给OMX输入缓冲一帧完好的数据。 NAL鸿沟的问起前面讨论过了,此处省掉。
3 OMX组件输入输出缓冲洽谈 在任何数据交换前,输入输出缓冲需求进行洽谈。PV结构需求做以下作业:
1)调用OMX_GetParameter获取输入端口缓冲参数(最小/实践缓冲数,缓冲区巨细等等) 2)查看有效的输入缓冲参数(修正一些参数,比方帧的长度和宽度,以及缓冲区的数目等等) 3)调用OMX_SetParameter函数设置输入缓冲参数 4)调用OMX_GetParameter获取输出端口缓冲参数(最小/实践缓冲数,缓冲区巨细等等) 5)查看有效的输出缓冲参数(修正一些参数,比方帧的长度和宽度,以及缓冲区的数目等等) 6)调用OMX_SetParameter函数设置输出缓冲参数
一些想象:
1)缓冲区尺度的想象:
关于编码器组件,终究分配的输出缓冲区尺度或许小于profile/level/target比特率所要求的最大帧尺度。出于内存消耗考虑,OMX编码器组件应当能够输出frame或NAL的比特流划分到多个输出缓冲区去。老调重弹,终究一个部分缓冲区运用“OMX_BUFFERFLAG_ENDOFFRAME”符号完毕。
关于解码器组件,终究分配的输入缓冲区尺度或许小于profile/level/target比特率所要求的最大帧尺度。出于内存消耗考虑,PV结构在恰当的时候供给采用“OMX_BUFFERFLAG_ENDOFFRAME”符号完毕的输入缓冲区。假如输入缓冲包含完好帧(H264也或许是NAL单元 )或多帧,“OMX_BUFFERFLAG_ENDOFFRAME”将会用来符号一帧的完毕。假如完好的frame/NAL不能放入一个输入缓冲区,则会被拆分放入多个缓冲区。包含部分信息的缓冲区,“OMX_BUFFERFLAG_ENDOFFRAME”符号位将会被置为0。一帧数据的终究一个缓冲区中“OMX_BUFFERFLAG_ENDOFFRAME”才会被置为1。经过这种做法,组件能够方便的重构一帧数据。
假如OMX解码器组件不兼容组长部分帧,PV结构将负责做这件工作。
2)输入/输出缓冲区数量。
结构或许期望分配比OMX要求更多的的输入/输出缓冲区以供给更优的功用(比方解码器输出缓冲或编码器输入缓冲——缓冲更多的数据能够进步功用)。虽然功用提升了,但无法送到OMX组件去(不太理解,应该还是洽谈不好的意思)。
4 OMX状况改换 加载->空闲 假如部分已分配的缓冲区进入空闲状况,缓冲区分配将挂起。在改变进程中,PV结构将“
1)经过“OMX_SendCommand”调用发送指令给OMX组件,将状况由OMX_StateLoaded改变为OMX_StateIdle 2)调用一系列“OMX_UseBuffer” 或者“OMX_AllocateBuffer”告诉OMX组件。这些调用运用NumInputBuffer记载输入输入端口的数目,运用NumOutputBuffer记载输入输入端口的数目。 3)等候OMX组件的EventHandler事情回调,告诉结构状况改换完结(OMX_EventCmdComplete)
一些想象与推荐参数:
根据OMX规范,兼容的OMX组件有必要一起支撑OMX_UseBuffer 和 OMX_AllocateBuffer调用。但是,出于一些内在原因,彻底实现是不或许的,组件应当告诉PV结构其所具有的功用。 推荐将INPUT缓冲分配在OMX组件外部,这样能够减少额外的内存拷贝(也便是说输入缓冲运用OMX_UseBuffer)。
5 改换到“履行”状况与数据交换 状况改变到履行时才开端真实处理数据。本进程中,PV结构将:
1)经过“OMX_SendCommand”调用发送指令给OMX组件,将状况由OMX_StateIdle改变为OMX_StateExecuting。
2)等候OMX组件的EventHandler事情回调,告诉结构状况改换完结(OMX_EventCmdComplete)。
3)经过OMX_EmptyThisBuffer调用将输入缓冲发送给OMX组件,经过OMX_FillThisBuffer调用将输出缓冲发送给OMX组件,组件运用合适的回调函数回来缓冲区。
留意:
1)在恣意时间OMX组件假如具有一切输入缓冲区(也便是一切的输入缓冲都经过EmptyThisBuffer调用传给了OMX组件),此刻将不能再传递任何输入缓冲区给OMX组件,知道OMX经过EmptyBufferDone开释一个缓冲。关于输出缓冲区同样。 2)假如PV结构没有及时将缓冲区发给OMX结构,OMX组件不会屡次回来同一个缓冲区(也便是一旦OMX结构回来一个缓冲区,只要PV结构再次调用这个缓冲,OMX组件才可运用)。这是OMX缓冲交换APSs所规则的基本规则。
6 暂停 PV结构常见的功用包含暂停和康复,PV结构将:
1)经过“OMX_SendCommand”调用发送指令给OMX组件,将状况由OMX_StateExecuting改变为OMX_StatePause。 2)等候OMX组件的EventHandler事情回调,告诉结构状况改换完结(OMX_EventCmdComplete)。
PV结构向OMX组件发送暂停指令后,立刻中止发送输入输出缓冲。
有暂停就有康复,PV结构将:
1)经过“OMX_SendCommand”调用发送指令给OMX组件,将状况由OMX_StatePause改变为OMX_StateExecuting。 2)等候OMX组件的EventHandler事情回调,告诉结构状况改换完结(OMX_EventCmdComplete)。
PV结构的状况康复到履行后,将康复发送OMX输入输出缓冲。
7 端口刷新(假如可用) 端口刷新调用一般用于重新装备解码器组件,这样会清空一切数据。在此情况下,PV结构将:
1)经过“OMX_SendCommand”调用发送刷新悉数端口指令。 2)等候输入缓冲的输出缓冲的两个EventHandler事情回调,告诉结构状况改换完结(OMX_EventCmdComplete)。
刷新指令来了之后,PV结构立刻中止向OMX组件发送输入/输出缓冲。当组件接收到2个回调函数后,PV结构开端康复发送输入/输出缓冲。端口刷新的次序和通告并不相关。
为了防止动态端口被重复装备,也能够先于OMX IL用户关闭OMX组件端口调用端口刷新指令。
8 中止/改变到“IDLE”状况 为了中止处理进程,PV结构将:
1)经过“OMX_SendCommand”调用发送指令给OMX组件,将状况由OMX_StatePause改变为OMX_StateIdle。 2)等候OMX组件的EventHandler事情回调,告诉结构状况改换完结(OMX_EventCmdComplete)。
一些想象: 当指令完结“IDEL”态的回调后,PV结构假设一切输入输出缓冲依照OMX规范要求那样回来。
9 OMX组件状况转换 IDLE->Loaded State和 De-initialization 当PV结构完毕与一个OMX组件的一切交互后,将:
1)经过“OMX_SendCommand”调用发送指令给OMX组件,将状况由OMX_StateIdle改变为OMX_StateLoaded。 2)向OMX组件发送一系列“OMX_FreeBuffer”调用。 3)等候OMX组件的EventHandler事情回调,告诉结构状况改换完结(OMX_EventCmdComplete)。运用NumInputBuffer记载输入输入端口的数目,运用NumOutputBuffer记载输入输入端口的数目。 4)对OMX中心履行OMX_FreeHandle调用,开释组件句柄。
留意:在向OMX组件发送卸载指令之前,PV结构一向在等候OMX组件回来的输入输出缓冲。由于回调的进程的不同步性,一些EmptyBufferDone/FillBufferDone的回调有或许在OMX组件状况由“executing” 到 “idle”之后才到达。
10 OMX Core 卸载 PV结构调用函数OMX_Deinit()。
OMX_Init()
没有什么好说的,初始化函数,一定要运转的.
OMX_GetHandle
得到某一个组件的句柄
OMX_API OMX_ERRORTYPE OMX_APIENTRY OMX_GetHandle(
OMX_OUT OMX_HANDLETYPE* pHandle,
OMX_IN OMX_STRING cComponentName,
OMX_IN OMX_PTR pAppData,
OMX_IN OMX_CALLBACKTYPE* pCallBacks);
OMX_GetExtensionIndex
除了规范OMX_INDEXTYPE中界说的以外,回来用户自界说的一些参数(第二个参数)对应的index(第三个参数,用在OMX_SetParameter…里面),用来configur或者parameter.
这些参数往往被界说在用户特别的extension interface里面
#define OMX_GetExtensionIndex( /
hComponent, /
cParameterName, /
pIndexType) /
((OMX_COMPONENTTYPE*)hComponent)->GetExtensionIndex( /
hComponent, /
cParameterName, /
pIndexType) /* Macro End */
OMX_SetParameter
设定某个参数对应的值
#define OMX_SetParameter( /
hComponent, /
nParamIndex, /
pComponentParameterStructure) /
((OMX_COMPONENTTYPE*)hComponent)->SetParameter( /
hComponent, /
nParamIndex, /
pComponentParameterStructure) /* Macro End */
OMX_SetConfig
设定某个config值
#define OMX_SetConfig( /
hComponent, /
nConfigIndex, /
pComponentConfigStructure) /
((OMX_COMPONENTTYPE*)hComponent)->SetConfig( /
hComponent, /
nConfigIndex, /
pComponentConfigStructure) /* Macro End */
留意有时候,需求先中止某个组件(的某些端口),才干设置config 成功
OMX_SendCommand
一般的指令有:
OMX_CommandStateSet 、OMX_CommandFlush、OMX_CommandPortDisable” 、 “OMX_CommandPortEnable、CommandMarkBuffer
#define OMX_SendCommand( /
hComponent, /
Cmd, /
nParam, /
pCmdData) /
((OMX_COMPONENTTYPE*)hComponent)->SendCommand( /
hComponent, /
Cmd, /
nParam, /
pCmdData) /* Macro End */
比如:OMXSAFE(OMX_SendCommand(vrenderer, OMX_CommandPortEnable, 1, 0)); // 停下1对应的端口
OMX_SetupTunnel
将两个组件连接起来,实践会引起调用每个组件的ComponentTunnelRequest
OMX_API OMX_ERRORTYPE OMX_APIENTRY OMX_SetupTunnel(
OMX_IN OMX_HANDLETYPE hOutput,
OMX_IN OMX_U32 nPortOutput,
OMX_IN OMX_HANDLETYPE hInput,
OMX_IN OMX_U32 nPortInput);
比如:
OMXSAFE(OMX_SetupTunnel(reader, 0, vdecoder, 0)); // reader的0端口为出,vdecoder的0端口为入,连接成一个Tunnel
准备好后,就能够设置OMX_StateExecuting,来让这个流程活动起来了。再以后,就能够经过OMX_StateIdle 来停下。 OMX_GetState
#define OMX_GetState( /
hComponent, /
pState) /
((OMX_COMPONENTTYPE*)hComponent)->GetState( /
hComponent, /
pState) /* Macro End */
以上便是OpenMAX中的——数据格式及OMX输入缓冲细节解析,更多音视频中心技术学习参阅传送直达↓↓↓ :www.6hu.cc/go//?target=htt…
音视频开发很难有必要需求系统性的学习,网上的音视频材料很多大多数比较凌乱,没有比较全面的因而。我从音视频开发网易大牛手上拿到这份笔记。依照音视频高工所记载的笔记,期望能够帮助到咱们。
补充:
1:
OMX_PARAM_PORTDEFINITIONTYPE decOutputPortDef;
INIT_PARAM(decOutputPortDef);
decOutputPortDef.nPortIndex = 0;
err = OMX_GetParameter(pCtx->hReaderComp,
OMX_IndexParamPortDefinition,
&decOutputPortDef); // 使用IndexParamPortDefinition来得到组件的输出端口的特点
videoWidth = decOutputPortDef.format.video.nFrameWidth;
videoHeight = decOutputPortDef.format.video.nFrameHeight;
2:
OMX_BUFFERHEADERTYPE *pOmxBufferHeader ;
// tell decoder output port that it will be using our buffer
• err = OMX_UseBuffer(hDecodeComp,
• &pOmxBufferHeader, //out
• OMX_DECODE_OUTPUT_PORT,
• NULL,
• outSize,
• (NvU8*)pOut);
将分配好的pOut指针和他的巨细outSize,配成一个OMX_BUF, 并给pOmxBufferHeader,这样就经过OMX_UseBuffer,来得到一个以后能给他用的Buffer,指针用咱们分配的。
3:
OMXErr = OMX_FillThisBuffer(hDecodeComp, pOmxBufferHeader);
让hDecodeComp将pOmxBufferHeader的数据准备好(便是输出数据),pOmxBufferHeader便是经过UseBuffer来拼装的。
经过nFilledLen,能够得到详细数据填充情况。
假如OMX_FillThisBuffer是异步函数的话,那真实的回来是要靠:Callback_FillBufferDone 来设置真实数据ready信号