blob: 9e1d540e9236c551903d48497a8cfba0f1442e38 [file] [log] [blame]
/* $Id: vidgui.cpp 4060 2012-04-17 09:55:30Z ming $ */
/*
* Copyright (C) 2011-2011 Teluu Inc. (http://www.teluu.com)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "vidgui.h"
#include "vidwin.h"
#if defined(PJ_WIN32)
# define SDL_MAIN_HANDLED
#endif
#include <SDL.h>
#include <assert.h>
#include <QMessageBox>
#define LOG_FILE "vidgui.log"
#define THIS_FILE "vidgui.cpp"
///////////////////////////////////////////////////////////////////////////
//
// SETTINGS
//
//
// These configure SIP registration
//
#define USE_REGISTRATION 0
#define SIP_DOMAIN "pjsip.org"
#define SIP_USERNAME "vidgui"
#define SIP_PASSWORD "secret"
#define SIP_PORT 5080
#define SIP_TCP 1
//
// NAT helper settings
//
#define USE_ICE 1
#define USE_STUN 0
#define STUN_SRV "stun.pjsip.org"
//
// Devices settings
//
#define DEFAULT_CAP_DEV PJMEDIA_VID_DEFAULT_CAPTURE_DEV
//#define DEFAULT_CAP_DEV 1
#define DEFAULT_REND_DEV PJMEDIA_VID_DEFAULT_RENDER_DEV
//
// End of Settings
///////////////////////////////////////////////////////////////////////////
MainWin *MainWin::theInstance_;
MainWin::MainWin(QWidget *parent)
: QWidget(parent), accountId_(-1), currentCall_(-1),
preview_on(false), video_(NULL), video_prev_(NULL)
{
theInstance_ = this;
initLayout();
emit signalCallReleased();
}
MainWin::~MainWin()
{
quit();
theInstance_ = NULL;
}
MainWin *MainWin::instance()
{
return theInstance_;
}
void MainWin::initLayout()
{
//statusBar_ = new QStatusBar(this);
/* main layout */
QHBoxLayout *hbox_main = new QHBoxLayout;
//QVBoxLayout *vbox_left = new QVBoxLayout;
vbox_left = new QVBoxLayout;
QVBoxLayout *vbox_right = new QVBoxLayout;
hbox_main->addLayout(vbox_left);
hbox_main->addLayout(vbox_right);
/* Left pane */
QHBoxLayout *hbox_url = new QHBoxLayout;
hbox_url->addWidget(new QLabel(tr("Url:")));
hbox_url->addWidget(url_=new QLineEdit(tr("sip:")), 1);
vbox_left->addLayout(hbox_url);
/* Right pane */
vbox_right->addWidget((localUri_ = new QLabel));
vbox_right->addWidget((vidEnabled_ = new QCheckBox(tr("Enable &video"))));
vbox_right->addWidget((previewButton_=new QPushButton(tr("Start &Preview"))));
vbox_right->addWidget((callButton_=new QPushButton(tr("Call"))));
vbox_right->addWidget((hangupButton_=new QPushButton(tr("Hangup"))));
vbox_right->addWidget((quitButton_=new QPushButton(tr("Quit"))));
#if PJMEDIA_HAS_VIDEO
vidEnabled_->setCheckState(Qt::Checked);
#else
vidEnabled_->setCheckState(Qt::Unchecked);
vidEnabled_->setEnabled(false);
#endif
/* Outest layout */
QVBoxLayout *vbox_outest = new QVBoxLayout;
vbox_outest->addLayout(hbox_main);
vbox_outest->addWidget((statusBar_ = new QLabel));
setLayout(vbox_outest);
connect(previewButton_, SIGNAL(clicked()), this, SLOT(preview()));
connect(callButton_, SIGNAL(clicked()), this, SLOT(call()));
connect(hangupButton_, SIGNAL(clicked()), this, SLOT(hangup()));
connect(quitButton_, SIGNAL(clicked()), this, SLOT(quit()));
//connect(this, SIGNAL(close()), this, SLOT(quit()));
connect(vidEnabled_, SIGNAL(stateChanged(int)), this, SLOT(onVidEnabledChanged(int)));
// UI updates must be done in the UI thread!
connect(this, SIGNAL(signalNewCall(int, bool)),
this, SLOT(onNewCall(int, bool)));
connect(this, SIGNAL(signalCallReleased()),
this, SLOT(onCallReleased()));
connect(this, SIGNAL(signalInitVideoWindow()),
this, SLOT(initVideoWindow()));
connect(this, SIGNAL(signalShowStatus(const QString&)),
this, SLOT(doShowStatus(const QString&)));
}
void MainWin::quit()
{
delete video_prev_;
video_prev_ = NULL;
delete video_;
video_ = NULL;
pjsua_destroy();
qApp->quit();
}
void MainWin::showStatus(const char *msg)
{
PJ_LOG(3,(THIS_FILE, "%s", msg));
QString msg_ = QString::fromUtf8(msg);
emit signalShowStatus(msg_);
}
void MainWin::doShowStatus(const QString& msg)
{
//statusBar_->showMessage(msg);
statusBar_->setText(msg);
}
void MainWin::showError(const char *title, pj_status_t status)
{
char errmsg[PJ_ERR_MSG_SIZE];
char errline[120];
pj_strerror(status, errmsg, sizeof(errmsg));
snprintf(errline, sizeof(errline), "%s error: %s", title, errmsg);
showStatus(errline);
}
void MainWin::onVidEnabledChanged(int state)
{
pjsua_call_setting call_setting;
if (currentCall_ == -1)
return;
pjsua_call_setting_default(&call_setting);
call_setting.vid_cnt = (state == Qt::Checked);
pjsua_call_reinvite2(currentCall_, &call_setting, NULL);
}
void MainWin::onNewCall(int cid, bool incoming)
{
pjsua_call_info ci;
pj_assert(currentCall_ == -1);
currentCall_ = cid;
pjsua_call_get_info(cid, &ci);
url_->setText(ci.remote_info.ptr);
url_->setEnabled(false);
hangupButton_->setEnabled(true);
if (incoming) {
callButton_->setText(tr("Answer"));
callButton_->setEnabled(true);
} else {
callButton_->setEnabled(false);
}
//video_->setText(ci.remote_contact.ptr);
//video_->setWindowTitle(ci.remote_contact.ptr);
}
void MainWin::onCallReleased()
{
url_->setEnabled(true);
callButton_->setEnabled(true);
callButton_->setText(tr("Call"));
hangupButton_->setEnabled(false);
currentCall_ = -1;
delete video_;
video_ = NULL;
}
void MainWin::preview()
{
if (preview_on) {
delete video_prev_;
video_prev_ = NULL;
pjsua_vid_preview_stop(DEFAULT_CAP_DEV);
showStatus("Preview stopped");
previewButton_->setText(tr("Start &Preview"));
} else {
pjsua_vid_win_id wid;
pjsua_vid_win_info wi;
pjsua_vid_preview_param pre_param;
pj_status_t status;
pjsua_vid_preview_param_default(&pre_param);
pre_param.rend_id = DEFAULT_REND_DEV;
pre_param.show = PJ_FALSE;
status = pjsua_vid_preview_start(DEFAULT_CAP_DEV, &pre_param);
if (status != PJ_SUCCESS) {
char errmsg[PJ_ERR_MSG_SIZE];
pj_strerror(status, errmsg, sizeof(errmsg));
QMessageBox::critical(0, "Error creating preview", errmsg);
return;
}
wid = pjsua_vid_preview_get_win(DEFAULT_CAP_DEV);
pjsua_vid_win_get_info(wid, &wi);
video_prev_ = new VidWin(&wi.hwnd);
video_prev_->putIntoLayout(vbox_left);
//Using this will cause SDL window to display blank
//screen sometimes, probably because it's using different
//X11 Display
//status = pjsua_vid_win_set_show(wid, PJ_TRUE);
//This is handled by VidWin now
//video_prev_->show_sdl();
showStatus("Preview started");
previewButton_->setText(tr("Stop &Preview"));
}
preview_on = !preview_on;
}
void MainWin::call()
{
if (callButton_->text() == "Answer") {
pjsua_call_setting call_setting;
pj_assert(currentCall_ != -1);
pjsua_call_setting_default(&call_setting);
call_setting.vid_cnt = (vidEnabled_->checkState()==Qt::Checked);
pjsua_call_answer2(currentCall_, &call_setting, 200, NULL, NULL);
callButton_->setEnabled(false);
} else {
pj_status_t status;
QString dst = url_->text();
char uri[256];
pjsua_call_setting call_setting;
pj_ansi_strncpy(uri, dst.toAscii().data(), sizeof(uri));
pj_str_t uri2 = pj_str((char*)uri);
pj_assert(currentCall_ == -1);
pjsua_call_setting_default(&call_setting);
call_setting.vid_cnt = (vidEnabled_->checkState()==Qt::Checked);
status = pjsua_call_make_call(accountId_, &uri2, &call_setting,
NULL, NULL, &currentCall_);
if (status != PJ_SUCCESS) {
showError("make call", status);
return;
}
}
}
void MainWin::hangup()
{
pj_assert(currentCall_ != -1);
//pjsua_call_hangup(currentCall_, PJSIP_SC_BUSY_HERE, NULL, NULL);
pjsua_call_hangup_all();
emit signalCallReleased();
}
void MainWin::initVideoWindow()
{
pjsua_call_info ci;
unsigned i;
if (currentCall_ == -1)
return;
delete video_;
video_ = NULL;
pjsua_call_get_info(currentCall_, &ci);
for (i = 0; i < ci.media_cnt; ++i) {
if ((ci.media[i].type == PJMEDIA_TYPE_VIDEO) &&
(ci.media[i].dir & PJMEDIA_DIR_DECODING))
{
pjsua_vid_win_info wi;
pjsua_vid_win_get_info(ci.media[i].stream.vid.win_in, &wi);
video_= new VidWin(&wi.hwnd);
video_->putIntoLayout(vbox_left);
break;
}
}
}
void MainWin::on_reg_state(pjsua_acc_id acc_id)
{
pjsua_acc_info info;
pjsua_acc_get_info(acc_id, &info);
char reg_status[80];
char status[120];
if (!info.has_registration) {
pj_ansi_snprintf(reg_status, sizeof(reg_status), "%.*s",
(int)info.status_text.slen,
info.status_text.ptr);
} else {
pj_ansi_snprintf(reg_status, sizeof(reg_status),
"%d/%.*s (expires=%d)",
info.status,
(int)info.status_text.slen,
info.status_text.ptr,
info.expires);
}
snprintf(status, sizeof(status),
"%.*s: %s\n",
(int)info.acc_uri.slen, info.acc_uri.ptr,
reg_status);
showStatus(status);
}
void MainWin::on_call_state(pjsua_call_id call_id, pjsip_event *e)
{
pjsua_call_info ci;
PJ_UNUSED_ARG(e);
pjsua_call_get_info(call_id, &ci);
if (currentCall_ == -1 && ci.state < PJSIP_INV_STATE_DISCONNECTED &&
ci.role == PJSIP_ROLE_UAC)
{
emit signalNewCall(call_id, false);
}
char status[80];
if (ci.state == PJSIP_INV_STATE_DISCONNECTED) {
snprintf(status, sizeof(status), "Call is %s (%s)",
ci.state_text.ptr,
ci.last_status_text.ptr);
showStatus(status);
emit signalCallReleased();
} else {
snprintf(status, sizeof(status), "Call is %s", pjsip_inv_state_name(ci.state));
showStatus(status);
}
}
void MainWin::on_incoming_call(pjsua_acc_id acc_id, pjsua_call_id call_id,
pjsip_rx_data *rdata)
{
PJ_UNUSED_ARG(acc_id);
PJ_UNUSED_ARG(rdata);
if (currentCall_ != -1) {
pjsua_call_answer(call_id, PJSIP_SC_BUSY_HERE, NULL, NULL);
return;
}
emit signalNewCall(call_id, true);
pjsua_call_info ci;
char status[80];
pjsua_call_get_info(call_id, &ci);
snprintf(status, sizeof(status), "Incoming call from %.*s",
(int)ci.remote_info.slen, ci.remote_info.ptr);
showStatus(status);
}
void MainWin::on_call_media_state(pjsua_call_id call_id)
{
pjsua_call_info ci;
pjsua_call_get_info(call_id, &ci);
for (unsigned i=0; i<ci.media_cnt; ++i) {
if (ci.media[i].type == PJMEDIA_TYPE_AUDIO) {
switch (ci.media[i].status) {
case PJSUA_CALL_MEDIA_ACTIVE:
pjsua_conf_connect(ci.media[i].stream.aud.conf_slot, 0);
pjsua_conf_connect(0, ci.media[i].stream.aud.conf_slot);
break;
default:
break;
}
} else if (ci.media[i].type == PJMEDIA_TYPE_VIDEO) {
emit signalInitVideoWindow();
}
}
}
//
// pjsua callbacks
//
static void on_reg_state(pjsua_acc_id acc_id)
{
MainWin::instance()->on_reg_state(acc_id);
}
static void on_call_state(pjsua_call_id call_id, pjsip_event *e)
{
MainWin::instance()->on_call_state(call_id, e);
}
static void on_incoming_call(pjsua_acc_id acc_id, pjsua_call_id call_id,
pjsip_rx_data *rdata)
{
MainWin::instance()->on_incoming_call(acc_id, call_id, rdata);
}
static void on_call_media_state(pjsua_call_id call_id)
{
MainWin::instance()->on_call_media_state(call_id);
}
//
// initStack()
//
bool MainWin::initStack()
{
pj_status_t status;
//showStatus("Creating stack..");
status = pjsua_create();
if (status != PJ_SUCCESS) {
showError("pjsua_create", status);
return false;
}
showStatus("Initializing stack..");
pjsua_config ua_cfg;
pjsua_config_default(&ua_cfg);
pjsua_callback ua_cb;
pj_bzero(&ua_cb, sizeof(ua_cb));
ua_cfg.cb.on_reg_state = &::on_reg_state;
ua_cfg.cb.on_call_state = &::on_call_state;
ua_cfg.cb.on_incoming_call = &::on_incoming_call;
ua_cfg.cb.on_call_media_state = &::on_call_media_state;
#if USE_STUN
ua_cfg.stun_srv_cnt = 1;
ua_cfg.stun_srv[0] = pj_str((char*)STUN_SRV);
#endif
pjsua_logging_config log_cfg;
pjsua_logging_config_default(&log_cfg);
log_cfg.log_filename = pj_str((char*)LOG_FILE);
pjsua_media_config med_cfg;
pjsua_media_config_default(&med_cfg);
med_cfg.enable_ice = USE_ICE;
status = pjsua_init(&ua_cfg, &log_cfg, &med_cfg);
if (status != PJ_SUCCESS) {
showError("pjsua_init", status);
goto on_error;
}
//
// Create UDP and TCP transports
//
pjsua_transport_config udp_cfg;
pjsua_transport_id udp_id;
pjsua_transport_config_default(&udp_cfg);
udp_cfg.port = SIP_PORT;
status = pjsua_transport_create(PJSIP_TRANSPORT_UDP,
&udp_cfg, &udp_id);
if (status != PJ_SUCCESS) {
showError("UDP transport creation", status);
goto on_error;
}
pjsua_transport_info udp_info;
status = pjsua_transport_get_info(udp_id, &udp_info);
if (status != PJ_SUCCESS) {
showError("UDP transport info", status);
goto on_error;
}
#if SIP_TCP
pjsua_transport_config tcp_cfg;
pjsua_transport_config_default(&tcp_cfg);
tcp_cfg.port = 0;
status = pjsua_transport_create(PJSIP_TRANSPORT_TCP,
&tcp_cfg, NULL);
if (status != PJ_SUCCESS) {
showError("TCP transport creation", status);
goto on_error;
}
#endif
//
// Create account
//
pjsua_acc_config acc_cfg;
pjsua_acc_config_default(&acc_cfg);
#if USE_REGISTRATION
acc_cfg.id = pj_str( (char*)"<sip:" SIP_USERNAME "@" SIP_DOMAIN ">");
acc_cfg.reg_uri = pj_str((char*) ("sip:" SIP_DOMAIN));
acc_cfg.cred_count = 1;
acc_cfg.cred_info[0].realm = pj_str((char*)"*");
acc_cfg.cred_info[0].scheme = pj_str((char*)"digest");
acc_cfg.cred_info[0].username = pj_str((char*)SIP_USERNAME);
acc_cfg.cred_info[0].data = pj_str((char*)SIP_PASSWORD);
# if SIP_TCP
acc_cfg.proxy[acc_cfg.proxy_cnt++] = pj_str((char*) "<sip:" SIP_DOMAIN ";transport=tcp>");
# endif
#else
char sip_id[80];
snprintf(sip_id, sizeof(sip_id),
"sip:%s@%.*s:%u", SIP_USERNAME,
(int)udp_info.local_name.host.slen,
udp_info.local_name.host.ptr,
udp_info.local_name.port);
acc_cfg.id = pj_str(sip_id);
#endif
acc_cfg.vid_cap_dev = DEFAULT_CAP_DEV;
acc_cfg.vid_rend_dev = DEFAULT_REND_DEV;
acc_cfg.vid_in_auto_show = PJ_TRUE;
acc_cfg.vid_out_auto_transmit = PJ_TRUE;
status = pjsua_acc_add(&acc_cfg, PJ_TRUE, &accountId_);
if (status != PJ_SUCCESS) {
showError("Account creation", status);
goto on_error;
}
localUri_->setText(acc_cfg.id.ptr);
//
// Start pjsua!
//
showStatus("Starting stack..");
status = pjsua_start();
if (status != PJ_SUCCESS) {
showError("pjsua_start", status);
goto on_error;
}
showStatus("Ready");
return true;
on_error:
pjsua_destroy();
return false;
}
/*
* A simple registrar, invoked by default_mod_on_rx_request()
*/
static void simple_registrar(pjsip_rx_data *rdata)
{
pjsip_tx_data *tdata;
const pjsip_expires_hdr *exp;
const pjsip_hdr *h;
unsigned cnt = 0;
pjsip_generic_string_hdr *srv;
pj_status_t status;
status = pjsip_endpt_create_response(pjsua_get_pjsip_endpt(),
rdata, 200, NULL, &tdata);
if (status != PJ_SUCCESS)
return;
exp = (pjsip_expires_hdr*)
pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_EXPIRES, NULL);
h = rdata->msg_info.msg->hdr.next;
while (h != &rdata->msg_info.msg->hdr) {
if (h->type == PJSIP_H_CONTACT) {
const pjsip_contact_hdr *c = (const pjsip_contact_hdr*)h;
int e = c->expires;
if (e < 0) {
if (exp)
e = exp->ivalue;
else
e = 3600;
}
if (e > 0) {
pjsip_contact_hdr *nc = (pjsip_contact_hdr*)
pjsip_hdr_clone(tdata->pool, h);
nc->expires = e;
pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)nc);
++cnt;
}
}
h = h->next;
}
srv = pjsip_generic_string_hdr_create(tdata->pool, NULL, NULL);
srv->name = pj_str((char*)"Server");
srv->hvalue = pj_str((char*)"pjsua simple registrar");
pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)srv);
pjsip_endpt_send_response2(pjsua_get_pjsip_endpt(),
rdata, tdata, NULL, NULL);
}
/* Notification on incoming request */
static pj_bool_t default_mod_on_rx_request(pjsip_rx_data *rdata)
{
/* Simple registrar */
if (pjsip_method_cmp(&rdata->msg_info.msg->line.req.method,
&pjsip_register_method) == 0)
{
simple_registrar(rdata);
return PJ_TRUE;
}
return PJ_FALSE;
}
/* The module instance. */
static pjsip_module mod_default_handler =
{
NULL, NULL, /* prev, next. */
{ (char*)"mod-default-handler", 19 }, /* Name. */
-1, /* Id */
PJSIP_MOD_PRIORITY_APPLICATION+99, /* Priority */
NULL, /* load() */
NULL, /* start() */
NULL, /* stop() */
NULL, /* unload() */
&default_mod_on_rx_request, /* on_rx_request() */
NULL, /* on_rx_response() */
NULL, /* on_tx_request. */
NULL, /* on_tx_response() */
NULL, /* on_tsx_state() */
};
int main(int argc, char *argv[])
{
/* At least on Linux, we have to initialize SDL video subsystem prior to
* creating/initializing QApplication, otherwise we'll segfault miserably
* in SDL_CreateWindow(). Here's a stack trace if you're interested:
Thread [7] (Suspended: Signal 'SIGSEGV' received. Description: Segmentation fault.)
13 XCreateIC()
12 SetupWindowData()
11 X11_CreateWindow()
10 SDL_CreateWindow()
..
*/
if ( SDL_InitSubSystem(SDL_INIT_VIDEO) < 0 ) {
printf("Unable to init SDL: %s\n", SDL_GetError());
return 1;
}
QApplication app(argc, argv);
MainWin win;
win.show();
if (!win.initStack()) {
win.quit();
return 1;
}
/* We want to be registrar too! */
if (pjsua_get_pjsip_endpt()) {
pjsip_endpt_register_module(pjsua_get_pjsip_endpt(),
&mod_default_handler);
}
return app.exec();
}