688 lines
15 KiB
C++
688 lines
15 KiB
C++
/***************************************************************************************
|
|
*
|
|
* IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
|
|
*
|
|
* By downloading, copying, installing or using the software you agree to this license.
|
|
* If you do not agree to this license, do not download, install,
|
|
* copy or use the software.
|
|
*
|
|
* Copyright (C) 2014-2024, Happytimesoft Corporation, all rights reserved.
|
|
*
|
|
* Redistribution and use in binary forms, with or without modification, are permitted.
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software distributed
|
|
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
|
* CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
|
|
* language governing permissions and limitations under the License.
|
|
*
|
|
****************************************************************************************/
|
|
|
|
#include "sys_inc.h"
|
|
#include "srt_cln.h"
|
|
#include "h264.h"
|
|
#include "h265.h"
|
|
#include "media_format.h"
|
|
#include "bs.h"
|
|
#include "media_util.h"
|
|
|
|
|
|
void * srt_rx_thread(void * argv)
|
|
{
|
|
CSrtClient * pSrtClient = (CSrtClient *) argv;
|
|
|
|
pSrtClient->rx_thread();
|
|
|
|
return NULL;
|
|
}
|
|
|
|
void ts_payload_cb(uint8 * data, uint32 size, uint32 type, uint64 pts, void * userdata)
|
|
{
|
|
CSrtClient * pSrtClient = (CSrtClient *) userdata;
|
|
|
|
pSrtClient->srt_payload_cb(data, size, type, pts);
|
|
}
|
|
|
|
CSrtClient::CSrtClient()
|
|
: m_nport(0)
|
|
, m_fd(0)
|
|
, m_eid(0)
|
|
, m_bRunning(FALSE)
|
|
, m_bVideoReady(FALSE)
|
|
, m_bAudioReady(FALSE)
|
|
, m_tidRx(0)
|
|
, m_nVpsLen(0)
|
|
, m_nSpsLen(0)
|
|
, m_nPpsLen(0)
|
|
, m_nAudioConfigLen(0)
|
|
, m_nVideoCodec(VIDEO_CODEC_NONE)
|
|
, m_nWidth(0)
|
|
, m_nHeight(0)
|
|
, m_nFrameRate(0)
|
|
, m_nAudioCodec(AUDIO_CODEC_NONE)
|
|
, m_nSamplerate(44100)
|
|
, m_nChannels(2)
|
|
, m_nVideoInitTS(0)
|
|
, m_nAudioInitTS(0)
|
|
, m_pNotify(NULL)
|
|
, m_pUserdata(NULL)
|
|
, m_pVideoCB(NULL)
|
|
, m_pAudioCB(NULL)
|
|
{
|
|
memset(m_url, 0, sizeof(m_url));
|
|
memset(m_user, 0, sizeof(m_user));
|
|
memset(m_pass, 0, sizeof(m_pass));
|
|
memset(m_ip, 0, sizeof(m_ip));
|
|
memset(m_streamid, 0, sizeof(m_streamid));
|
|
|
|
memset(&m_pVps, 0, sizeof(m_pVps));
|
|
memset(&m_pSps, 0, sizeof(m_pVps));
|
|
memset(&m_pPps, 0, sizeof(m_pVps));
|
|
memset(&m_pAudioConfig, 0, sizeof(m_pAudioConfig));
|
|
|
|
m_pMutex = sys_os_create_mutex();
|
|
|
|
m_nRxTimeout = 10; // default 10s timeout
|
|
|
|
ts_parser_init(&m_tsParser);
|
|
|
|
ts_parser_set_cb(&m_tsParser, ts_payload_cb, this);
|
|
}
|
|
|
|
CSrtClient::~CSrtClient()
|
|
{
|
|
srt_close();
|
|
|
|
if (m_pMutex)
|
|
{
|
|
sys_os_destroy_sig_mutex(m_pMutex);
|
|
m_pMutex = NULL;
|
|
}
|
|
|
|
ts_parser_free(&m_tsParser);
|
|
}
|
|
|
|
BOOL CSrtClient::srt_start(const char * url, const char * user, const char * pass)
|
|
{
|
|
int port = 0;
|
|
char proto[32], username[64], password[64], host[100], path[200];
|
|
|
|
url_split(url, proto, sizeof(proto), username, sizeof(username),
|
|
password, sizeof(password), host, sizeof(host), &port, path, sizeof(path));
|
|
|
|
if (strcasecmp(proto, "srt") != 0)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
if (port <= 0)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
if (host[0] == '\0')
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
m_nport = port;
|
|
|
|
strncpy(m_ip, host, sizeof(m_ip) - 1);
|
|
|
|
char * p = strstr(path, "streamid");
|
|
if (p)
|
|
{
|
|
p += strlen("streamid=");
|
|
strncpy(m_streamid, p, sizeof(m_streamid)-1);
|
|
}
|
|
|
|
if (url && url[0] != '\0')
|
|
{
|
|
strncpy(m_url, url, sizeof(m_url)-1);
|
|
}
|
|
|
|
if (user && user[0] != '\0')
|
|
{
|
|
strncpy(m_user, user, sizeof(m_user)-1);
|
|
}
|
|
|
|
if (pass && pass[0] != '\0')
|
|
{
|
|
strncpy(m_pass, pass, sizeof(m_pass)-1);
|
|
}
|
|
|
|
log_print(HT_LOG_INFO, "%s, url=%s, ip=%s, port=%d, streamid=%s\r\n",
|
|
__FUNCTION__, m_url, m_ip, m_nport, m_streamid);
|
|
|
|
m_bRunning = TRUE;
|
|
m_tidRx = sys_os_create_thread((void *)srt_rx_thread, this);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL CSrtClient::srt_play()
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL CSrtClient::srt_stop()
|
|
{
|
|
return srt_close();
|
|
}
|
|
|
|
BOOL CSrtClient::srt_pause()
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
BOOL CSrtClient::srt_close()
|
|
{
|
|
sys_os_mutex_enter(m_pMutex);
|
|
m_pAudioCB = NULL;
|
|
m_pVideoCB = NULL;
|
|
m_pNotify = NULL;
|
|
m_pUserdata = NULL;
|
|
sys_os_mutex_leave(m_pMutex);
|
|
|
|
m_bRunning = FALSE;
|
|
|
|
while (m_tidRx)
|
|
{
|
|
usleep(10*1000);
|
|
}
|
|
|
|
if (m_fd)
|
|
{
|
|
::srt_close(m_fd);
|
|
m_fd = 0;
|
|
}
|
|
|
|
if (m_eid)
|
|
{
|
|
srt_epoll_release(m_eid);
|
|
m_eid = 0;
|
|
}
|
|
|
|
m_bVideoReady = FALSE;
|
|
m_bAudioReady = FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL CSrtClient::srt_connect()
|
|
{
|
|
srt_send_notify(SRT_EVE_CONNECTING);
|
|
|
|
SRTSOCKET fd = srt_create_socket();
|
|
|
|
if (srt_setsockopt(fd, 0, SRTO_STREAMID, m_streamid, strlen(m_streamid)) < 0)
|
|
{
|
|
log_print(HT_LOG_ERR, "%s, srt_setsockopt SRTO_STREAMID failure. err=%s\r\n",
|
|
__FUNCTION__, srt_getlasterror_str());
|
|
::srt_close(fd);
|
|
return FALSE;
|
|
}
|
|
|
|
struct sockaddr_in sa;
|
|
memset(&sa, 0, sizeof sa);
|
|
sa.sin_family = AF_INET;
|
|
sa.sin_port = htons(m_nport);
|
|
sa.sin_addr.s_addr = get_address_by_name(m_ip);
|
|
|
|
struct sockaddr *psa = (struct sockaddr *) &sa;
|
|
|
|
int status = ::srt_connect(fd, psa, sizeof sa);
|
|
if (status == SRT_ERROR)
|
|
{
|
|
log_print(HT_LOG_ERR, "%s, srt_connect failure. ip=%s, port=%d\r\n",
|
|
__FUNCTION__, m_ip, m_nport);
|
|
::srt_close(fd);
|
|
return FALSE;
|
|
}
|
|
|
|
m_eid = srt_epoll_create();
|
|
|
|
int modes = SRT_EPOLL_IN | SRT_EPOLL_ERR;
|
|
|
|
int ret = srt_epoll_add_usock(m_eid, fd, &modes);
|
|
if (ret < 0)
|
|
{
|
|
log_print(HT_LOG_ERR, "%s, srt_epoll_add_usock failed, epoll=%d, fd=%d, modes=%d, %s\r\n",
|
|
__FUNCTION__, m_eid, fd, modes, srt_getlasterror_str());
|
|
::srt_close(fd);
|
|
return FALSE;
|
|
}
|
|
|
|
m_fd = fd;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
void CSrtClient::rx_thread()
|
|
{
|
|
if (!srt_connect())
|
|
{
|
|
m_tidRx = 0;
|
|
srt_send_notify(SRT_EVE_CONNFAIL);
|
|
return;
|
|
}
|
|
|
|
#define TS_UDP_LEN 1316 //7*188
|
|
|
|
int tm_count = 0;
|
|
BOOL nodata_notify = FALSE;
|
|
char buff[TS_UDP_LEN];
|
|
|
|
while (m_bRunning)
|
|
{
|
|
SRTSOCKET read_socks[1];
|
|
int read_len = 1;
|
|
|
|
int ret = srt_epoll_wait(m_eid, read_socks, &read_len, NULL, NULL, 1000, 0, 0, 0, 0);
|
|
if (ret < 0)
|
|
{
|
|
ret = srt_getlasterror(NULL);
|
|
if (ret == SRT_ETIMEOUT)
|
|
{
|
|
tm_count++;
|
|
if (tm_count >= m_nRxTimeout && !nodata_notify) // in 10s without data
|
|
{
|
|
nodata_notify = TRUE;
|
|
srt_send_notify(SRT_EVE_NODATA);
|
|
}
|
|
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
log_print(HT_LOG_ERR, "%s, srt_epoll_wait failed. err=%d\r\n", __FUNCTION__, ret);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (read_len <= 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
SRT_SOCKSTATUS status = srt_getsockstate(m_fd);
|
|
if (status == SRTS_BROKEN ||
|
|
status == SRTS_NONEXIST ||
|
|
status == SRTS_CLOSED)
|
|
{
|
|
srt_epoll_remove_usock(m_eid, m_fd);
|
|
break;
|
|
}
|
|
|
|
ret = srt_recvmsg(m_fd, buff, TS_UDP_LEN);
|
|
if (ret < 0)
|
|
{
|
|
log_print(HT_LOG_WARN, "%s, srt_recvmsg failed, sock=%d, ret=%d, err=%s\r\n",
|
|
__FUNCTION__, m_fd, ret, srt_getlasterror_str());
|
|
continue;
|
|
}
|
|
|
|
if (nodata_notify)
|
|
{
|
|
nodata_notify = FALSE;
|
|
srt_send_notify(SRT_EVE_RESUME);
|
|
}
|
|
|
|
tm_count = 0;
|
|
|
|
ts_parser_parse(&m_tsParser, (uint8*)buff, ret);
|
|
}
|
|
|
|
m_tidRx = 0;
|
|
|
|
srt_send_notify(SRT_EVE_STOPPED);
|
|
}
|
|
|
|
void CSrtClient::srt_send_notify(int event)
|
|
{
|
|
sys_os_mutex_enter(m_pMutex);
|
|
|
|
if (m_pNotify)
|
|
{
|
|
m_pNotify(event, m_pUserdata);
|
|
}
|
|
|
|
sys_os_mutex_leave(m_pMutex);
|
|
}
|
|
|
|
void CSrtClient::srt_h264_rx(uint8 * p_data, uint32 len, uint64 pts)
|
|
{
|
|
int s_len = 0, n_len = 0, parse_len = len;
|
|
uint8 nalu_t;
|
|
uint8 * p_cur = p_data;
|
|
uint8 * p_end = p_data + len;
|
|
|
|
while (p_cur && p_cur < p_end && parse_len > 0)
|
|
{
|
|
uint8 * p_next = avc_split_nalu(p_cur, parse_len, &s_len, &n_len);
|
|
|
|
nalu_t = (p_cur[s_len] & 0x1F);
|
|
|
|
if (nalu_t == H264_NAL_SPS && m_nSpsLen == 0)
|
|
{
|
|
if (n_len <= (int)sizeof(m_pSps))
|
|
{
|
|
memcpy(m_pSps, p_cur, n_len);
|
|
m_nSpsLen = n_len;
|
|
}
|
|
|
|
video_data_cb(p_cur, n_len, pts);
|
|
}
|
|
else if (nalu_t == H264_NAL_PPS && m_nPpsLen == 0)
|
|
{
|
|
if (n_len <= (int)sizeof(m_pPps))
|
|
{
|
|
memcpy(m_pPps, p_cur, n_len);
|
|
m_nPpsLen = n_len;
|
|
}
|
|
|
|
video_data_cb(p_cur, n_len, pts);
|
|
}
|
|
else if (nalu_t == H264_NAL_AUD || nalu_t == H264_NAL_SEI)
|
|
{
|
|
// skip
|
|
}
|
|
else
|
|
{
|
|
video_data_cb(p_cur, n_len, pts);
|
|
}
|
|
|
|
parse_len -= n_len;
|
|
p_cur = p_next;
|
|
}
|
|
}
|
|
|
|
void CSrtClient::srt_h265_rx(uint8 * p_data, uint32 len, uint64 pts)
|
|
{
|
|
int s_len = 0, n_len = 0, parse_len = len;
|
|
uint8 nalu_t;
|
|
uint8 * p_cur = p_data;
|
|
uint8 * p_end = p_data + len;
|
|
|
|
while (p_cur && p_cur < p_end && parse_len > 0)
|
|
{
|
|
uint8 * p_next = avc_split_nalu(p_cur, parse_len, &s_len, &n_len);
|
|
|
|
nalu_t = (p_cur[s_len] >> 1) & 0x3F;
|
|
|
|
if (nalu_t == HEVC_NAL_VPS && m_nVpsLen == 0)
|
|
{
|
|
if (n_len <= (int)sizeof(m_pVps))
|
|
{
|
|
memcpy(m_pVps, p_cur, n_len);
|
|
m_nVpsLen = n_len;
|
|
}
|
|
|
|
video_data_cb(p_cur, n_len, pts);
|
|
}
|
|
else if (nalu_t == HEVC_NAL_SPS && m_nSpsLen == 0)
|
|
{
|
|
if (n_len <= (int)sizeof(m_pSps))
|
|
{
|
|
memcpy(m_pSps, p_cur, n_len);
|
|
m_nSpsLen = n_len;
|
|
}
|
|
|
|
video_data_cb(p_cur, n_len, pts);
|
|
}
|
|
else if (nalu_t == HEVC_NAL_PPS && m_nPpsLen == 0)
|
|
{
|
|
if (n_len <= (int)sizeof(m_pPps))
|
|
{
|
|
memcpy(m_pPps, p_cur, n_len);
|
|
m_nPpsLen = n_len;
|
|
}
|
|
|
|
video_data_cb(p_cur, n_len, pts);
|
|
}
|
|
else if (nalu_t == HEVC_NAL_AUD ||
|
|
nalu_t == HEVC_NAL_SEI_PREFIX ||
|
|
nalu_t == HEVC_NAL_SEI_SUFFIX)
|
|
{
|
|
// skip
|
|
}
|
|
else
|
|
{
|
|
video_data_cb(p_cur, n_len, pts);
|
|
}
|
|
|
|
parse_len -= n_len;
|
|
p_cur = p_next;
|
|
}
|
|
}
|
|
|
|
BOOL CSrtClient::srt_parse_adts(uint8 * p_data, uint32 len)
|
|
{
|
|
bs_t bs;
|
|
bs_init(&bs, p_data, len);
|
|
|
|
if (bs_read(&bs, 12) != 0xfff)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
const int audio_sample_rates[16] = {
|
|
96000, 88200, 64000, 48000, 44100, 32000,
|
|
24000, 22050, 16000, 12000, 11025, 8000, 7350
|
|
};
|
|
|
|
int sr, ch, size;
|
|
|
|
bs_skip(&bs, 1); /* id */
|
|
bs_skip(&bs, 2); /* layer */
|
|
bs_skip(&bs, 1); /* protection_absent */
|
|
bs_skip(&bs, 2); /* profile_objecttype */
|
|
sr = bs_read(&bs, 4); /* sample_frequency_index */
|
|
if (!audio_sample_rates[sr])
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
bs_skip(&bs, 1); /* private_bit */
|
|
ch = bs_read(&bs, 3); /* channel_configuration */
|
|
|
|
bs_skip(&bs, 1); /* original/copy */
|
|
bs_skip(&bs, 1); /* home */
|
|
|
|
/* adts_variable_header */
|
|
bs_skip(&bs, 1); /* copyright_identification_bit */
|
|
bs_skip(&bs, 1); /* copyright_identification_start */
|
|
size = bs_read(&bs, 13); /* aac_frame_length */
|
|
if (size < 7)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
bs_skip(&bs, 11); /* adts_buffer_fullness */
|
|
bs_skip(&bs, 2); /* number_of_raw_data_blocks_in_frame */
|
|
|
|
m_nSamplerate = audio_sample_rates[sr];
|
|
m_nChannels = ch;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
void CSrtClient::srt_payload_cb(uint8 * p_data, uint32 len, uint32 type, uint64 pts)
|
|
{
|
|
if (type == TS_VIDEO_H264)
|
|
{
|
|
if (!m_bVideoReady)
|
|
{
|
|
m_nVideoCodec = VIDEO_CODEC_H264;
|
|
m_bVideoReady = TRUE;
|
|
srt_send_notify(SRT_EVE_VIDEOREADY);
|
|
}
|
|
|
|
srt_h264_rx(p_data, len, pts);
|
|
}
|
|
else if (type == TS_VIDEO_HEVC)
|
|
{
|
|
if (!m_bVideoReady)
|
|
{
|
|
m_nVideoCodec = VIDEO_CODEC_H265;
|
|
m_bVideoReady = TRUE;
|
|
srt_send_notify(SRT_EVE_VIDEOREADY);
|
|
}
|
|
|
|
srt_h265_rx(p_data, len, pts);
|
|
}
|
|
else if (type == TS_VIDEO_MPEG4)
|
|
{
|
|
if (!m_bVideoReady)
|
|
{
|
|
m_nVideoCodec = VIDEO_CODEC_MP4;
|
|
m_bVideoReady = TRUE;
|
|
srt_send_notify(SRT_EVE_VIDEOREADY);
|
|
}
|
|
|
|
video_data_cb(p_data, len, pts);
|
|
}
|
|
else if (type == TS_AUDIO_AAC)
|
|
{
|
|
if (!m_bAudioReady)
|
|
{
|
|
srt_parse_adts(p_data, len);
|
|
|
|
m_nAudioCodec = AUDIO_CODEC_AAC;
|
|
m_bAudioReady = TRUE;
|
|
srt_send_notify(SRT_EVE_AUDIOREADY);
|
|
}
|
|
|
|
audio_data_cb(p_data, len, pts);
|
|
}
|
|
}
|
|
|
|
void CSrtClient::video_data_cb(uint8 * p_data, int len, uint32 ts)
|
|
{
|
|
if (m_nVideoInitTS == 0 && ts != 0)
|
|
{
|
|
m_nVideoInitTS = ts;
|
|
}
|
|
|
|
sys_os_mutex_enter(m_pMutex);
|
|
if (m_pVideoCB)
|
|
{
|
|
m_pVideoCB(p_data, len, ts, m_pUserdata);
|
|
}
|
|
sys_os_mutex_leave(m_pMutex);
|
|
}
|
|
|
|
void CSrtClient::audio_data_cb(uint8 * p_data, int len, uint32 ts)
|
|
{
|
|
if (m_nAudioInitTS == 0 && ts != 0)
|
|
{
|
|
m_nAudioInitTS = ts;
|
|
}
|
|
|
|
sys_os_mutex_enter(m_pMutex);
|
|
if (m_pAudioCB)
|
|
{
|
|
m_pAudioCB(p_data, len, ts, m_pUserdata);
|
|
}
|
|
sys_os_mutex_leave(m_pMutex);
|
|
}
|
|
|
|
void CSrtClient::get_h264_params()
|
|
{
|
|
if (m_nSpsLen > 0)
|
|
{
|
|
video_data_cb(m_pSps, m_nSpsLen, 0);
|
|
}
|
|
|
|
if (m_nPpsLen > 0)
|
|
{
|
|
video_data_cb(m_pPps, m_nPpsLen, 0);
|
|
}
|
|
}
|
|
|
|
BOOL CSrtClient::get_h264_params(uint8 * p_sps, int * sps_len, uint8 * p_pps, int * pps_len)
|
|
{
|
|
if (m_nSpsLen > 0)
|
|
{
|
|
*sps_len = m_nSpsLen;
|
|
memcpy(p_sps, m_pSps, m_nSpsLen);
|
|
}
|
|
|
|
if (m_nPpsLen > 0)
|
|
{
|
|
*pps_len = m_nPpsLen;
|
|
memcpy(p_pps, m_pPps, m_nPpsLen);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
void CSrtClient::get_h265_params()
|
|
{
|
|
if (m_nVpsLen > 0)
|
|
{
|
|
video_data_cb(m_pVps, m_nVpsLen, 0);
|
|
}
|
|
|
|
if (m_nSpsLen > 0)
|
|
{
|
|
video_data_cb(m_pSps, m_nSpsLen, 0);
|
|
}
|
|
|
|
if (m_nPpsLen > 0)
|
|
{
|
|
video_data_cb(m_pPps, m_nPpsLen, 0);
|
|
}
|
|
}
|
|
|
|
BOOL CSrtClient::get_h265_params(uint8 * p_sps, int * sps_len, uint8 * p_pps, int * pps_len, uint8 * p_vps, int * vps_len)
|
|
{
|
|
if (m_nVpsLen > 0)
|
|
{
|
|
*vps_len = m_nVpsLen;
|
|
memcpy(p_vps, m_pVps, m_nVpsLen);
|
|
}
|
|
|
|
if (m_nSpsLen > 0)
|
|
{
|
|
*sps_len = m_nSpsLen;
|
|
memcpy(p_sps, m_pSps, m_nSpsLen);
|
|
}
|
|
|
|
if (m_nPpsLen > 0)
|
|
{
|
|
*pps_len = m_nPpsLen;
|
|
memcpy(p_pps, m_pPps, m_nPpsLen);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
void CSrtClient::set_notify_cb(srt_notify_cb notify, void * userdata)
|
|
{
|
|
sys_os_mutex_enter(m_pMutex);
|
|
m_pNotify = notify;
|
|
m_pUserdata = userdata;
|
|
sys_os_mutex_leave(m_pMutex);
|
|
}
|
|
|
|
void CSrtClient::set_video_cb(srt_video_cb cb)
|
|
{
|
|
sys_os_mutex_enter(m_pMutex);
|
|
m_pVideoCB = cb;
|
|
sys_os_mutex_leave(m_pMutex);
|
|
}
|
|
|
|
void CSrtClient::set_audio_cb(srt_audio_cb cb)
|
|
{
|
|
sys_os_mutex_enter(m_pMutex);
|
|
m_pAudioCB = cb;
|
|
sys_os_mutex_leave(m_pMutex);
|
|
}
|
|
|
|
void CSrtClient::set_rx_timeout(int timeout)
|
|
{
|
|
m_nRxTimeout = timeout;
|
|
}
|
|
|
|
|