blob: 4ae8a74aeea4a77a6b14d7626f0125eca81cd242 [file] [log] [blame]
/*
* rtp_server.c
*
* RTP transport service.
*
* -------------------------
* | |
* ------ UDP | ------- |
* | | -----|-> playback -> | | |
* | MCU | | | Voice | |
* | | UDP | | | |
* ------ <----|-- record <--- | | |
* | ------- |
* -------------------------
*/
/******************************************************************************
EDIT HISTORY FOR FILE
WHEN WHO WHAT,WHERE,WHY
-------- -------- -------------------------------------------------------
2024/12/3 LiuBin Initial version
******************************************************************************/
#include <stdio.h>
#include <errno.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <sys/epoll.h>
#include <arpa/inet.h>
#include <sys/ioctl.h>
#include <sys/time.h>
#include "mbtk_log.h"
#include "mbtk_utils.h"
#include "mbtk_audio2.h"
#include "mbtk_rtp_internal.h"
#define RTP_DEBUG_PCM_FILE 1
#define RTP_UDP_READ_BUFF_SIZE 2048
static rtp_udp_ser_state_enum udp_state = RTP_UDP_SER_STATE_IDEL;
static rtp_voip_ser_state_enum voip_state = RTP_VOIP_SER_STATE_IDEL;
extern rtp_info_t rtp_info;
static pthread_t voip_playback;
static rtp_header_info_t rtp_head = {
.version = 2, // 2 (2 bit)
.padding = 0, // 0 (1 bit)
.extension = 0, // 0 (1 bit)
.csrc = 1, // 1 (4 bit)
.marker = 0, // 0 (1 bit)
.payload_type = 0x60, // 0x60 (7 bit)
.sequence = 0, // (16 bit)
.timestamp = 0, // (32 bit)
.ssrc = 0xFFFF0000, // 0xFFFF0000 (32 bit)
.csrc = 0xFFFF0000 // 0xFFFF0000 (32 bit)
};
int epoll_fd_add(int fd);
int epoll_fd_del(int fd);
static void *mbtk_memcpy(const void *src, unsigned int n)
{
void *dest = malloc(n);
if(dest) {
return memcpy(dest, src, n);
} else {
return NULL;
}
}
static void rtp_send_init()
{
rtp_info.send.iov_idx = 1; // First is RTP header.
rtp_info.send.pack_size = 0;
rtp_info.send.remain_buff_len = 0;
rtp_info.recv.first_packet = TRUE;
if(rtp_info.recv.recv_buff == NULL)
rtp_info.recv.recv_buff = mbtk_loopbuff_get(1024 * 1024);
rtp_head.sequence = (uint32) (rand()*rand());
rtp_head.timestamp = 0;
}
static int rtp_pack_push(const uint8 *data, uint32 data_len, uint32_t timestamp)
{
UNUSED(data);
UNUSED(data_len);
UNUSED(timestamp);
if(rtp_info.recv.recv_buff) {
if(data_len != mbtk_loopbuff_write(rtp_info.recv.recv_buff, data, data_len))
return -1;
}
return 0;
}
static void udp_read_cb(int fd)
{
int size;
size_t audio_length;
size_t metadata_length;
struct msghdr m;
//struct cmsghdr *cm;
struct iovec iov;
uint32_t header;
uint32_t ssrc;
uint8_t payload;
unsigned cc;
ssize_t r;
uint8_t aux[1024];
//bool found_tstamp = false;
uint32_t timestamp;
int64_t k, j, delta;
uint8_t recv_buf[RTP_UDP_READ_BUFF_SIZE];
//size_t recv_buf_size = 0;
if (ioctl(fd, FIONREAD, &size) < 0) {
LOGE("FIONREAD failed: %d", errno);
goto fail;
}
if (size <= 0) {
/* size can be 0 due to any of the following reasons:
*
* 1. Somebody sent us a perfectly valid zero-length UDP packet.
* 2. Somebody sent us a UDP packet with a bad CRC.
*
* It is unknown whether size can actually be less than zero.
*
* In the first case, the packet has to be read out, otherwise the
* kernel will tell us again and again about it, thus preventing
* reception of any further packets. So let's just read it out
* now and discard it later, when comparing the number of bytes
* received (0) with the number of bytes wanted (1, see below).
*
* In the second case, recvmsg() will fail, thus allowing us to
* return the error.
*
* Just to avoid passing zero-sized memchunks and NULL pointers to
* recvmsg(), let's force allocation of at least one byte by setting
* size to 1.
*/
size = 1;
}
iov.iov_base = recv_buf;
iov.iov_len = (size_t) size;
m.msg_name = NULL;
m.msg_namelen = 0;
m.msg_iov = &iov;
m.msg_iovlen = 1;
m.msg_control = aux;
m.msg_controllen = sizeof(aux);
m.msg_flags = 0;
r = recvmsg(fd, &m, 0);
if (r != size) {
if (r < 0 && errno != EAGAIN && errno != EINTR)
LOGE("recvmsg() failed: %s", r < 0 ? strerror(errno) : "size mismatch");
goto fail;
}
if(voip_state != RTP_VOIP_SER_STATE_RUNNING) {
return;
}
#if 0
printf("RECV : %d\n", r);
int send_len = mbtk_audio_pcm_play_data_send(recv_buf, r);
if(r != send_len) {
printf("play_data_send fail: %d/%d\n", send_len, r);
}
#endif
if (size < 12) {
LOGE("RTP packet too short.");
goto fail;
}
#if 1
memcpy(&header, iov.iov_base, sizeof(uint32_t));
memcpy(&timestamp, (uint8_t*) iov.iov_base + 4, sizeof(uint32_t));
memcpy(&ssrc, (uint8_t*) iov.iov_base + 8, sizeof(uint32_t));
header = ntohl(header);
timestamp = ntohl(timestamp);
ssrc = ntohl(ssrc);
if ((header >> 30) != 2) {
LOGE("Unsupported RTP version.");
goto fail;
}
if ((header >> 29) & 1) {
LOGE("RTP padding not supported.");
goto fail;
}
if ((header >> 28) & 1) {
LOGE("RTP header extensions not supported.");
goto fail;
}
if (ssrc != rtp_head.ssrc) {
LOGE("Got unexpected SSRC");
goto fail;
}
cc = (header >> 24) & 0xF;
payload = (uint8_t) ((header >> 16) & 127U);
rtp_head.sequence = (uint16_t) (header & 0xFFFFU);
metadata_length = 12 + cc * 4;
if (payload != rtp_head.payload_type) {
LOGE("Got unexpected payload: %u", payload);
goto fail;
}
if (metadata_length > (unsigned) size) {
LOGE("RTP packet too short. (CSRC)");
goto fail;
}
audio_length = size - metadata_length;
if (audio_length % rtp_info.frame_size != 0) {
LOGE("Bad RTP packet size.");
goto fail;
}
if (rtp_info.recv.first_packet) {
rtp_info.recv.first_packet = FALSE;
rtp_info.recv.offset = timestamp;
}
/* Check whether there was a timestamp overflow */
k = (int64_t) timestamp - (int64_t) rtp_info.recv.offset;
j = (int64_t) 0x100000000LL - (int64_t) rtp_info.recv.offset + (int64_t) timestamp;
if ((k < 0 ? -k : k) < (j < 0 ? -j : j))
delta = k;
else
delta = j;
LOGD("RECV : %d, delta = %d", audio_length, delta);
// pa_memblockq_seek(s->memblockq, delta * (int64_t) rtp_info.frame_size, PA_SEEK_RELATIVE, true);
/* The next timestamp we expect */
rtp_info.recv.offset = timestamp + (uint32_t) (audio_length / rtp_info.frame_size);
if(rtp_pack_push(recv_buf + metadata_length, audio_length, timestamp)) {
LOGE("rtp_pack_push() fail.");
}
#endif
return;
fail:
return;
}
static void voip_recorder_cb(void *data, uint32 data_len)
{
if(data_len > 0) {
LOGD("Record : %d", data_len);
if(rtp_info.udp_send_sock.fd > 0) {
#if 0
int len = sendto(rtp_info.udp_send_sock.fd, data, data_len, 0, NULL, 0);
printf("SEND : %d / %d\n", len, data_len);
#else
// 有剩余数据
if(rtp_info.send.iov_idx == 1 && rtp_info.send.remain_buff_len > 0) {
rtp_info.send.iov[rtp_info.send.iov_idx].iov_base = mbtk_memcpy(rtp_info.send.remain_buff,
rtp_info.send.remain_buff_len);
rtp_info.send.iov[rtp_info.send.iov_idx].iov_len = rtp_info.send.remain_buff_len;
rtp_info.send.iov_idx++;
rtp_info.send.pack_size += rtp_info.send.remain_buff_len;
rtp_info.send.remain_buff_len = 0;
}
// UDP各分包总大小不超过 c->mtu ( 默认: DEFAULT_MTU )
// k 为分包大小
uint32 k = rtp_info.send.pack_size + data_len > rtp_info.send.mtu ?
rtp_info.send.mtu - rtp_info.send.pack_size : data_len;
rtp_info.send.iov[rtp_info.send.iov_idx].iov_base = mbtk_memcpy(data, k);
rtp_info.send.iov[rtp_info.send.iov_idx].iov_len = k;
rtp_info.send.iov_idx++;
rtp_info.send.pack_size += k;
if(rtp_info.send.pack_size % rtp_info.frame_size != 0) {
LOGW("pack size error: %d - %d", rtp_info.send.pack_size, rtp_info.frame_size);
return;
}
if (rtp_info.send.pack_size >= rtp_info.send.mtu || rtp_info.send.iov_idx >= MAX_IOVECS) {
uint32_t header[4];
struct msghdr m;
ssize_t k;
int i;
header[0] = htonl(((uint32_t) rtp_head.version << 30) | ((uint32_t) rtp_head.csrc_count << 24) | ((uint32_t) rtp_head.payload_type << 16) | ((uint32_t) rtp_head.sequence));
header[1] = htonl(rtp_head.timestamp);
header[2] = htonl(rtp_head.ssrc);
header[3] = htonl(rtp_head.csrc);
rtp_info.send.iov[0].iov_base = (void*)header;
rtp_info.send.iov[0].iov_len = sizeof(header);
m.msg_name = NULL;
m.msg_namelen = 0;
m.msg_iov = rtp_info.send.iov;
m.msg_iovlen = (size_t) rtp_info.send.iov_idx;
m.msg_control = NULL;
m.msg_controllen = 0;
m.msg_flags = 0;
k = sendmsg(rtp_info.udp_send_sock.fd, &m, MSG_DONTWAIT);
for (i = 1; i < rtp_info.send.iov_idx; i++) {
free(rtp_info.send.iov[i].iov_base);
rtp_info.send.iov[i].iov_base = NULL;
}
rtp_head.sequence++;
// 时间单位转为帧数(每帧多少us)
rtp_head.timestamp += (unsigned) (rtp_info.send.pack_size / rtp_info.frame_size);
if (k < 0) {
if (errno != EAGAIN && errno != EINTR) /* If the queue is full, just ignore it */
LOGE("sendmsg() failed: %s", strerror(errno));
return;
}
rtp_info.send.pack_size = 0;
rtp_info.send.iov_idx = 1;
}
#endif
}
} else {
LOGD("Recorver data end.");
}
}
static int64 time_us_get()
{
struct timespec ts;
memset(&ts, 0, sizeof(struct timespec));
if(clock_gettime(CLOCK_REALTIME, &ts)) {
LOGE("clock_gettime() fail:%d", errno);
return -1;
}
return ts.tv_sec * 1000000 + ts.tv_nsec / 1000;
}
static void voip_playback_thread(void *arg)
{
UNUSED(arg);
char *buff = (char*)malloc(rtp_info.playback_size * 2);
if(buff == NULL) {
LOGE("malloc() fail.");
return;
}
if(mbtk_audio_voice_pcm_playback_start()) {
LOGE("mbtk_audio_voice_pcm_playback_start() fail.");
return;
}
usleep(100000);
int64 play_start = time_us_get(); // us
//uint64 play_count = 0;
//char num_buff[1024] = {0};
while (voip_state == RTP_VOIP_SER_STATE_RUNNING) {
int len;
if((len = mbtk_loopbuff_read(rtp_info.recv.recv_buff, buff, rtp_info.playback_size * 2)) > 0) {
int send_len = mbtk_audio_pcm_play_data_send(buff, len);
if(send_len > 0) {
if(len != send_len) {
LOGW("play_data_send fail: %d/%d\n", send_len, len);
}
//play_count += send_len;
int64 play_now = time_us_get();
int time_offset = (int)((play_now - play_start) / 1000 * rtp_info.sample_for_ms);
LOGD("loopbuff:%d, time_offset:%d, send_len:%d", mbtk_loopbuff_size(rtp_info.recv.recv_buff), time_offset,
send_len / 2);
#if 0
int offset = send_len / 2 - time_offset;
if(offset < 0 && len > -offset * 2) {
#if 0
if(mbtk_loopbuff_seek(rtp_info.recv.recv_buff, offset * 2)) {
LOGE("mbtk_loopbuff_seek() fail.");
}
mbtk_loopbuff_print(rtp_info.recv.recv_buff);
#else
mbtk_audio_pcm_play_data_send(buff + len + offset * 2, -offset * 2);
#endif
}
#endif
play_start = play_now;
}
}
#if 0
else {
if(voip_state == RTP_VOIP_SER_STATE_RUNNING)
usleep(5000);
}
// LOGD("%s: No.%d frame playback.", __FUNCTION__, ++frames);
int time_offset = (int)((time_us_get() - play_start) / 1000 * rtp_info.sample_for_ms);
LOGD("loopbuff:%d, time_offset:%d, play_count:%d", mbtk_loopbuff_size(rtp_info.recv.recv_buff), time_offset,
play_count / 2);
#endif
}
if(mbtk_audio_pcm_play_stop()) {
LOGE("mbtk_audio_pcm_play_stop() fail.");
return;
}
free(buff);
return;
}
static int udp_setsockopt(int fd)
{
int priority = 6;
if (setsockopt(fd, SOL_SOCKET, SO_PRIORITY, (const void *) &priority, sizeof(priority)) < 0) {
LOGE("setsockopt(SO_PRIORITY) fail:errno - %d", errno);
return -1;
}
int one = 1;
if (setsockopt(fd, SOL_SOCKET, SO_TIMESTAMP, &one, sizeof(one)) < 0) {
LOGE("SO_TIMESTAMP failed: %d", errno);
return -1;
}
one = 1;
if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)) < 0) {
LOGE("SO_REUSEADDR failed: %d", errno);
return -1;
}
return 0;
}
static int socket_noblock(int fd)
{
// Set O_NONBLOCK
int flags = fcntl(fd, F_GETFL, 0);
if (flags < 0)
{
LOGE("Get flags error:%d", errno);
return -1;
}
flags |= O_NONBLOCK;
if (fcntl(fd, F_SETFL, flags) < 0)
{
LOGE("Set flags error:%d", errno);
return -1;
}
return 0;
}
static int rtp_udp_ser_open(const char *local_addr, int local_port)
{
// No set local addr.
UNUSED(local_addr);
int fd = socket(AF_INET, SOCK_DGRAM, 0);
if(fd < 0){
LOGE("socket() fail.[%d]", errno);
return -1;
}
if(udp_setsockopt(fd)) {
goto result_fail_with_close;
}
struct sockaddr_in servaddr;
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(local_port);
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(fd, (struct sockaddr *)&servaddr, sizeof(struct sockaddr_in)) < 0) {
LOGE("bind() failed: %d", errno);
goto result_fail_with_close;
}
return fd;
result_fail_with_close:
close(fd);
fd = -1;
LOGE("mbtk_sock_open() end:fail");
return -1;
}
static int rtp_udp_cli_open(const char *remote_addr, int remote_port)
{
struct sockaddr_in dst_sa4, src_sa4;
if (inet_pton(AF_INET, "0.0.0.0", &src_sa4.sin_addr) > 0) {
src_sa4.sin_family = AF_INET;
src_sa4.sin_port = htons(0);
memset(&src_sa4.sin_zero, 0, sizeof(src_sa4.sin_zero));
} else {
LOGE("Set src addr fail.");
return -1;
}
if (inet_pton(AF_INET, remote_addr, &dst_sa4.sin_addr) > 0) {
dst_sa4.sin_family = AF_INET;
dst_sa4.sin_port = htons(remote_port);
memset(&dst_sa4.sin_zero, 0, sizeof(dst_sa4.sin_zero));
} else {
LOGE("Set dst addr fail.");
return -1;
}
int fd = socket(AF_INET, SOCK_DGRAM, 0);
if(fd < 0){
LOGE("socket() fail.[%d]", errno);
return -1;
}
if (bind(fd, (struct sockaddr*) &src_sa4, sizeof(src_sa4)) < 0) {
LOGE("bind() failed: %d", errno);
goto result_fail_with_close;
}
if (connect(fd, (struct sockaddr*) &dst_sa4, sizeof(dst_sa4)) < 0) {
LOGE("connect() failed: %d", errno);
goto result_fail_with_close;
}
if(socket_noblock(fd)) {
goto result_fail_with_close;
}
if(udp_setsockopt(fd)) {
goto result_fail_with_close;
}
return fd;
result_fail_with_close:
close(fd);
fd = -1;
LOGE("mbtk_sock_open() end:fail");
return -1;
}
/*===========================================================================
FUNCTION rtp_udp_server_start
DESCRIPTION:
Start RTP UDP server,will monitor rtp packet from MCU.
PARAMETERS:
conf_info : RTP config informations.
RETURN VALUE:
int : 0 for success, other for fail.
===========================================================================*/
int rtp_udp_server_start(rtp_config_t *conf_info)
{
UNUSED(conf_info);
if(udp_state != RTP_UDP_SER_STATE_IDEL) {
LOGE("udp_state error : %d", udp_state);
return -1;
}
udp_state = RTP_UDP_SER_STATE_STARTING;
rtp_info.frame_size = conf_info->channel * RTP_SAMPLE_NUMBER;
rtp_info.send.mtu = (RTP_DEFAULT_MTU / rtp_info.frame_size) * rtp_info.frame_size;
if(conf_info->sample_rate == MBTK_AUDIO_SAMPLE_RATE_8000) {
rtp_info.playback_size = MBTK_PCM_NB_BUF_SIZE;
rtp_info.sample_for_ms = 8;
} else {
rtp_info.playback_size = MBTK_PCM_WB_BUF_SIZE;
rtp_info.sample_for_ms = 16;
}
LOGD("frame_size = %d, MTU = %d", rtp_info.frame_size, rtp_info.send.mtu);
// Open UDP server socket.
LOGD("Start open UDP server : NULL-%d", conf_info->client_port);
rtp_info.udp_recv_sock.fd = rtp_udp_ser_open(NULL, conf_info->client_port);
if(rtp_info.udp_recv_sock.fd < 0) {
LOGE("socket(udp_recv_sock) fail : errno = %d", errno);
goto fail;
}
rtp_info.udp_recv_sock.read_cb = udp_read_cb;
socket_noblock(rtp_info.udp_recv_sock.fd);
epoll_fd_add(rtp_info.udp_recv_sock.fd);
#if 0
rtp_info.udp_send_sock.fd = rtp_udp_cli_open(conf_info->remote_ip, conf_info->server_port);
if(rtp_info.udp_send_sock.fd < 0) {
LOGW("socket(udp_send_sock) fail : errno = %d", errno);
LOGW("Can not connected to %s:%d.", conf_info->remote_ip, conf_info->server_port);
// goto fail;
}
rtp_info.udp_send_sock.read_cb = NULL;
#endif
udp_state = RTP_UDP_SER_STATE_RUNNING;
LOGD("UDP server is running...");
return 0;
fail:
if(rtp_info.udp_recv_sock.fd > 0) {
epoll_fd_del(rtp_info.udp_recv_sock.fd);
close(rtp_info.udp_recv_sock.fd);
rtp_info.udp_recv_sock.fd = -1;
rtp_info.udp_recv_sock.read_cb = NULL;
}
#if 0
if(rtp_info.udp_send_sock.fd > 0) {
close(rtp_info.udp_send_sock.fd);
rtp_info.udp_send_sock.fd = -1;
rtp_info.udp_send_sock.read_cb = NULL;
}
#endif
udp_state = RTP_UDP_SER_STATE_IDEL;
return -1;
}
/*===========================================================================
FUNCTION rtp_udp_server_stop
DESCRIPTION:
Stop RTP UDP server.
PARAMETERS:
Non.
RETURN VALUE:
int : 0 for success, other for fail.
===========================================================================*/
int rtp_udp_server_stop()
{
if(udp_state != RTP_UDP_SER_STATE_RUNNING) {
LOGE("udp_state error : %d", udp_state);
return -1;
}
udp_state = RTP_UDP_SER_STATE_STOPING;
if(rtp_info.udp_recv_sock.fd > 0) {
epoll_fd_del(rtp_info.udp_recv_sock.fd);
close(rtp_info.udp_recv_sock.fd);
rtp_info.udp_recv_sock.fd = -1;
rtp_info.udp_recv_sock.read_cb = NULL;
}
#if 0
if(rtp_info.udp_send_sock.fd > 0) {
close(rtp_info.udp_send_sock.fd);
rtp_info.udp_send_sock.fd = -1;
rtp_info.udp_send_sock.read_cb = NULL;
}
#endif
udp_state = RTP_UDP_SER_STATE_IDEL;
return 0;
}
/*===========================================================================
FUNCTION rtp_voip_server_start
DESCRIPTION:
Start RTP voip server.will start playback/record PCM to/from voice path.
PARAMETERS:
conf_info : RTP config informations.
RETURN VALUE:
int : 0 for success, other for fail.
===========================================================================*/
int rtp_voip_server_start(const rtp_config_t *conf_info)
{
UNUSED(conf_info);
if(voip_state != RTP_VOIP_SER_STATE_IDEL) {
LOGE("voip_state error : %d", voip_state);
return -1;
}
voip_state = RTP_VOIP_SER_STATE_STARTING;
if(mbtk_audio_pcm_init()) {
LOGE("mbtk_audio_pcm_init() fail.");
voip_state = RTP_VOIP_SER_STATE_IDEL;
}
LOGD("Start open UDP client : %s-%d", conf_info->remote_ip, conf_info->server_port);
rtp_info.udp_send_sock.fd = rtp_udp_cli_open(conf_info->remote_ip, conf_info->server_port);
if(rtp_info.udp_send_sock.fd < 0) {
LOGE("Can not connected to %s:%d [errno-%d].", conf_info->remote_ip, conf_info->server_port, errno);
goto error;
}
rtp_info.udp_send_sock.read_cb = NULL;
rtp_send_init();
if(mbtk_audio_voice_pcm_record_start(voip_recorder_cb)) {
LOGE("mbtk_audio_voice_pcm_record_start() fail.");
goto error;
}
voip_state = RTP_VOIP_SER_STATE_RUNNING;
pthread_attr_t thread_attr;
pthread_attr_init(&thread_attr);
if(pthread_attr_setdetachstate(&thread_attr, PTHREAD_CREATE_DETACHED))
{
LOGE("pthread_attr_setdetachstate() fail.");
goto error;
}
if (pthread_create(&voip_playback, NULL, (void *)&voip_playback_thread, NULL) < 0) {
LOGE("%s: error creating thread_recorder!", __FUNCTION__);
goto error;
}
LOGD("VOIP server is running...");
return 0;
error:
if(mbtk_audio_pcm_deinit()) {
LOGE("mbtk_audio_pcm_deinit() fail.");
}
if(rtp_info.udp_send_sock.fd > 0) {
close(rtp_info.udp_send_sock.fd);
rtp_info.udp_send_sock.fd = -1;
rtp_info.udp_send_sock.read_cb = NULL;
}
voip_state = RTP_VOIP_SER_STATE_IDEL;
return -1;
}
/*===========================================================================
FUNCTION rtp_server_stop
DESCRIPTION:
Stop RTP voip server.
PARAMETERS:
Non.
RETURN VALUE:
int : 0 for success, other for fail.
===========================================================================*/
int rtp_voip_server_stop()
{
if(voip_state != RTP_VOIP_SER_STATE_RUNNING) {
LOGE("voip_state error : %d", voip_state);
return -1;
}
voip_state = RTP_VOIP_SER_STATE_STOPING;
#if 0
if(mbtk_audio_pcm_play_stop()) {
LOGE("mbtk_audio_pcm_play_stop() fail.");
return -1;
}
#else
if (pthread_join(voip_playback, NULL)) {
LOGE("error join voip_playback!");
return -1;
}
#endif
if(mbtk_audio_pcm_recorder_stop()) {
LOGE("mbtk_audio_pcm_recorder_stop() fail.");
return -1;
}
if(mbtk_audio_pcm_deinit()) {
LOGE("mbtk_audio_pcm_deinit() fail.");
return -1;
}
if(rtp_info.recv.recv_buff) {
mbtk_loopbuff_free(rtp_info.recv.recv_buff);
rtp_info.recv.recv_buff = NULL;
}
voip_state = RTP_VOIP_SER_STATE_IDEL;
return 0;
}