/* $Id$ */ /* * Copyright (C)2003-2007 Benny Prijono * * 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 #include #include #include #include #include #include #include #include #include #include /* * Only build this file if PJMEDIA_HAS_SPEEX_CODEC != 0 */ #if defined(PJMEDIA_HAS_SPEEX_CODEC) && PJMEDIA_HAS_SPEEX_CODEC!=0 #define THIS_FILE "speex_codec.c" #define DEFAULT_QUALITY 10 #define DEFAULT_COMPLEXITY 10 /* Prototypes for Speex factory */ static pj_status_t spx_test_alloc( pjmedia_codec_factory *factory, const pjmedia_codec_info *id ); static pj_status_t spx_default_attr( pjmedia_codec_factory *factory, const pjmedia_codec_info *id, pjmedia_codec_param *attr ); static pj_status_t spx_enum_codecs( pjmedia_codec_factory *factory, unsigned *count, pjmedia_codec_info codecs[]); static pj_status_t spx_alloc_codec( pjmedia_codec_factory *factory, const pjmedia_codec_info *id, pjmedia_codec **p_codec); static pj_status_t spx_dealloc_codec( pjmedia_codec_factory *factory, pjmedia_codec *codec ); /* Prototypes for Speex implementation. */ static pj_status_t spx_codec_init( pjmedia_codec *codec, pj_pool_t *pool ); static pj_status_t spx_codec_open( pjmedia_codec *codec, pjmedia_codec_param *attr ); static pj_status_t spx_codec_close( pjmedia_codec *codec ); static pj_status_t spx_codec_modify(pjmedia_codec *codec, const pjmedia_codec_param *attr ); static pj_status_t spx_codec_parse( pjmedia_codec *codec, void *pkt, pj_size_t pkt_size, const pj_timestamp *ts, unsigned *frame_cnt, pjmedia_frame frames[]); static pj_status_t spx_codec_encode( pjmedia_codec *codec, const struct pjmedia_frame *input, unsigned output_buf_len, struct pjmedia_frame *output); static pj_status_t spx_codec_decode( pjmedia_codec *codec, const struct pjmedia_frame *input, unsigned output_buf_len, struct pjmedia_frame *output); static pj_status_t spx_codec_recover(pjmedia_codec *codec, unsigned output_buf_len, struct pjmedia_frame *output); /* Definition for Speex codec operations. */ static pjmedia_codec_op spx_op = { &spx_codec_init, &spx_codec_open, &spx_codec_close, &spx_codec_modify, &spx_codec_parse, &spx_codec_encode, &spx_codec_decode, &spx_codec_recover }; /* Definition for Speex codec factory operations. */ static pjmedia_codec_factory_op spx_factory_op = { &spx_test_alloc, &spx_default_attr, &spx_enum_codecs, &spx_alloc_codec, &spx_dealloc_codec }; /* Index to Speex parameter. */ enum { PARAM_NB, /* Index for narrowband parameter. */ PARAM_WB, /* Index for wideband parameter. */ PARAM_UWB, /* Index for ultra-wideband parameter */ }; /* Speex default parameter */ struct speex_param { int enabled; /* Is this mode enabled? */ const SpeexMode *mode; /* Speex mode. */ int pt; /* Payload type. */ unsigned clock_rate; /* Default sampling rate to be used.*/ int quality; /* Default encoder quality. */ int complexity; /* Default encoder complexity. */ int samples_per_frame; /* Samples per frame. */ int framesize; /* Frame size for current mode. */ int bitrate; /* Bit rate for current mode. */ }; /* Speex factory */ static struct spx_factory { pjmedia_codec_factory base; pjmedia_endpt *endpt; pj_pool_t *pool; pj_mutex_t *mutex; pjmedia_codec codec_list; struct speex_param speex_param[3]; } spx_factory; /* Speex codec private data. */ struct spx_private { int param_id; /**< Index to speex param. */ void *enc; /**< Encoder state. */ SpeexBits enc_bits; /**< Encoder bits. */ void *dec; /**< Decoder state. */ SpeexBits dec_bits; /**< Decoder bits. */ }; /* * Get codec bitrate and frame size. */ static pj_status_t get_speex_info( struct speex_param *p ) { void *state; int tmp; /* Create temporary encoder */ state = speex_encoder_init(p->mode); if (!state) return PJMEDIA_CODEC_EFAILED; /* Set the quality */ if (p->quality != -1) speex_encoder_ctl(state, SPEEX_SET_QUALITY, &p->quality); /* Sampling rate. */ speex_encoder_ctl(state, SPEEX_SET_SAMPLING_RATE, &p->clock_rate); /* VAD off to have max bitrate */ tmp = 0; speex_encoder_ctl(state, SPEEX_SET_VAD, &tmp); /* Complexity. */ if (p->complexity != -1) speex_encoder_ctl(state, SPEEX_SET_COMPLEXITY, &p->complexity); /* Now get the frame size */ speex_encoder_ctl(state, SPEEX_GET_FRAME_SIZE, &p->samples_per_frame); /* Now get the the averate bitrate */ speex_encoder_ctl(state, SPEEX_GET_BITRATE, &p->bitrate); /* Calculate framesize. */ p->framesize = p->bitrate * 20 / 1000; /* Destroy encoder. */ speex_encoder_destroy(state); return PJ_SUCCESS; } /* * Initialize and register Speex codec factory to pjmedia endpoint. */ PJ_DEF(pj_status_t) pjmedia_codec_speex_init( pjmedia_endpt *endpt, unsigned options, int quality, int complexity ) { pjmedia_codec_mgr *codec_mgr; unsigned i; pj_status_t status; if (spx_factory.pool != NULL) { /* Already initialized. */ return PJ_SUCCESS; } /* Get defaults */ if (quality <= 0) quality = DEFAULT_QUALITY; if (complexity <= 0) complexity = DEFAULT_COMPLEXITY; /* Create Speex codec factory. */ spx_factory.base.op = &spx_factory_op; spx_factory.base.factory_data = NULL; spx_factory.endpt = endpt; spx_factory.pool = pjmedia_endpt_create_pool(endpt, "speex", 4000, 4000); if (!spx_factory.pool) return PJ_ENOMEM; pj_list_init(&spx_factory.codec_list); /* Create mutex. */ status = pj_mutex_create_simple(spx_factory.pool, "speex", &spx_factory.mutex); if (status != PJ_SUCCESS) goto on_error; /* Initialize default Speex parameter. */ spx_factory.speex_param[PARAM_NB].enabled = ((options & PJMEDIA_SPEEX_NO_NB) == 0); spx_factory.speex_param[PARAM_NB].pt = PJMEDIA_RTP_PT_SPEEX_NB; spx_factory.speex_param[PARAM_NB].mode = &speex_nb_mode; spx_factory.speex_param[PARAM_NB].clock_rate = 8000; spx_factory.speex_param[PARAM_NB].quality = quality; spx_factory.speex_param[PARAM_NB].complexity = complexity; spx_factory.speex_param[PARAM_WB].enabled = ((options & PJMEDIA_SPEEX_NO_WB) == 0); spx_factory.speex_param[PARAM_WB].pt = PJMEDIA_RTP_PT_SPEEX_WB; spx_factory.speex_param[PARAM_WB].mode = &speex_wb_mode; spx_factory.speex_param[PARAM_WB].clock_rate = 16000; spx_factory.speex_param[PARAM_WB].quality = quality; spx_factory.speex_param[PARAM_WB].complexity = complexity; spx_factory.speex_param[PARAM_UWB].enabled = ((options & PJMEDIA_SPEEX_NO_UWB) == 0); spx_factory.speex_param[PARAM_UWB].pt = PJMEDIA_RTP_PT_SPEEX_UWB; spx_factory.speex_param[PARAM_UWB].mode = &speex_uwb_mode; spx_factory.speex_param[PARAM_UWB].clock_rate = 32000; spx_factory.speex_param[PARAM_UWB].quality = quality; spx_factory.speex_param[PARAM_UWB].complexity = complexity; /* Somehow quality <=4 is broken in linux. */ if (quality <= 4 && quality >= 0) { PJ_LOG(5,(THIS_FILE, "Adjusting quality to 5 for uwb")); spx_factory.speex_param[PARAM_UWB].quality = 5; } /* Get codec framesize and avg bitrate for each mode. */ for (i=0; itype != PJMEDIA_TYPE_AUDIO) return PJMEDIA_CODEC_EUNSUP; /* Check encoding name. */ if (pj_stricmp(&info->encoding_name, &speex_tag) != 0) return PJMEDIA_CODEC_EUNSUP; /* Check clock-rate */ for (i=0; iclock_rate == spx_factory.speex_param[i].clock_rate) { /* Okay, let's Speex! */ return PJ_SUCCESS; } } /* Unsupported, or mode is disabled. */ return PJMEDIA_CODEC_EUNSUP; } /* * Generate default attribute. */ static pj_status_t spx_default_attr (pjmedia_codec_factory *factory, const pjmedia_codec_info *id, pjmedia_codec_param *attr ) { PJ_ASSERT_RETURN(factory==&spx_factory.base, PJ_EINVAL); pj_bzero(attr, sizeof(pjmedia_codec_param)); attr->info.pt = (pj_uint8_t)id->pt; attr->info.channel_cnt = 1; if (id->clock_rate <= 8000) { attr->info.clock_rate = spx_factory.speex_param[PARAM_NB].clock_rate; attr->info.avg_bps = spx_factory.speex_param[PARAM_NB].bitrate; } else if (id->clock_rate <= 16000) { attr->info.clock_rate = spx_factory.speex_param[PARAM_WB].clock_rate; attr->info.avg_bps = spx_factory.speex_param[PARAM_WB].bitrate; } else { /* Wow.. somebody is doing ultra-wideband. Cool...! */ attr->info.clock_rate = spx_factory.speex_param[PARAM_UWB].clock_rate; attr->info.avg_bps = spx_factory.speex_param[PARAM_UWB].bitrate; } attr->info.pcm_bits_per_sample = 16; attr->info.frm_ptime = 20; attr->info.pt = (pj_uint8_t)id->pt; attr->setting.frm_per_pkt = 1; /* Default flags. */ attr->setting.cng = 1; attr->setting.plc = 1; attr->setting.penh =1 ; attr->setting.vad = 1; return PJ_SUCCESS; } /* * Enum codecs supported by this factory (i.e. only Speex!). */ static pj_status_t spx_enum_codecs(pjmedia_codec_factory *factory, unsigned *count, pjmedia_codec_info codecs[]) { unsigned max; int i; /* Must be signed */ PJ_UNUSED_ARG(factory); PJ_ASSERT_RETURN(codecs && *count > 0, PJ_EINVAL); max = *count; *count = 0; /* * We return three codecs here, and in this order: * - ultra-wideband, wideband, and narrowband. */ for (i=PJ_ARRAY_SIZE(spx_factory.speex_param)-1; i>=0 && *countop = &spx_op; codec->factory = factory; codec->codec_data = pj_pool_alloc(spx_factory.pool, sizeof(struct spx_private)); } pj_mutex_unlock(spx_factory.mutex); spx = (struct spx_private*) codec->codec_data; spx->enc = NULL; spx->dec = NULL; if (id->clock_rate <= 8000) spx->param_id = PARAM_NB; else if (id->clock_rate <= 16000) spx->param_id = PARAM_WB; else spx->param_id = PARAM_UWB; *p_codec = codec; return PJ_SUCCESS; } /* * Free codec. */ static pj_status_t spx_dealloc_codec( pjmedia_codec_factory *factory, pjmedia_codec *codec ) { struct spx_private *spx; PJ_ASSERT_RETURN(factory && codec, PJ_EINVAL); PJ_ASSERT_RETURN(factory == &spx_factory.base, PJ_EINVAL); /* Close codec, if it's not closed. */ spx = (struct spx_private*) codec->codec_data; if (spx->enc != NULL || spx->dec != NULL) { spx_codec_close(codec); } /* Put in the free list. */ pj_mutex_lock(spx_factory.mutex); pj_list_push_front(&spx_factory.codec_list, codec); pj_mutex_unlock(spx_factory.mutex); return PJ_SUCCESS; } /* * Init codec. */ static pj_status_t spx_codec_init( pjmedia_codec *codec, pj_pool_t *pool ) { PJ_UNUSED_ARG(codec); PJ_UNUSED_ARG(pool); return PJ_SUCCESS; } /* * Open codec. */ static pj_status_t spx_codec_open( pjmedia_codec *codec, pjmedia_codec_param *attr ) { struct spx_private *spx; int id, tmp; spx = (struct spx_private*) codec->codec_data; id = spx->param_id; /* Only supports one frame per packet */ PJ_ASSERT_RETURN(attr->setting.frm_per_pkt==1, PJ_EINVAL); /* * Create and initialize encoder. */ spx->enc = speex_encoder_init(spx_factory.speex_param[id].mode); if (!spx->enc) return PJMEDIA_CODEC_EFAILED; speex_bits_init(&spx->enc_bits); /* Set the quality*/ if (spx_factory.speex_param[id].quality != -1) { speex_encoder_ctl(spx->enc, SPEEX_SET_QUALITY, &spx_factory.speex_param[id].quality); } /* Sampling rate. */ tmp = attr->info.clock_rate; speex_encoder_ctl(spx->enc, SPEEX_SET_SAMPLING_RATE, &spx_factory.speex_param[id].clock_rate); /* VAD */ tmp = (attr->setting.vad != 0); speex_encoder_ctl(spx->enc, SPEEX_SET_VAD, &tmp); speex_encoder_ctl(spx->enc, SPEEX_SET_DTX, &tmp); /* Complexity */ if (spx_factory.speex_param[id].complexity != -1) { speex_encoder_ctl(spx->enc, SPEEX_SET_COMPLEXITY, &spx_factory.speex_param[id].complexity); } /* * Create and initialize decoder. */ spx->dec = speex_decoder_init(spx_factory.speex_param[id].mode); if (!spx->dec) { spx_codec_close(codec); return PJMEDIA_CODEC_EFAILED; } speex_bits_init(&spx->dec_bits); /* Sampling rate. */ speex_decoder_ctl(spx->dec, SPEEX_SET_SAMPLING_RATE, &spx_factory.speex_param[id].clock_rate); /* PENH */ tmp = attr->setting.penh; speex_decoder_ctl(spx->dec, SPEEX_SET_ENH, &tmp); return PJ_SUCCESS; } /* * Close codec. */ static pj_status_t spx_codec_close( pjmedia_codec *codec ) { struct spx_private *spx; spx = (struct spx_private*) codec->codec_data; /* Destroy encoder*/ if (spx->enc) { speex_encoder_destroy( spx->enc ); spx->enc = NULL; speex_bits_destroy( &spx->enc_bits ); } /* Destroy decoder */ if (spx->dec) { speex_decoder_destroy( spx->dec); spx->dec = NULL; speex_bits_destroy( &spx->dec_bits ); } return PJ_SUCCESS; } /* * Modify codec settings. */ static pj_status_t spx_codec_modify(pjmedia_codec *codec, const pjmedia_codec_param *attr ) { struct spx_private *spx; int tmp; spx = (struct spx_private*) codec->codec_data; /* Only supports one frame per packet */ PJ_ASSERT_RETURN(attr->setting.frm_per_pkt==1, PJ_EINVAL); /* VAD */ tmp = (attr->setting.vad != 0); speex_encoder_ctl(spx->enc, SPEEX_SET_VAD, &tmp); speex_encoder_ctl(spx->enc, SPEEX_SET_DTX, &tmp); /* PENH */ tmp = attr->setting.penh; speex_decoder_ctl(spx->dec, SPEEX_SET_ENH, &tmp); return PJ_SUCCESS; } /* * Get frames in the packet. */ static pj_status_t spx_codec_parse( pjmedia_codec *codec, void *pkt, pj_size_t pkt_size, const pj_timestamp *ts, unsigned *frame_cnt, pjmedia_frame frames[]) { struct spx_private *spx; unsigned frame_size, samples_per_frame; unsigned count; spx = (struct spx_private*) codec->codec_data; frame_size = spx_factory.speex_param[spx->param_id].framesize; samples_per_frame = spx_factory.speex_param[spx->param_id].samples_per_frame; /* Don't really know how to do this... */ count = 0; while (pkt_size >= frame_size && count < *frame_cnt) { frames[count].buf = pkt; frames[count].size = frame_size; frames[count].type = PJMEDIA_FRAME_TYPE_AUDIO; frames[count].timestamp.u64 = ts->u64 + count * samples_per_frame; pkt_size -= frame_size; ++count; pkt = ((char*)pkt) + frame_size; } /* Just in case speex has silence frame which size is less than normal * frame size... */ if (pkt_size && count < *frame_cnt) { frames[count].buf = pkt; frames[count].size = pkt_size; frames[count].type = PJMEDIA_FRAME_TYPE_AUDIO; frames[count].timestamp.u64 = ts->u64 + count * samples_per_frame; ++count; } *frame_cnt = count; return PJ_SUCCESS; } /* * Encode frame. */ static pj_status_t spx_codec_encode( pjmedia_codec *codec, const struct pjmedia_frame *input, unsigned output_buf_len, struct pjmedia_frame *output) { struct spx_private *spx; unsigned sz; int tx; spx = (struct spx_private*) codec->codec_data; if (input->type != PJMEDIA_FRAME_TYPE_AUDIO) { output->size = 0; output->buf = NULL; output->timestamp = input->timestamp; output->type = input->type; return PJ_SUCCESS; } /* Flush all the bits in the struct so we can encode a new frame */ speex_bits_reset(&spx->enc_bits); /* Encode the frame */ tx = speex_encode_int(spx->enc, (spx_int16_t*)input->buf, &spx->enc_bits); /* Check if we need not to transmit the frame (DTX) */ if (tx == 0) { output->buf = NULL; output->size = 0; output->timestamp.u64 = input->timestamp.u64; output->type = PJMEDIA_FRAME_TYPE_NONE; return PJ_SUCCESS; } /* Check size. */ sz = speex_bits_nbytes(&spx->enc_bits); pj_assert(sz <= output_buf_len); /* Copy the bits to an array of char that can be written */ output->size = speex_bits_write(&spx->enc_bits, (char*)output->buf, output_buf_len); output->type = PJMEDIA_FRAME_TYPE_AUDIO; output->timestamp = input->timestamp; return PJ_SUCCESS; } /* * Decode frame. */ static pj_status_t spx_codec_decode( pjmedia_codec *codec, const struct pjmedia_frame *input, unsigned output_buf_len, struct pjmedia_frame *output) { struct spx_private *spx; spx = (struct spx_private*) codec->codec_data; PJ_ASSERT_RETURN(output_buf_len >= 320, PJMEDIA_CODEC_EPCMTOOSHORT); if (input->type != PJMEDIA_FRAME_TYPE_AUDIO) { pjmedia_zero_samples((pj_int16_t*)output->buf, 160); output->size = 320; output->timestamp.u64 = input->timestamp.u64; output->type = PJMEDIA_FRAME_TYPE_AUDIO; return PJ_SUCCESS; } /* Copy the data into the bit-stream struct */ speex_bits_read_from(&spx->dec_bits, (char*)input->buf, input->size); /* Decode the data */ speex_decode_int(spx->dec, &spx->dec_bits, (spx_int16_t*)output->buf); output->type = PJMEDIA_FRAME_TYPE_AUDIO; output->size = 320; output->timestamp.u64 = input->timestamp.u64; return PJ_SUCCESS; } /* * Recover lost frame. */ static pj_status_t spx_codec_recover(pjmedia_codec *codec, unsigned output_buf_len, struct pjmedia_frame *output) { struct spx_private *spx; unsigned count; /* output_buf_len is unreferenced when building in Release mode */ PJ_UNUSED_ARG(output_buf_len); spx = (struct spx_private*) codec->codec_data; count = spx_factory.speex_param[spx->param_id].clock_rate * 20 / 1000; pj_assert(count <= output_buf_len/2); /* Recover packet loss */ speex_decode_int(spx->dec, NULL, (spx_int16_t*) output->buf); output->size = count * 2; return PJ_SUCCESS; } #endif /* PJMEDIA_HAS_SPEEX_CODEC */