Added support for playing WAV file

git-svn-id: https://svn.pjsip.org/repos/pjproject/trunk@222 74dad513-b988-da41-8d7b-12977e46ad98
This commit is contained in:
Benny Prijono 2006-02-23 02:09:10 +00:00
parent ed1056c859
commit 3987915f25
14 changed files with 540 additions and 12 deletions

View File

@ -108,6 +108,10 @@ SOURCE=..\src\pjmedia\errno.c
# End Source File
# Begin Source File
SOURCE=..\src\pjmedia\file_port.c
# End Source File
# Begin Source File
SOURCE=..\src\pjmedia\g711.c
# End Source File
# Begin Source File
@ -193,6 +197,10 @@ SOURCE=..\include\pjmedia\errno.h
# End Source File
# Begin Source File
SOURCE=..\include\pjmedia\file_port.h
# End Source File
# Begin Source File
SOURCE=..\include\pjmedia\jbuf.h
# End Source File
# Begin Source File
@ -243,6 +251,10 @@ SOURCE=..\include\pjmedia\types.h
SOURCE=..\include\pjmedia\vad.h
# End Source File
# Begin Source File
SOURCE=..\include\pjmedia\wave.h
# End Source File
# End Group
# Begin Group "PortAudio"

View File

@ -29,6 +29,7 @@
#include <pjmedia/conference.h>
#include <pjmedia/endpoint.h>
#include <pjmedia/errno.h>
#include <pjmedia/file_port.h>
#include <pjmedia/jbuf.h>
#include <pjmedia/port.h>
#include <pjmedia/rtcp.h>
@ -37,6 +38,7 @@
#include <pjmedia/sdp_neg.h>
#include <pjmedia/session.h>
#include <pjmedia/sound.h>
#include <pjmedia/wave.h>
#endif /* __PJMEDIA_H__ */

View File

@ -40,6 +40,7 @@ typedef struct pjmedia_conf pjmedia_conf;
*/
typedef struct pjmedia_conf_port_info
{
unsigned slot;
pj_str_t name;
pjmedia_port_op tx_setting;
pjmedia_port_op rx_setting;
@ -150,12 +151,34 @@ PJ_DECL(pj_status_t) pjmedia_conf_remove_port( pjmedia_conf *conf,
/**
* Get port info.
*
* @param conf The conference bridge.
* @param slot Port index.
* @param info Pointer to receive the info.
*
* @return PJ_SUCCESS on success.
*/
PJ_DECL(pj_status_t) pjmedia_conf_get_port_info( pjmedia_conf *conf,
unsigned slot,
pjmedia_conf_port_info *info);
/**
* Get occupied ports info.
*
* @param conf The conference bridge.
* @param size On input, contains maximum number of infos
* to be retrieved. On output, contains the actual
* number of infos that have been copied.
* @param info Array of info.
*
* @return PJ_SUCCESS on success.
*/
PJ_DECL(pj_status_t) pjmedia_conf_get_ports_info(pjmedia_conf *conf,
unsigned *size,
pjmedia_conf_port_info info[]);
PJ_END_DECL

View File

@ -406,6 +406,25 @@ PJ_BEGIN_DECL
#define PJMEDIA_ENCBYTES (PJMEDIA_ERRNO_START+165) /* 220165 */
/************************************************************
* FILE ERRORS
***********************************************************/
/**
* @hideinitializer
* Not a valid WAVE file.
*/
#define PJMEDIA_ENOTVALIDWAVE (PJMEDIA_ERRNO_START+180) /* 220180 */
/**
* @hideinitializer
* Unsupported WAVE file.
*/
#define PJMEDIA_EWAVEUNSUPP (PJMEDIA_ERRNO_START+181) /* 220181 */
/**
* @hideinitializer
* Wave file too short.
*/
#define PJMEDIA_EWAVETOOSHORT (PJMEDIA_ERRNO_START+182) /* 220182 */
PJ_END_DECL

View File

@ -0,0 +1,47 @@
/* $Id$ */
/*
* Copyright (C) 2003-2006 Benny Prijono <benny@prijono.org>
*
* 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
*/
#ifndef __PJMEDIA_FILE_PORT_H__
#define __PJMEDIA_FILE_PORT_H__
/**
* @file file_port.h
* @brief File player and recorder.
*/
#include <pjmedia/port.h>
PJ_BEGIN_DECL
/**
* Create file player port.
*/
PJ_DECL(pj_status_t) pjmedia_file_player_port_create( pj_pool_t *pool,
const char *filename,
unsigned flags,
pj_ssize_t buff_size,
void *user_data,
pjmedia_port **p_port );
PJ_END_DECL
#endif /* __PJMEDIA_FILE_PORT_H__ */

View File

@ -22,7 +22,7 @@
/**
* @file vad.h
* @brief Voice Activity Detector.
* @brief Simple, adaptive silence detector.
*/
#include <pjmedia/types.h>

View File

@ -0,0 +1,80 @@
/* $Id$ */
/*
* Copyright (C) 2003-2006 Benny Prijono <benny@prijono.org>
*
* 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
*/
#ifndef __PJMEDIA_WAVE_H__
#define __PJMEDIA_WAVE_H__
/**
* @file wave.h
* @brief WAVE file manipulation.
*/
#include <pjmedia/types.h>
PJ_BEGIN_DECL
#if PJ_IS_BIG_ENDIAN
# define PJMEDIA_RIFF_TAG ('R'<<24|'I'<<16|'F'<<8|'F')
# define PJMEDIA_WAVE_TAG ('W'<<24|'A'<<16|'V'<<8|'E')
# define PJMEDIA_FMT_TAG ('f'<<24|'m'<<16|'t'<<8|' ')
#else
# define PJMEDIA_RIFF_TAG ('F'<<24|'F'<<16|'I'<<8|'R')
# define PJMEDIA_WAVE_TAG ('E'<<24|'V'<<16|'A'<<8|'W')
# define PJMEDIA_FMT_TAG (' '<<24|'t'<<16|'m'<<8|'f')
#endif
/**
* This file describes the simpler/canonical version of a WAVE file.
* It does not support the full RIFF format specification.
*/
struct pjmedia_wave_hdr
{
struct {
pj_uint32_t riff;
pj_uint32_t file_len;
pj_uint32_t wave;
} riff_hdr;
struct {
pj_uint32_t fmt;
pj_uint32_t len;
pj_uint16_t fmt_tag;
pj_uint16_t nchan;
pj_uint32_t sample_rate;
pj_uint32_t bytes_per_sec;
pj_uint16_t block_align;
pj_uint16_t bits_per_sample;
} fmt_hdr;
struct {
pj_uint32_t data;
pj_uint32_t len;
} data_hdr;
};
/**
* @see pjmedia_wave_hdr
*/
typedef struct pjmedia_wave_hdr pjmedia_wave_hdr;
PJ_END_DECL
#endif /* __PJMEDIA_WAVE_H__ */

View File

@ -599,6 +599,7 @@ PJ_DEF(pj_status_t) pjmedia_conf_get_port_info( pjmedia_conf *conf,
conf_port = conf->ports[slot];
info->slot = slot;
info->name = conf_port->name;
info->tx_setting = conf_port->tx_setting;
info->rx_setting = conf_port->rx_setting;
@ -608,6 +609,27 @@ PJ_DEF(pj_status_t) pjmedia_conf_get_port_info( pjmedia_conf *conf,
}
PJ_DEF(pj_status_t) pjmedia_conf_get_ports_info(pjmedia_conf *conf,
unsigned *size,
pjmedia_conf_port_info info[])
{
unsigned i, count=0;
PJ_ASSERT_RETURN(conf && size && info, PJ_EINVAL);
for (i=0; i<conf->max_ports && count<*size; ++i) {
if (!conf->ports[i])
continue;
pjmedia_conf_get_port_info(conf, i, &info[count]);
++count;
}
*size = count;
return PJ_SUCCESS;
}
/* Convert signed 16bit pcm sample to unsigned 16bit sample */
static pj_uint16_t pcm2unsigned(pj_int32_t pcm)
{

View File

@ -112,6 +112,11 @@ static const struct
{ PJMEDIA_ENCTYPE, "Media ports have incompatible media type" },
{ PJMEDIA_ENCBITS, "Media ports have incompatible bits per sample" },
{ PJMEDIA_ENCBYTES, "Media ports have incompatible bytes per frame" },
/* Media file errors: */
{ PJMEDIA_ENOTVALIDWAVE, "Not a valid WAVE file" },
{ PJMEDIA_EWAVEUNSUPP, "Unsupported WAVE file format" },
{ PJMEDIA_EWAVETOOSHORT, "WAVE file too short" },
};

View File

@ -0,0 +1,261 @@
/* $Id$ */
/*
* Copyright (C) 2003-2006 Benny Prijono <benny@prijono.org>
*
* 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 <pjmedia/file_port.h>
#include <pjmedia/errno.h>
#include <pjmedia/wave.h>
#include <pj/assert.h>
#include <pj/file_access.h>
#include <pj/file_io.h>
#include <pj/pool.h>
#include <pj/string.h>
#define SIGNATURE ('F'<<24|'I'<<16|'L'<<8|'E')
#define BUF_SIZE (320*10)
struct file_port
{
pjmedia_port base;
pj_size_t bufsize;
char *buf;
char *readpos;
};
static pj_status_t file_put_frame(pjmedia_port *this_port,
const pjmedia_frame *frame);
static pj_status_t file_get_frame(pjmedia_port *this_port,
pjmedia_frame *frame);
static pj_status_t file_on_destroy(pjmedia_port *this_port);
static pj_status_t read_buffer(struct file_port *port);
static struct file_port *create_file_port(pj_pool_t *pool)
{
struct file_port *port;
port = pj_pool_zalloc(pool, sizeof(struct file_port));
if (!port)
return NULL;
port->base.info.name = pj_str("file");
port->base.info.signature = SIGNATURE;
port->base.info.type = PJMEDIA_TYPE_AUDIO;
port->base.info.has_info = PJ_TRUE;
port->base.info.need_info = PJ_FALSE;
port->base.info.pt = 0xFF;
port->base.info.encoding_name = pj_str("pcm");
port->base.info.sample_rate = 8000;
port->base.info.bits_per_sample = 16;
port->base.info.samples_per_frame = 160;
port->base.info.bytes_per_frame = 320;
port->base.put_frame = &file_put_frame;
port->base.get_frame = &file_get_frame;
port->base.on_destroy = &file_on_destroy;
return port;
}
/*
* Create WAVE player port.
*/
PJ_DEF(pj_status_t) pjmedia_file_player_port_create( pj_pool_t *pool,
const char *filename,
unsigned flags,
pj_ssize_t buff_size,
void *user_data,
pjmedia_port **p_port )
{
pj_off_t file_size;
pj_oshandle_t fd = NULL;
pjmedia_wave_hdr wave_hdr;
pj_ssize_t size_read;
struct file_port *file_port;
pj_status_t status;
PJ_UNUSED_ARG(flags);
PJ_UNUSED_ARG(buff_size);
/* Check arguments. */
PJ_ASSERT_RETURN(pool && filename && p_port, PJ_EINVAL);
/* Check the file really exists. */
if (!pj_file_exists(filename)) {
return PJ_ENOTFOUND;
}
/* Get the file size. */
file_size = pj_file_size(filename);
/* Size must be more than WAVE header size */
if (file_size <= sizeof(pjmedia_wave_hdr)) {
return PJMEDIA_ENOTVALIDWAVE;
}
/* Open file. */
status = pj_file_open( pool, filename, PJ_O_RDONLY, &fd);
if (status != PJ_SUCCESS)
return status;
/* Read the WAVE header. */
size_read = sizeof(wave_hdr);
status = pj_file_read( fd, &wave_hdr, &size_read);
if (status != PJ_SUCCESS) {
pj_file_close(fd);
return status;
}
if (size_read != sizeof(wave_hdr)) {
pj_file_close(fd);
return PJMEDIA_ENOTVALIDWAVE;
}
/* Validate WAVE file. */
if (wave_hdr.riff_hdr.riff != PJMEDIA_RIFF_TAG ||
wave_hdr.riff_hdr.wave != PJMEDIA_WAVE_TAG ||
wave_hdr.fmt_hdr.fmt != PJMEDIA_FMT_TAG)
{
pj_file_close(fd);
return PJMEDIA_ENOTVALIDWAVE;
}
if (wave_hdr.fmt_hdr.fmt_tag != 1 ||
wave_hdr.fmt_hdr.nchan != 1 ||
wave_hdr.fmt_hdr.sample_rate != 8000 ||
wave_hdr.fmt_hdr.bytes_per_sec != 16000 ||
wave_hdr.fmt_hdr.block_align != 2 ||
wave_hdr.fmt_hdr.bits_per_sample != 16)
{
pj_file_close(fd);
return PJMEDIA_EWAVEUNSUPP;
}
/* Validate length. */
if (wave_hdr.data_hdr.len != file_size-sizeof(pjmedia_wave_hdr)) {
pj_file_close(fd);
return PJMEDIA_EWAVEUNSUPP;
}
if (wave_hdr.data_hdr.len < 400) {
pj_file_close(fd);
return PJMEDIA_EWAVETOOSHORT;
}
/* It seems like we have a valid WAVE file. */
/* Create file_port instance. */
file_port = create_file_port(pool);
if (!file_port) {
pj_file_close(fd);
return PJ_ENOMEM;
}
/* Initialize */
file_port->base.user_data = user_data;
/* For this version, we only support reading the whole
* contents of the file.
*/
file_port->bufsize = wave_hdr.data_hdr.len - 8;
/* Create buffer. */
file_port->buf = pj_pool_alloc(pool, file_port->bufsize);
if (!file_port->buf) {
pj_file_close(fd);
return PJ_ENOMEM;
}
file_port->readpos = file_port->buf;
/* Read the the file. */
size_read = file_port->bufsize;
status = pj_file_read(fd, file_port->buf, &size_read);
if (status != PJ_SUCCESS) {
pj_file_close(fd);
return status;
}
if (size_read != (pj_ssize_t)file_port->bufsize) {
pj_file_close(fd);
return PJMEDIA_ENOTVALIDWAVE;
}
/* Done. */
pj_file_close(fd);
*p_port = &file_port->base;
return PJ_SUCCESS;
}
/*
* Put frame to file.
*/
static pj_status_t file_put_frame(pjmedia_port *this_port,
const pjmedia_frame *frame)
{
PJ_UNUSED_ARG(this_port);
PJ_UNUSED_ARG(frame);
return PJ_EINVALIDOP;
}
/*
* Get frame from file.
*/
static pj_status_t file_get_frame(pjmedia_port *this_port,
pjmedia_frame *frame)
{
struct file_port *port = (struct file_port*)this_port;
pj_assert(port->base.info.signature == SIGNATURE);
/* Copy frame from buffer. */
frame->type = PJMEDIA_FRAME_TYPE_AUDIO;
frame->size = 320;
frame->timestamp.u64 = 0;
if (port->readpos + 320 <= port->buf + port->bufsize) {
pj_memcpy(frame->buf, port->readpos, 320);
port->readpos += 320;
if (port->readpos == port->buf + port->bufsize)
port->readpos = port->buf;
} else {
unsigned endread;
endread = (port->buf+port->bufsize) - port->readpos;
pj_memcpy(frame->buf, port->readpos, endread);
pj_memcpy(((char*)frame->buf)+endread, port->buf, 320-endread);
port->readpos = port->buf + (320-endread);
}
return PJ_SUCCESS;
}
/*
*
*/
static pj_status_t file_on_destroy(pjmedia_port *this_port)
{
PJ_UNUSED_ARG(this_port);
pj_assert(this_port->info.signature == SIGNATURE);
return PJ_SUCCESS;
}

View File

@ -261,21 +261,34 @@ static void ui_input_url(const char *title, char *buf, int len,
static void conf_list(void)
{
pjmedia_conf_port_info info;
struct pjsua_inv_data *inv_data;
unsigned i, count;
pjmedia_conf_port_info info[16];
printf("Conference ports:\n");
inv_data = pjsua.inv_list.next;
while (inv_data != &pjsua.inv_list) {
count = PJ_ARRAY_SIZE(info);
pjmedia_conf_get_ports_info(pjsua.mconf, &count, info);
for (i=0; i<count; ++i) {
char txlist[80];
unsigned j;
pjmedia_conf_port_info *port_info = &info[i];
pjmedia_conf_get_port_info(pjsua.mconf, inv_data->conf_slot, &info);
txlist[0] = '\0';
for (j=0; j<pjsua.max_ports; ++j) {
char s[10];
if (port_info->listener[j]) {
pj_sprintf(s, "#%d ", j);
pj_ansi_strcat(txlist, s);
}
}
printf("Port #%02d %20.*s tx to: %s\n",
port_info->slot,
(int)port_info->name.slen,
port_info->name.ptr,
txlist);
printf("Port %2d %.*s\n", inv_data->conf_slot,
(int)info.name.slen, info.name.ptr);
inv_data = inv_data->next;
}
puts("");
}

View File

@ -115,8 +115,11 @@ struct pjsua
/* Media: */
pjmedia_endpt *med_endpt; /**< Media endpoint. */
unsigned max_ports; /**< Max ports in conf. */
pjmedia_conf *mconf; /**< Media conference. */
pj_bool_t null_audio; /**< Null audio flag. */
char *wav_file; /**< WAV file name to play. */
unsigned wav_slot; /**< WAV player slot in bridge */
/* Since we support simultaneous calls, we need to have multiple

View File

@ -76,6 +76,10 @@ void pjsua_default(void)
pjsua.reg_timeout = 55;
/* Default maximum conference ports: */
pjsua.max_ports = 8;
/* Init route set list: */
pj_list_init(&pjsua.route_set);
@ -517,7 +521,8 @@ pj_status_t pjsua_init(void)
/* Init conference bridge. */
status = pjmedia_conf_create(pjsua.pool, 8, 8000, 160, 16, &pjsua.mconf);
status = pjmedia_conf_create(pjsua.pool, pjsua.max_ports,
8000, 160, 16, &pjsua.mconf);
if (status != PJ_SUCCESS) {
pj_caching_pool_destroy(&pjsua.cp);
pjsua_perror(THIS_FILE,
@ -554,6 +559,35 @@ pj_status_t pjsua_start(void)
pjsip_transport *udp_transport;
pj_status_t status = PJ_SUCCESS;
/* Create WAV file player if required: */
if (pjsua.wav_file) {
pjmedia_port *port;
pj_str_t port_name;
/* Create the file player port. */
status = pjmedia_file_player_port_create( pjsua.pool, pjsua.wav_file,
0, -1, NULL, &port);
if (status != PJ_SUCCESS) {
pjsua_perror(THIS_FILE,
"Error playing media file",
status);
return status;
}
/* Add port to conference bridge: */
status = pjmedia_conf_add_port(pjsua.mconf, pjsua.pool, port,
pj_cstr(&port_name, pjsua.wav_file),
&pjsua.wav_slot);
if (status != PJ_SUCCESS) {
pjsua_perror(THIS_FILE,
"Unable to add file player to conference bridge",
status);
return status;
}
}
/* Init sockets (STUN etc): */
for (i=0; i<PJ_ARRAY_SIZE(pjsua.med_sock_info); ++i) {
status = init_sockets(i==0, &pjsua.med_sock_info[i]);

View File

@ -55,6 +55,8 @@ static void usage(void)
puts("");
puts("Media options:");
puts(" --null-audio Use NULL audio device");
puts(" --wav-file=file Play WAV file in conference bridge");
puts("");
//puts("");
//puts("User Agent options:");
//puts(" --auto-answer=sec Auto-answer all incoming calls after sec seconds.");
@ -196,7 +198,7 @@ pj_status_t pjsua_parse_args(int argc, char *argv[])
OPT_REALM, OPT_USERNAME, OPT_PASSWORD,
OPT_USE_STUN1, OPT_USE_STUN2,
OPT_ADD_BUDDY, OPT_OFFER_X_MS_MSG, OPT_NO_PRESENCE,
OPT_AUTO_ANSWER, OPT_AUTO_HANGUP};
OPT_AUTO_ANSWER, OPT_AUTO_HANGUP, OPT_WAV_FILE};
struct option long_options[] = {
{ "config-file",1, 0, OPT_CONFIG_FILE},
{ "log-file", 1, 0, OPT_LOG_FILE},
@ -222,6 +224,7 @@ pj_status_t pjsua_parse_args(int argc, char *argv[])
{ "no-presence", 0, 0, OPT_NO_PRESENCE},
{ "auto-answer",1, 0, OPT_AUTO_ANSWER},
{ "auto-hangup",1, 0, OPT_AUTO_HANGUP},
{ "wav-file", 1, 0, OPT_WAV_FILE},
{ NULL, 0, 0, 0}
};
pj_status_t status;
@ -405,6 +408,10 @@ pj_status_t pjsua_parse_args(int argc, char *argv[])
}
pjsua.buddies[pjsua.buddy_cnt++].uri = pj_str(optarg);
break;
case OPT_WAV_FILE:
pjsua.wav_file = optarg;
break;
}
}