嵌入式 H264视频通过RTMP直播
视频通过RTMP方式发布需要一个RTMP Server(常见的有FMS、Wowza Media Server, 开源的有CRtmpServer、Red5等),原始视频只要按照RTMP协议发送给RTMP Server就可以RTMP视频流的发布了。为了便于视频的打包发布,封装了一个RTMPStream,目前只支持发送H264的视频文件。可以直接发送H264数据帧或H264文件,RTMPStream提供的接口如下。
- class CRTMPStream
- {
- public:
- CRTMPStream(void);
- ~CRTMPStream(void);
- public:
- // 连接到RTMP Server
- bool Connect(const char* url);
- // 断开连接
- void Close();
- // 发送MetaData
- bool SendMetadata(LPRTMPMetadata lpMetaData);
- // 发送H264数据帧
- bool SendH264Packet(unsigned char *data,unsigned int size,bool bIsKeyFrame,unsigned int nTimeStamp);
- // 发送H264文件
- bool SendH264File(const char *pFileName);
- //...
- }
调用示例:
- #include <stdio.h>
- #include "RTMPStream\RTMPStream.h"
- int main(int argc,char* argv[])
- {
- CRTMPStream rtmpSender;
- bool bRet = rtmpSender.Connect("rtmp://192.168.1.104/live/test");
- rtmpSender.SendH264File("E:\\video\\test.264");
- rtmpSender.Close();
- }
通过JwPlayer播放效果如下:
最后附上RTMPStream完整的代码:
- /********************************************************************
- filename: RTMPStream.h
- created: 2013-04-3
- author: firehood
- purpose: 发送H264视频到RTMP Server,使用libRtmp库
- *********************************************************************/
- #pragma once
- #include "rtmp.h"
- #include "rtmp_sys.h"
- #include "amf.h"
- #include <stdio.h>
- #define FILEBUFSIZE (1024 * 1024 * 10) // 10M
- // NALU单元
- typedef struct _NaluUnit
- {
- int type;
- int size;
- unsigned char *data;
- }NaluUnit;
- typedef struct _RTMPMetadata
- {
- // video, must be h264 type
- unsigned int nWidth;
- unsigned int nHeight;
- unsigned int nFrameRate; // fps
- unsigned int nVideoDataRate; // bps
- unsigned int nSpsLen;
- unsigned char Sps[1024];
- unsigned int nPpsLen;
- unsigned char Pps[1024];
- // audio, must be aac type
- bool bHasAudio;
- unsigned int nAudioSampleRate;
- unsigned int nAudioSampleSize;
- unsigned int nAudioChannels;
- char pAudioSpecCfg;
- unsigned int nAudioSpecCfgLen;
- } RTMPMetadata,*LPRTMPMetadata;
- class CRTMPStream
- {
- public:
- CRTMPStream(void);
- ~CRTMPStream(void);
- public:
- // 连接到RTMP Server
- bool Connect(const char* url);
- // 断开连接
- void Close();
- // 发送MetaData
- bool SendMetadata(LPRTMPMetadata lpMetaData);
- // 发送H264数据帧
- bool SendH264Packet(unsigned char *data,unsigned int size,bool bIsKeyFrame,unsigned int nTimeStamp);
- // 发送H264文件
- bool SendH264File(const char *pFileName);
- private:
- // 送缓存中读取一个NALU包
- bool ReadOneNaluFromBuf(NaluUnit &nalu);
- // 发送数据
- int SendPacket(unsigned int nPacketType,unsigned char *data,unsigned int size,unsigned int nTimestamp);
- private:
- RTMP* m_pRtmp;
- unsigned char* m_pFileBuf;
- unsigned int m_nFileBufSize;
- unsigned int m_nCurPos;
- };
- /********************************************************************
- filename: RTMPStream.cpp
- created: 2013-04-3
- author: firehood
- purpose: 发送H264视频到RTMP Server,使用libRtmp库
- *********************************************************************/
- #include "RTMPStream.h"
- #include "SpsDecode.h"
- #ifdef WIN32
- #include <windows.h>
- #endif
- #ifdef WIN32
- #pragma comment(lib,"WS2_32.lib")
- #pragma comment(lib,"winmm.lib")
- #endif
- enum
- {
- FLV_CODECID_H264 = 7,
- };
- int InitSockets()
- {
- #ifdef WIN32
- WORD version;
- WSADATA wsaData;
- version = MAKEWORD(1, 1);
- return (WSAStartup(version, &wsaData) == 0);
- #else
- return TRUE;
- #endif
- }
- inline void CleanupSockets()
- {
- #ifdef WIN32
- WSACleanup();
- #endif
- }
- char * put_byte( char *output, uint8_t nVal )
- {
- output[0] = nVal;
- return output+1;
- }
- char * put_be16(char *output, uint16_t nVal )
- {
- output[1] = nVal & 0xff;
- output[0] = nVal >> 8;
- return output+2;
- }
- char * put_be24(char *output,uint32_t nVal )
- {
- output[2] = nVal & 0xff;
- output[1] = nVal >> 8;
- output[0] = nVal >> 16;
- return output+3;
- }
- char * put_be32(char *output, uint32_t nVal )
- {
- output[3] = nVal & 0xff;
- output[2] = nVal >> 8;
- output[1] = nVal >> 16;
- output[0] = nVal >> 24;
- return output+4;
- }
- char * put_be64( char *output, uint64_t nVal )
- {
- output=put_be32( output, nVal >> 32 );
- output=put_be32( output, nVal );
- return output;
- }
- char * put_amf_string( char *c, const char *str )
- {
- uint16_t len = strlen( str );
- c=put_be16( c, len );
- memcpy(c,str,len);
- return c+len;
- }
- char * put_amf_double( char *c, double d )
- {
- *c++ = AMF_NUMBER; /* type: Number */
- {
- unsigned char *ci, *co;
- ci = (unsigned char *)&d;
- co = (unsigned char *)c;
- co[0] = ci[7];
- co[1] = ci[6];
- co[2] = ci[5];
- co[3] = ci[4];
- co[4] = ci[3];
- co[5] = ci[2];
- co[6] = ci[1];
- co[7] = ci[0];
- }
- return c+8;
- }
- CRTMPStream::CRTMPStream(void):
- m_pRtmp(NULL),
- m_nFileBufSize(0),
- m_nCurPos(0)
- {
- m_pFileBuf = new unsigned char[FILEBUFSIZE];
- memset(m_pFileBuf,0,FILEBUFSIZE);
- InitSockets();
- m_pRtmp = RTMP_Alloc();
- RTMP_Init(m_pRtmp);
- }
- CRTMPStream::~CRTMPStream(void)
- {
- Close();
- WSACleanup();
- delete[] m_pFileBuf;
- }
- bool CRTMPStream::Connect(const char* url)
- {
- if(RTMP_SetupURL(m_pRtmp, (char*)url)<0)
- {
- return FALSE;
- }
- RTMP_EnableWrite(m_pRtmp);
- if(RTMP_Connect(m_pRtmp, NULL)<0)
- {
- return FALSE;
- }
- if(RTMP_ConnectStream(m_pRtmp,0)<0)
- {
- return FALSE;
- }
- return TRUE;
- }
- void CRTMPStream::Close()
- {
- if(m_pRtmp)
- {
- RTMP_Close(m_pRtmp);
- RTMP_Free(m_pRtmp);
- m_pRtmp = NULL;
- }
- }
- int CRTMPStream::SendPacket(unsigned int nPacketType,unsigned char *data,unsigned int size,unsigned int nTimestamp)
- {
- if(m_pRtmp == NULL)
- {
- return FALSE;
- }
- RTMPPacket packet;
- RTMPPacket_Reset(&packet);
- RTMPPacket_Alloc(&packet,size);
- packet.m_packetType = nPacketType;
- packet.m_nChannel = 0x04;
- packet.m_headerType = RTMP_PACKET_SIZE_LARGE;
- packet.m_nTimeStamp = nTimestamp;
- packet.m_nInfoField2 = m_pRtmp->m_stream_id;
- packet.m_nBodySize = size;
- memcpy(packet.m_body,data,size);
- int nRet = RTMP_SendPacket(m_pRtmp,&packet,0);
- RTMPPacket_Free(&packet);
- return nRet;
- }
- bool CRTMPStream::SendMetadata(LPRTMPMetadata lpMetaData)
- {
- if(lpMetaData == NULL)
- {
- return false;
- }
- char body[1024] = {0};;
- char * p = (char *)body;
- p = put_byte(p, AMF_STRING );
- p = put_amf_string(p , "@setDataFrame" );
- p = put_byte( p, AMF_STRING );
- p = put_amf_string( p, "onMetaData" );
- p = put_byte(p, AMF_OBJECT );
- p = put_amf_string( p, "copyright" );
- p = put_byte(p, AMF_STRING );
- p = put_amf_string( p, "firehood" );
- p =put_amf_string( p, "width");
- p =put_amf_double( p, lpMetaData->nWidth);
- p =put_amf_string( p, "height");
- p =put_amf_double( p, lpMetaData->nHeight);
- p =put_amf_string( p, "framerate" );
- p =put_amf_double( p, lpMetaData->nFrameRate);
- p =put_amf_string( p, "videocodecid" );
- p =put_amf_double( p, FLV_CODECID_H264 );
- p =put_amf_string( p, "" );
- p =put_byte( p, AMF_OBJECT_END );
- int index = p-body;
- SendPacket(RTMP_PACKET_TYPE_INFO,(unsigned char*)body,p-body,0);
- int i = 0;
- body[i++] = 0x17; // 1:keyframe 7:AVC
- body[i++] = 0x00; // AVC sequence header
- body[i++] = 0x00;
- body[i++] = 0x00;
- body[i++] = 0x00; // fill in 0;
- // AVCDecoderConfigurationRecord.
- body[i++] = 0x01; // configurationVersion
- body[i++] = lpMetaData->Sps[1]; // AVCProfileIndication
- body[i++] = lpMetaData->Sps[2]; // profile_compatibility
- body[i++] = lpMetaData->Sps[3]; // AVCLevelIndication
- body[i++] = 0xff; // lengthSizeMinusOne
- // sps nums
- body[i++] = 0xE1; //&0x1f
- // sps data length
- body[i++] = lpMetaData->nSpsLen>>8;
- body[i++] = lpMetaData->nSpsLen&0xff;
- // sps data
- memcpy(&body[i],lpMetaData->Sps,lpMetaData->nSpsLen);
- i= i+lpMetaData->nSpsLen;
- // pps nums
- body[i++] = 0x01; //&0x1f
- // pps data length
- body[i++] = lpMetaData->nPpsLen>>8;
- body[i++] = lpMetaData->nPpsLen&0xff;
- // sps data
- memcpy(&body[i],lpMetaData->Pps,lpMetaData->nPpsLen);
- i= i+lpMetaData->nPpsLen;
- return SendPacket(RTMP_PACKET_TYPE_VIDEO,(unsigned char*)body,i,0);
- }
- bool CRTMPStream::SendH264Packet(unsigned char *data,unsigned int size,bool bIsKeyFrame,unsigned int nTimeStamp)
- {
- if(data == NULL && size<11)
- {
- return false;
- }
- unsigned char *body = new unsigned char[size+9];
- int i = 0;
- if(bIsKeyFrame)
- {
- body[i++] = 0x17;// 1:Iframe 7:AVC
- }
- else
- {
- body[i++] = 0x27;// 2:Pframe 7:AVC
- }
- body[i++] = 0x01;// AVC NALU
- body[i++] = 0x00;
- body[i++] = 0x00;
- body[i++] = 0x00;
- // NALU size
- body[i++] = size>>24;
- body[i++] = size>>16;
- body[i++] = size>>8;
- body[i++] = size&0xff;;
- // NALU data
- memcpy(&body[i],data,size);
- bool bRet = SendPacket(RTMP_PACKET_TYPE_VIDEO,body,i+size,nTimeStamp);
- delete[] body;
- return bRet;
- }
- bool CRTMPStream::SendH264File(const char *pFileName)
- {
- if(pFileName == NULL)
- {
- return FALSE;
- }
- FILE *fp = fopen(pFileName, "rb");
- if(!fp)
- {
- printf("ERROR:open file %s failed!",pFileName);
- }
- fseek(fp, 0, SEEK_SET);
- m_nFileBufSize = fread(m_pFileBuf, sizeof(unsigned char), FILEBUFSIZE, fp);
- if(m_nFileBufSize >= FILEBUFSIZE)
- {
- printf("warning : File size is larger than BUFSIZE\n");
- }
- fclose(fp);
- RTMPMetadata metaData;
- memset(&metaData,0,sizeof(RTMPMetadata));
- NaluUnit naluUnit;
- // 读取SPS帧
- ReadOneNaluFromBuf(naluUnit);
- metaData.nSpsLen = naluUnit.size;
- memcpy(metaData.Sps,naluUnit.data,naluUnit.size);
- // 读取PPS帧
- ReadOneNaluFromBuf(naluUnit);
- metaData.nPpsLen = naluUnit.size;
- memcpy(metaData.Pps,naluUnit.data,naluUnit.size);
- // 解码SPS,获取视频图像宽、高信息
- int width = 0,height = 0;
- h264_decode_sps(metaData.Sps,metaData.nSpsLen,width,height);
- metaData.nWidth = width;
- metaData.nHeight = height;
- metaData.nFrameRate = 25;
- // 发送MetaData
- SendMetadata(&metaData);
- unsigned int tick = 0;
- while(ReadOneNaluFromBuf(naluUnit))
- {
- bool bKeyframe = (naluUnit.type == 0x05) ? TRUE : FALSE;
- // 发送H264数据帧
- SendH264Packet(naluUnit.data,naluUnit.size,bKeyframe,tick);
- msleep(40);
- tick +=40;
- }
- return TRUE;
- }
- bool CRTMPStream::ReadOneNaluFromBuf(NaluUnit &nalu)
- {
- int i = m_nCurPos;
- while(i<m_nFileBufSize)
- {
- if(m_pFileBuf[i++] == 0x00 &&
- m_pFileBuf[i++] == 0x00 &&
- m_pFileBuf[i++] == 0x00 &&
- m_pFileBuf[i++] == 0x01
- )
- {
- int pos = i;
- while (pos<m_nFileBufSize)
- {
- if(m_pFileBuf[pos++] == 0x00 &&
- m_pFileBuf[pos++] == 0x00 &&
- m_pFileBuf[pos++] == 0x00 &&
- m_pFileBuf[pos++] == 0x01
- )
- {
- break;
- }
- }
- if(pos == nBufferSize)
- {
- nalu.size = pos-i;
- }
- else
- {
- nalu.size = (pos-4)-i;
- }
- nalu.type = m_pFileBuf[i]&0x1f;
- nalu.data = &m_pFileBuf[i];
- m_nCurPos = pos-4;
- return TRUE;
- }
- }
- return FALSE;
- }
附上SpsDecode.h文件:
- #include <stdio.h>
- #include <math.h>
- UINT Ue(BYTE *pBuff, UINT nLen, UINT &nStartBit)
- {
- //计算0bit的个数
- UINT nZeroNum = 0;
- while (nStartBit < nLen * 8)
- {
- if (pBuff[nStartBit / 8] & (0x80 >> (nStartBit % 8))) //&:按位与,%取余
- {
- break;
- }
- nZeroNum++;
- nStartBit++;
- }
- nStartBit ++;
- //计算结果
- DWORD dwRet = 0;
- for (UINT i=0; i<nZeroNum; i++)
- {
- dwRet <<= 1;
- if (pBuff[nStartBit / 8] & (0x80 >> (nStartBit % 8)))
- {
- dwRet += 1;
- }
- nStartBit++;
- }
- return (1 << nZeroNum) - 1 + dwRet;
- }
- int Se(BYTE *pBuff, UINT nLen, UINT &nStartBit)
- {
- int UeVal=Ue(pBuff,nLen,nStartBit);
- double k=UeVal;
- int nValue=ceil(k/2);//ceil函数:ceil函数的作用是求不小于给定实数的最小整数。ceil(2)=ceil(1.2)=cei(1.5)=2.00
- if (UeVal % 2==0)
- nValue=-nValue;
- return nValue;
- }
- DWORD u(UINT BitCount,BYTE * buf,UINT &nStartBit)
- {
- DWORD dwRet = 0;
- for (UINT i=0; i<BitCount; i++)
- {
- dwRet <<= 1;
- if (buf[nStartBit / 8] & (0x80 >> (nStartBit % 8)))
- {
- dwRet += 1;
- }
- nStartBit++;
- }
- return dwRet;
- }
- bool h264_decode_sps(BYTE * buf,unsigned int nLen,int &width,int &height)
- {
- UINT StartBit=0;
- int forbidden_zero_bit=u(1,buf,StartBit);
- int nal_ref_idc=u(2,buf,StartBit);
- int nal_unit_type=u(5,buf,StartBit);
- if(nal_unit_type==7)
- {
- int profile_idc=u(8,buf,StartBit);
- int constraint_set0_flag=u(1,buf,StartBit);//(buf[1] & 0x80)>>7;
- int constraint_set1_flag=u(1,buf,StartBit);//(buf[1] & 0x40)>>6;
- int constraint_set2_flag=u(1,buf,StartBit);//(buf[1] & 0x20)>>5;
- int constraint_set3_flag=u(1,buf,StartBit);//(buf[1] & 0x10)>>4;
- int reserved_zero_4bits=u(4,buf,StartBit);
- int level_idc=u(8,buf,StartBit);
- int seq_parameter_set_id=Ue(buf,nLen,StartBit);
- if( profile_idc == 100 || profile_idc == 110 ||
- profile_idc == 122 || profile_idc == 144 )
- {
- int chroma_format_idc=Ue(buf,nLen,StartBit);
- if( chroma_format_idc == 3 )
- int residual_colour_transform_flag=u(1,buf,StartBit);
- int bit_depth_luma_minus8=Ue(buf,nLen,StartBit);
- int bit_depth_chroma_minus8=Ue(buf,nLen,StartBit);
- int qpprime_y_zero_transform_bypass_flag=u(1,buf,StartBit);
- int seq_scaling_matrix_present_flag=u(1,buf,StartBit);
- int seq_scaling_list_present_flag[8];
- if( seq_scaling_matrix_present_flag )
- {
- for( int i = 0; i < 8; i++ ) {
- seq_scaling_list_present_flag[i]=u(1,buf,StartBit);
- }
- }
- }
- int log2_max_frame_num_minus4=Ue(buf,nLen,StartBit);
- int pic_order_cnt_type=Ue(buf,nLen,StartBit);
- if( pic_order_cnt_type == 0 )
- int log2_max_pic_order_cnt_lsb_minus4=Ue(buf,nLen,StartBit);
- else if( pic_order_cnt_type == 1 )
- {
- int delta_pic_order_always_zero_flag=u(1,buf,StartBit);
- int offset_for_non_ref_pic=Se(buf,nLen,StartBit);
- int offset_for_top_to_bottom_field=Se(buf,nLen,StartBit);
- int num_ref_frames_in_pic_order_cnt_cycle=Ue(buf,nLen,StartBit);
- int *offset_for_ref_frame=new int[num_ref_frames_in_pic_order_cnt_cycle];
- for( int i = 0; i < num_ref_frames_in_pic_order_cnt_cycle; i++ )
- offset_for_ref_frame[i]=Se(buf,nLen,StartBit);
- delete [] offset_for_ref_frame;
- }
- int num_ref_frames=Ue(buf,nLen,StartBit);
- int gaps_in_frame_num_value_allowed_flag=u(1,buf,StartBit);
- int pic_width_in_mbs_minus1=Ue(buf,nLen,StartBit);
- int pic_height_in_map_units_minus1=Ue(buf,nLen,StartBit);
- width=(pic_width_in_mbs_minus1+1)*16;
- height=(pic_height_in_map_units_minus1+1)*16;
- return true;
- }
- else
- return false;
- }
嵌入式 H264视频通过RTMP直播的更多相关文章
- H264视频通过RTMP直播
http://blog.csdn.net/firehood_/article/details/8783589 前面的文章中提到了通过RTSP(Real Time Streaming Protocol) ...
- 公布一个软件,轻新视频录播程序,H264/AAC录制视音频,保存FLV,支持RTMP直播
已经上传到CSDN,下载地址:http://download.csdn.net/detail/avsuper/7421647,不要钱滴,嘿嘿... 本程序能够把摄像头视频和麦克风音频,录制为FLV文件 ...
- 实时监控、直播流、流媒体、视频网站开发方案流媒体服务器搭建及配置详解:使用nginx搭建rtmp直播、rtmp点播、,hls直播服务配置详解
注意:这里不会讲到nginx流媒体模块如何安装的问题,只研究rtmp,hls直播和录制相关的nginx服务器配置文件的详细用法和说明.可以对照这些命令详解配置nginx -rtmp服务 一.nginx ...
- Android流媒体开发之路二:NDK开发Android端RTMP直播推流程序
NDK开发Android端RTMP直播推流程序 经过一番折腾,成功把RTMP直播推流代码,通过NDK交叉编译的方式,移植到了Android下,从而实现了Android端采集摄像头和麦克缝数据,然后进行 ...
- RTSP协议转换RTMP直播协议
RTSP协议转换RTMP直播协议 RTSP协议也是广泛使用的直播/点播流媒体协议,最近实现了一个RTSP协议转换RTMP直播协议的程序,为的是可以接收远端设备或服务器的多路RTSP直播数据,实时转换为 ...
- 园 首页 新随笔 联系 管理 订阅 订阅 RTSP协议转换RTMP直播协议
RTSP协议转换RTMP直播协议 RTSP协议也是广泛使用的直播/点播流媒体协议,最近实现了一个RTSP协议转换RTMP直播协议的程序,为的是可以接收远端设备或服务器的多路RTSP直播数据,实时转换为 ...
- 将EasyRTMP_RTSP移植到Android平台实现的RTSP拉流转推RTMP直播流功能
本文转自EasyDarwin开源团队成员Kim的博客:http://blog.csdn.net/jinlong0603/article/details/73253044 前言 安防互联网化的需求已经越 ...
- 三、直播整体流程 五、搭建Nginx+Rtmp直播流服务
HTML5实现视频直播功能思路详解_html5教程技巧_脚本之家 https://m.jb51.net/html5/587215.html 三.直播整体流程 直播整体流程大致可分为: 视频采集端:可以 ...
- 基于nginx的rtmp直播服务器(nginx-rtmp-module实现)
首先,在搭建服务之前先了解下目前主流的几个直播协议: 1.RTMP: 实时消息传输协议,Real Time Messaging Protocol,是 Adobe Systems 公司为 Flash 播 ...
随机推荐
- c# 事件为何要继承EventArgs
1:继承EventArgs是表示该类可作为事件,删掉了就默认继承object,没人会说你错 ----就是说事件不继承EventArgs 也没有错,也能正常运用,那么继承他的意义是什么呢?看2,3. 觉 ...
- hdu 4111 Alice and Bob(中档博弈题)
copy VS study 1.每堆部是1的时候,是3的倍数时输否则赢: 2.只有一堆2其他全是1的时候,1的堆数是3的倍数时输否则赢: 3.其他情况下,计算出总和+堆数-1,若为偶数,且1的堆数是偶 ...
- IE8 浏览器自动保存文档副本,添加缓存
若响应(response)HTTP头信息中没有关于缓存的头信息,则在IE8中第二次请求网页时,从缓存中拿取文件,而不是重新向服务器请求.而在Firefox或chrome则是重新向服务器请求. 解决方法 ...
- MDX语法
https://msdn.microsoft.com/zh-cn/library/ms145506.aspx
- Effective C++条款01: 视C++为一个语言联邦
一开始C++定义为:C with Classes. 如今的C++已经是一个多重范型编程语言,可以把C++视为有四个次语言组成的联邦语言. C.C++任然以C为基础.区块.语句.预处理.内置语言类型.数 ...
- .Net MVC视图
1.View显示 return View(): 默认为/Views/<控制器>/<方法> return View("Test"); 显示/View/< ...
- ANSI是什么编码?
用Notepad++创建一个文本文件text.txt,其默认编码格式为ANSI(乍看之下,还以为是ASCII呢),输入汉字居然不是乱码: 保存为test.txt,发送给你美国的同事Bob.他也用Not ...
- 大数据工具——Splunk
Splunk是机器数据的引擎.使用 Splunk 可收集.索引和利用所有应用程序.服务器和设备(物理.虚拟和云中)生成的快速移动型计算机数据 .从一个位置搜索并分析所有实时和历史数据. 使用 Splu ...
- 《OD学hadoop》在LINUX下如何将tar压缩文件解压到指定的目录下
linux下tar命令解压到指定的目录 :#tar zxvf /bbs.tar.zip -C /zzz/bbs //把根目录下的bbs.tar.zip解压到/zzz/bbs下,前提要保证存在/zzz/ ...
- JSON 之 SuperObject(5): Format 与转义字符
unit Unit1; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, For ...