1376 lines
49 KiB
C++
1376 lines
49 KiB
C++
/*
|
|
* Copyright (C)2020 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 <pjmedia-codec/and_aud_mediacodec.h>
|
|
#include <pjmedia-codec/amr_sdp_match.h>
|
|
#include <pjmedia/codec.h>
|
|
#include <pjmedia/errno.h>
|
|
#include <pjmedia/endpoint.h>
|
|
#include <pjmedia/plc.h>
|
|
#include <pjmedia/port.h>
|
|
#include <pjmedia/silencedet.h>
|
|
#include <pj/assert.h>
|
|
#include <pj/log.h>
|
|
#include <pj/math.h>
|
|
#include <pj/pool.h>
|
|
#include <pj/string.h>
|
|
#include <pj/os.h>
|
|
|
|
/*
|
|
* Only build this file if PJMEDIA_HAS_ANDROID_MEDIACODEC != 0
|
|
*/
|
|
#if defined(PJMEDIA_HAS_ANDROID_MEDIACODEC) && \
|
|
PJMEDIA_HAS_ANDROID_MEDIACODEC != 0
|
|
|
|
/* Android AMediaCodec: */
|
|
#include "media/NdkMediaCodec.h"
|
|
|
|
#define THIS_FILE "and_aud_mediacodec.cpp"
|
|
|
|
#define AND_MEDIA_KEY_PCM_ENCODING "pcm-encoding"
|
|
#define AND_MEDIA_KEY_CHANNEL_COUNT "channel-count"
|
|
#define AND_MEDIA_KEY_SAMPLE_RATE "sample-rate"
|
|
#define AND_MEDIA_KEY_BITRATE "bitrate"
|
|
#define AND_MEDIA_KEY_MIME "mime"
|
|
|
|
#define CODEC_WAIT_RETRY 10
|
|
#define CODEC_THREAD_WAIT 10
|
|
/* Timeout until the buffer is ready in ms. */
|
|
#define CODEC_DEQUEUE_TIMEOUT 10
|
|
|
|
/* Prototypes for Android MediaCodec codecs factory */
|
|
static pj_status_t and_media_test_alloc(pjmedia_codec_factory *factory,
|
|
const pjmedia_codec_info *id );
|
|
static pj_status_t and_media_default_attr(pjmedia_codec_factory *factory,
|
|
const pjmedia_codec_info *id,
|
|
pjmedia_codec_param *attr );
|
|
static pj_status_t and_media_enum_codecs(pjmedia_codec_factory *factory,
|
|
unsigned *count,
|
|
pjmedia_codec_info codecs[]);
|
|
static pj_status_t and_media_alloc_codec(pjmedia_codec_factory *factory,
|
|
const pjmedia_codec_info *id,
|
|
pjmedia_codec **p_codec);
|
|
static pj_status_t and_media_dealloc_codec(pjmedia_codec_factory *factory,
|
|
pjmedia_codec *codec );
|
|
|
|
/* Prototypes for Android MediaCodec codecs implementation. */
|
|
static pj_status_t and_media_codec_init(pjmedia_codec *codec,
|
|
pj_pool_t *pool );
|
|
static pj_status_t and_media_codec_open(pjmedia_codec *codec,
|
|
pjmedia_codec_param *attr );
|
|
static pj_status_t and_media_codec_close(pjmedia_codec *codec );
|
|
static pj_status_t and_media_codec_modify(pjmedia_codec *codec,
|
|
const pjmedia_codec_param *attr );
|
|
static pj_status_t and_media_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 and_media_codec_encode(pjmedia_codec *codec,
|
|
const struct pjmedia_frame *input,
|
|
unsigned output_buf_len,
|
|
struct pjmedia_frame *output);
|
|
static pj_status_t and_media_codec_decode(pjmedia_codec *codec,
|
|
const struct pjmedia_frame *input,
|
|
unsigned output_buf_len,
|
|
struct pjmedia_frame *output);
|
|
static pj_status_t and_media_codec_recover(pjmedia_codec *codec,
|
|
unsigned output_buf_len,
|
|
struct pjmedia_frame *output);
|
|
|
|
/* Definition for Android MediaCodec codecs operations. */
|
|
static pjmedia_codec_op and_media_op =
|
|
{
|
|
&and_media_codec_init,
|
|
&and_media_codec_open,
|
|
&and_media_codec_close,
|
|
&and_media_codec_modify,
|
|
&and_media_codec_parse,
|
|
&and_media_codec_encode,
|
|
&and_media_codec_decode,
|
|
&and_media_codec_recover
|
|
};
|
|
|
|
/* Definition for Android MediaCodec codecs factory operations. */
|
|
static pjmedia_codec_factory_op and_media_factory_op =
|
|
{
|
|
&and_media_test_alloc,
|
|
&and_media_default_attr,
|
|
&and_media_enum_codecs,
|
|
&and_media_alloc_codec,
|
|
&and_media_dealloc_codec,
|
|
&pjmedia_codec_and_media_aud_deinit
|
|
};
|
|
|
|
/* Android MediaCodec codecs factory */
|
|
static struct and_media_factory {
|
|
pjmedia_codec_factory base;
|
|
pjmedia_endpt *endpt;
|
|
pj_pool_t *pool;
|
|
pj_mutex_t *mutex;
|
|
} and_media_factory;
|
|
|
|
typedef enum and_aud_codec_id {
|
|
/* AMRNB codec. */
|
|
AND_AUD_CODEC_AMRNB,
|
|
|
|
/* AMRWB codec. */
|
|
AND_AUD_CODEC_AMRWB
|
|
} and_aud_codec_id;
|
|
|
|
/* Android MediaCodec codecs private data. */
|
|
typedef struct and_media_private {
|
|
int codec_idx; /**< Codec index. */
|
|
void *codec_setting; /**< Specific codec setting. */
|
|
pj_pool_t *pool; /**< Pool for each instance. */
|
|
AMediaCodec *enc; /**< Encoder state. */
|
|
AMediaCodec *dec; /**< Decoder state. */
|
|
|
|
pj_uint16_t frame_size; /**< Bitstream frame size. */
|
|
|
|
pj_bool_t plc_enabled; /**< PLC enabled flag. */
|
|
pjmedia_plc *plc; /**< PJMEDIA PLC engine, NULL if
|
|
codec has internal PLC. */
|
|
|
|
pj_bool_t vad_enabled; /**< VAD enabled flag. */
|
|
pjmedia_silence_det *vad; /**< PJMEDIA VAD engine, NULL if
|
|
codec has internal VAD. */
|
|
pj_timestamp last_tx; /**< Timestamp of last transmit.*/
|
|
} and_media_private_t;
|
|
|
|
/* CUSTOM CALLBACKS */
|
|
|
|
/* Parse frames from a packet. Default behaviour of frame parsing is
|
|
* just separating frames based on calculating frame length derived
|
|
* from bitrate. Implement this callback when the default behaviour is
|
|
* unapplicable.
|
|
*/
|
|
typedef pj_status_t (*parse_cb)(and_media_private_t *codec_data, void *pkt,
|
|
pj_size_t pkt_size, const pj_timestamp *ts,
|
|
unsigned *frame_cnt, pjmedia_frame frames[]);
|
|
|
|
/* Pack frames into a packet. Default behaviour of packing frames is
|
|
* just stacking the frames with octet aligned without adding any
|
|
* payload header. Implement this callback when the default behaviour is
|
|
* unapplicable.
|
|
*/
|
|
typedef pj_status_t (*pack_cb)(and_media_private_t *codec_data,
|
|
unsigned nframes, void *pkt, pj_size_t *pkt_size,
|
|
pj_size_t max_pkt_size);
|
|
|
|
/* This callback is useful for preparing a frame before pass it to decoder.
|
|
*/
|
|
typedef void (*predecode_cb)(and_media_private_t *codec_data,
|
|
const pjmedia_frame *rtp_frame,
|
|
pjmedia_frame *out);
|
|
|
|
#if PJMEDIA_HAS_AND_MEDIA_AMRNB || PJMEDIA_HAS_AND_MEDIA_AMRWB
|
|
/* Custom callback implementations. */
|
|
static pj_status_t parse_amr(and_media_private_t *codec_data, void *pkt,
|
|
pj_size_t pkt_size, const pj_timestamp *ts,
|
|
unsigned *frame_cnt, pjmedia_frame frames[]);
|
|
static pj_status_t pack_amr(and_media_private_t *codec_data, unsigned nframes,
|
|
void *pkt, pj_size_t *pkt_size,
|
|
pj_size_t max_pkt_size);
|
|
static void predecode_amr(and_media_private_t *codec_data,
|
|
const pjmedia_frame *input,
|
|
pjmedia_frame *out);
|
|
#endif
|
|
|
|
#if PJMEDIA_HAS_AND_MEDIA_AMRNB
|
|
|
|
static pj_str_t AMRNB_encoder[] = {{(char *)"OMX.google.amrnb.encoder\0", 24},
|
|
{(char *)"c2.android.amrnb.encoder\0", 24}};
|
|
|
|
static pj_str_t AMRNB_decoder[] = {{(char *)"OMX.google.amrnb.decoder\0", 24},
|
|
{(char *)"c2.android.amrnb.decoder\0", 24}};
|
|
#endif
|
|
|
|
#if PJMEDIA_HAS_AND_MEDIA_AMRWB
|
|
|
|
static pj_str_t AMRWB_encoder[] = {{(char *)"OMX.google.amrwb.encoder\0", 24},
|
|
{(char *)"c2.android.amrwb.encoder\0", 24}};
|
|
|
|
static pj_str_t AMRWB_decoder[] = {{(char *)"OMX.google.amrwb.decoder\0", 24},
|
|
{(char *)"c2.android.amrwb.decoder\0", 24}};
|
|
#endif
|
|
|
|
/* Android MediaCodec codec implementation descriptions. */
|
|
static struct and_media_codec {
|
|
int enabled; /* Is this codec enabled? */
|
|
const char *name; /* Codec name. */
|
|
const char *mime_type; /* Mime type. */
|
|
pj_str_t *encoder_name; /* Encoder name. */
|
|
pj_str_t *decoder_name; /* Decoder name. */
|
|
|
|
pj_uint8_t pt; /* Payload type. */
|
|
and_aud_codec_id codec_id; /* Codec id. */
|
|
unsigned clock_rate; /* Codec's clock rate. */
|
|
unsigned channel_count; /* Codec's channel count. */
|
|
unsigned samples_per_frame; /* Codec's samples count. */
|
|
unsigned def_bitrate; /* Default bitrate of this codec. */
|
|
unsigned max_bitrate; /* Maximum bitrate of this codec. */
|
|
pj_uint8_t frm_per_pkt; /* Default num of frames per packet.*/
|
|
int has_native_vad; /* Codec has internal VAD? */
|
|
int has_native_plc; /* Codec has internal PLC? */
|
|
|
|
parse_cb parse; /* Callback to parse bitstream. */
|
|
pack_cb pack; /* Callback to pack bitstream. */
|
|
predecode_cb predecode; /* Callback to prepare bitstream
|
|
before passing it to decoder. */
|
|
|
|
pjmedia_codec_fmtp dec_fmtp; /* Decoder's fmtp params. */
|
|
}
|
|
|
|
and_media_codec[] =
|
|
{
|
|
# if PJMEDIA_HAS_AND_MEDIA_AMRNB
|
|
{0, "AMR", "audio/3gpp", NULL, NULL,
|
|
PJMEDIA_RTP_PT_AMR, AND_AUD_CODEC_AMRNB, 8000, 1, 160, 7400, 12200,
|
|
2, 0, 0, &parse_amr, &pack_amr, &predecode_amr,
|
|
{1, {{{(char *)"octet-align", 11}, {(char *)"1", 1}}}}
|
|
},
|
|
# endif
|
|
|
|
# if PJMEDIA_HAS_AND_MEDIA_AMRWB
|
|
{0, "AMR-WB", "audio/amr-wb", NULL, NULL,
|
|
PJMEDIA_RTP_PT_AMRWB, AND_AUD_CODEC_AMRWB, 16000, 1, 320, 15850, 23850,
|
|
2, 0, 0, &parse_amr, &pack_amr, &predecode_amr,
|
|
{1, {{{(char *)"octet-align", 11}, {(char *)"1", 1}}}}
|
|
},
|
|
# endif
|
|
};
|
|
|
|
#if PJMEDIA_HAS_AND_MEDIA_AMRNB || PJMEDIA_HAS_AND_MEDIA_AMRWB
|
|
|
|
#include <pjmedia-codec/amr_helper.h>
|
|
|
|
typedef struct amr_settings_t {
|
|
pjmedia_codec_amr_pack_setting enc_setting;
|
|
pjmedia_codec_amr_pack_setting dec_setting;
|
|
pj_int8_t enc_mode;
|
|
} amr_settings_t;
|
|
|
|
/* Pack AMR payload */
|
|
static pj_status_t pack_amr(and_media_private_t *codec_data, unsigned nframes,
|
|
void *pkt, pj_size_t *pkt_size,
|
|
pj_size_t max_pkt_size)
|
|
{
|
|
enum {MAX_FRAMES_PER_PACKET = PJMEDIA_MAX_FRAME_DURATION_MS / 20};
|
|
|
|
pjmedia_frame frames[MAX_FRAMES_PER_PACKET];
|
|
pj_uint8_t *p; /* Read cursor */
|
|
pjmedia_codec_amr_pack_setting *setting;
|
|
unsigned i;
|
|
pj_status_t status;
|
|
|
|
setting = &((amr_settings_t*)codec_data->codec_setting)->enc_setting;
|
|
|
|
/* Align pkt buf right */
|
|
p = (pj_uint8_t*)pkt + max_pkt_size - *pkt_size;
|
|
pj_memmove(p, pkt, *pkt_size);
|
|
|
|
/* Get frames */
|
|
for (i = 0; i < nframes; ++i) {
|
|
pjmedia_codec_amr_bit_info *info = (pjmedia_codec_amr_bit_info*)
|
|
&frames[i].bit_info;
|
|
pj_bzero(info, sizeof(*info));
|
|
info->frame_type = (pj_uint8_t)((*p >> 3) & 0x0F);
|
|
info->good_quality = (pj_uint8_t)((*p >> 2) & 0x01);
|
|
info->mode = ((amr_settings_t*)codec_data->codec_setting)->enc_mode;
|
|
info->start_bit = 0;
|
|
frames[i].buf = p + 1;
|
|
if (setting->amr_nb) {
|
|
frames[i].size = (info->frame_type <= 8)?
|
|
pjmedia_codec_amrnb_framelen[info->frame_type] : 0;
|
|
} else {
|
|
frames[i].size = (info->frame_type <= 9)?
|
|
pjmedia_codec_amrwb_framelen[info->frame_type] : 0;
|
|
}
|
|
p += frames[i].size + 1;
|
|
}
|
|
/* Pack */
|
|
*pkt_size = max_pkt_size;
|
|
status = pjmedia_codec_amr_pack(frames, nframes, setting, pkt, pkt_size);
|
|
|
|
return status;
|
|
}
|
|
|
|
/* Parse AMR payload into frames. */
|
|
static pj_status_t parse_amr(and_media_private_t *codec_data, void *pkt,
|
|
pj_size_t pkt_size, const pj_timestamp *ts,
|
|
unsigned *frame_cnt, pjmedia_frame frames[])
|
|
{
|
|
amr_settings_t* s = (amr_settings_t*)codec_data->codec_setting;
|
|
pjmedia_codec_amr_pack_setting *setting;
|
|
pj_status_t status;
|
|
pj_uint8_t cmr;
|
|
|
|
setting = &s->dec_setting;
|
|
status = pjmedia_codec_amr_parse(pkt, pkt_size, ts, setting, frames,
|
|
frame_cnt, &cmr);
|
|
if (status != PJ_SUCCESS)
|
|
return status;
|
|
|
|
/* Check Change Mode Request. */
|
|
if (((setting->amr_nb && cmr <= 7) || (!setting->amr_nb && cmr <= 8)) &&
|
|
s->enc_mode != cmr)
|
|
{
|
|
s->enc_mode = cmr;
|
|
}
|
|
return PJ_SUCCESS;
|
|
}
|
|
|
|
static void predecode_amr(and_media_private_t *codec_data,
|
|
const pjmedia_frame *input,
|
|
pjmedia_frame *out)
|
|
{
|
|
pjmedia_codec_amr_bit_info *info;
|
|
pj_uint8_t *bitstream = (pj_uint8_t *)out->buf;
|
|
pjmedia_codec_amr_pack_setting *setting;
|
|
|
|
out->buf = &bitstream[1];
|
|
setting = &((amr_settings_t*)codec_data->codec_setting)->dec_setting;
|
|
pjmedia_codec_amr_predecode(input, setting, out);
|
|
info = (pjmedia_codec_amr_bit_info*)&out->bit_info;
|
|
bitstream[0] = (info->frame_type << 3) | (info->good_quality << 2);
|
|
out->buf = &bitstream[0];
|
|
++out->size;
|
|
}
|
|
|
|
#endif /* PJMEDIA_HAS_AND_MEDIA_AMRNB || PJMEDIA_HAS_AND_MEDIA_AMRWB */
|
|
|
|
static pj_status_t configure_codec(and_media_private_t *and_media_data,
|
|
pj_bool_t is_encoder)
|
|
{
|
|
media_status_t am_status;
|
|
AMediaFormat *aud_fmt;
|
|
int idx = and_media_data->codec_idx;
|
|
AMediaCodec *codec = (is_encoder?and_media_data->enc:and_media_data->dec);
|
|
|
|
aud_fmt = AMediaFormat_new();
|
|
if (!aud_fmt) {
|
|
return PJ_ENOMEM;
|
|
}
|
|
AMediaFormat_setString(aud_fmt, AND_MEDIA_KEY_MIME,
|
|
and_media_codec[idx].mime_type);
|
|
AMediaFormat_setInt32(aud_fmt, AND_MEDIA_KEY_PCM_ENCODING, 2);
|
|
AMediaFormat_setInt32(aud_fmt, AND_MEDIA_KEY_CHANNEL_COUNT,
|
|
and_media_codec[idx].channel_count);
|
|
AMediaFormat_setInt32(aud_fmt, AND_MEDIA_KEY_SAMPLE_RATE,
|
|
and_media_codec[idx].clock_rate);
|
|
AMediaFormat_setInt32(aud_fmt, AND_MEDIA_KEY_BITRATE,
|
|
and_media_codec[idx].def_bitrate);
|
|
|
|
/* Configure and start encoder. */
|
|
am_status = AMediaCodec_configure(codec, aud_fmt, NULL, NULL, is_encoder);
|
|
AMediaFormat_delete(aud_fmt);
|
|
if (am_status != AMEDIA_OK) {
|
|
PJ_LOG(4, (THIS_FILE, "%s [0x%p] configure failed, status=%d",
|
|
is_encoder?"Encoder":"Decoder", codec, am_status));
|
|
return PJMEDIA_CODEC_EFAILED;
|
|
}
|
|
am_status = AMediaCodec_start(codec);
|
|
if (am_status != AMEDIA_OK) {
|
|
PJ_LOG(4, (THIS_FILE, "%s [0x%p] start failed, status=%d",
|
|
is_encoder?"Encoder":"Decoder", codec, am_status));
|
|
return PJMEDIA_CODEC_EFAILED;
|
|
}
|
|
PJ_LOG(4, (THIS_FILE, "%s [0x%p] started", is_encoder?"Encoder":"Decoder",
|
|
codec));
|
|
return PJ_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
* Initialize and register Android MediaCodec codec factory to pjmedia endpoint.
|
|
*/
|
|
PJ_DEF(pj_status_t) pjmedia_codec_and_media_aud_init( pjmedia_endpt *endpt )
|
|
{
|
|
pjmedia_codec_mgr *codec_mgr;
|
|
pj_str_t codec_name;
|
|
pj_status_t status;
|
|
|
|
if (and_media_factory.pool != NULL) {
|
|
/* Already initialized. */
|
|
return PJ_SUCCESS;
|
|
}
|
|
|
|
PJ_LOG(4, (THIS_FILE, "Initing codec"));
|
|
|
|
/* Create Android MediaCodec codec factory. */
|
|
and_media_factory.base.op = &and_media_factory_op;
|
|
and_media_factory.base.factory_data = NULL;
|
|
and_media_factory.endpt = endpt;
|
|
|
|
and_media_factory.pool = pjmedia_endpt_create_pool(endpt,
|
|
"Android MediaCodec codecs",
|
|
4000, 4000);
|
|
if (!and_media_factory.pool)
|
|
return PJ_ENOMEM;
|
|
|
|
/* Create mutex. */
|
|
status = pj_mutex_create_simple(and_media_factory.pool,
|
|
"Android MediaCodec codecs",
|
|
&and_media_factory.mutex);
|
|
if (status != PJ_SUCCESS)
|
|
goto on_error;
|
|
|
|
/* Get the codec manager. */
|
|
codec_mgr = pjmedia_endpt_get_codec_mgr(endpt);
|
|
if (!codec_mgr) {
|
|
status = PJ_EINVALIDOP;
|
|
goto on_error;
|
|
}
|
|
|
|
#if PJMEDIA_HAS_AND_MEDIA_AMRNB
|
|
PJ_LOG(4, (THIS_FILE, "Registering AMRNB codec"));
|
|
|
|
pj_cstr(&codec_name, "AMR");
|
|
status = pjmedia_sdp_neg_register_fmt_match_cb(
|
|
&codec_name,
|
|
&pjmedia_codec_amr_match_sdp);
|
|
if (status != PJ_SUCCESS)
|
|
goto on_error;
|
|
#endif
|
|
|
|
#if PJMEDIA_HAS_AND_MEDIA_AMRWB
|
|
PJ_LOG(4, (THIS_FILE, "Registering AMRWB codec"));
|
|
|
|
pj_cstr(&codec_name, "AMR-WB");
|
|
status = pjmedia_sdp_neg_register_fmt_match_cb(
|
|
&codec_name,
|
|
&pjmedia_codec_amr_match_sdp);
|
|
if (status != PJ_SUCCESS)
|
|
goto on_error;
|
|
#endif
|
|
|
|
/* Suppress compile warning */
|
|
PJ_UNUSED_ARG(codec_name);
|
|
|
|
/* Register codec factory to endpoint. */
|
|
status = pjmedia_codec_mgr_register_factory(codec_mgr,
|
|
&and_media_factory.base);
|
|
if (status != PJ_SUCCESS)
|
|
goto on_error;
|
|
|
|
/* Done. */
|
|
return PJ_SUCCESS;
|
|
|
|
on_error:
|
|
pj_pool_release(and_media_factory.pool);
|
|
and_media_factory.pool = NULL;
|
|
return status;
|
|
}
|
|
|
|
/*
|
|
* Unregister Android MediaCodec codecs factory from pjmedia endpoint.
|
|
*/
|
|
PJ_DEF(pj_status_t) pjmedia_codec_and_media_aud_deinit(void)
|
|
{
|
|
pjmedia_codec_mgr *codec_mgr;
|
|
pj_status_t status;
|
|
|
|
if (and_media_factory.pool == NULL) {
|
|
/* Already deinitialized */
|
|
return PJ_SUCCESS;
|
|
}
|
|
|
|
pj_mutex_lock(and_media_factory.mutex);
|
|
|
|
/* Get the codec manager. */
|
|
codec_mgr = pjmedia_endpt_get_codec_mgr(and_media_factory.endpt);
|
|
if (!codec_mgr) {
|
|
pj_pool_release(and_media_factory.pool);
|
|
and_media_factory.pool = NULL;
|
|
pj_mutex_unlock(and_media_factory.mutex);
|
|
return PJ_EINVALIDOP;
|
|
}
|
|
|
|
/* Unregister Android MediaCodec codecs factory. */
|
|
status = pjmedia_codec_mgr_unregister_factory(codec_mgr,
|
|
&and_media_factory.base);
|
|
|
|
/* Destroy mutex. */
|
|
pj_mutex_unlock(and_media_factory.mutex);
|
|
pj_mutex_destroy(and_media_factory.mutex);
|
|
and_media_factory.mutex = NULL;
|
|
|
|
/* Destroy pool. */
|
|
pj_pool_release(and_media_factory.pool);
|
|
and_media_factory.pool = NULL;
|
|
|
|
return status;
|
|
}
|
|
|
|
/*
|
|
* Check if factory can allocate the specified codec.
|
|
*/
|
|
static pj_status_t and_media_test_alloc(pjmedia_codec_factory *factory,
|
|
const pjmedia_codec_info *info )
|
|
{
|
|
unsigned i;
|
|
|
|
PJ_UNUSED_ARG(factory);
|
|
|
|
/* Type MUST be audio. */
|
|
if (info->type != PJMEDIA_TYPE_AUDIO)
|
|
return PJMEDIA_CODEC_EUNSUP;
|
|
|
|
for (i = 0; i < PJ_ARRAY_SIZE(and_media_codec); ++i) {
|
|
pj_str_t name = pj_str((char*)and_media_codec[i].name);
|
|
if ((pj_stricmp(&info->encoding_name, &name) == 0) &&
|
|
(info->clock_rate == (unsigned)and_media_codec[i].clock_rate) &&
|
|
(info->channel_cnt == (unsigned)and_media_codec[i].channel_count) &&
|
|
(and_media_codec[i].enabled))
|
|
{
|
|
return PJ_SUCCESS;
|
|
}
|
|
}
|
|
|
|
/* Unsupported, or mode is disabled. */
|
|
return PJMEDIA_CODEC_EUNSUP;
|
|
}
|
|
|
|
/*
|
|
* Generate default attribute.
|
|
*/
|
|
static pj_status_t and_media_default_attr (pjmedia_codec_factory *factory,
|
|
const pjmedia_codec_info *id,
|
|
pjmedia_codec_param *attr)
|
|
{
|
|
unsigned i;
|
|
|
|
PJ_ASSERT_RETURN(factory==&and_media_factory.base, PJ_EINVAL);
|
|
|
|
pj_bzero(attr, sizeof(pjmedia_codec_param));
|
|
|
|
for (i = 0; i < PJ_ARRAY_SIZE(and_media_codec); ++i) {
|
|
pj_str_t name = pj_str((char*)and_media_codec[i].name);
|
|
if ((and_media_codec[i].enabled) &&
|
|
(pj_stricmp(&id->encoding_name, &name) == 0) &&
|
|
(id->clock_rate == (unsigned)and_media_codec[i].clock_rate) &&
|
|
(id->channel_cnt == (unsigned)and_media_codec[i].channel_count) &&
|
|
(id->pt == (unsigned)and_media_codec[i].pt))
|
|
{
|
|
attr->info.pt = (pj_uint8_t)id->pt;
|
|
attr->info.channel_cnt = and_media_codec[i].channel_count;
|
|
attr->info.clock_rate = and_media_codec[i].clock_rate;
|
|
attr->info.avg_bps = and_media_codec[i].def_bitrate;
|
|
attr->info.max_bps = and_media_codec[i].max_bitrate;
|
|
attr->info.pcm_bits_per_sample = 16;
|
|
attr->info.frm_ptime = (pj_uint16_t)
|
|
(and_media_codec[i].samples_per_frame * 1000 /
|
|
and_media_codec[i].channel_count /
|
|
and_media_codec[i].clock_rate);
|
|
attr->setting.frm_per_pkt = and_media_codec[i].frm_per_pkt;
|
|
|
|
/* Default flags. */
|
|
attr->setting.plc = 1;
|
|
attr->setting.penh= 0;
|
|
attr->setting.vad = 1;
|
|
attr->setting.cng = attr->setting.vad;
|
|
attr->setting.dec_fmtp = and_media_codec[i].dec_fmtp;
|
|
|
|
return PJ_SUCCESS;
|
|
}
|
|
}
|
|
|
|
return PJMEDIA_CODEC_EUNSUP;
|
|
}
|
|
|
|
static pj_bool_t codec_exists(const pj_str_t *codec_name)
|
|
{
|
|
AMediaCodec *codec;
|
|
char *codec_txt;
|
|
|
|
codec_txt = codec_name->ptr;
|
|
|
|
codec = AMediaCodec_createCodecByName(codec_txt);
|
|
if (!codec) {
|
|
PJ_LOG(4, (THIS_FILE, "Failed creating codec : %.*s",
|
|
(int)codec_name->slen, codec_name->ptr));
|
|
return PJ_FALSE;
|
|
}
|
|
AMediaCodec_delete(codec);
|
|
|
|
return PJ_TRUE;
|
|
}
|
|
|
|
/*
|
|
* Enum codecs supported by this factory.
|
|
*/
|
|
static pj_status_t and_media_enum_codecs(pjmedia_codec_factory *factory,
|
|
unsigned *count,
|
|
pjmedia_codec_info codecs[])
|
|
{
|
|
unsigned max;
|
|
unsigned i;
|
|
|
|
PJ_UNUSED_ARG(factory);
|
|
PJ_ASSERT_RETURN(codecs && *count > 0, PJ_EINVAL);
|
|
|
|
max = *count;
|
|
|
|
for (i = 0, *count = 0; i < PJ_ARRAY_SIZE(and_media_codec) &&
|
|
*count < max; ++i)
|
|
{
|
|
unsigned enc_idx, dec_idx;
|
|
pj_str_t *enc_name = NULL;
|
|
unsigned num_enc = 0;
|
|
pj_str_t *dec_name = NULL;
|
|
unsigned num_dec = 0;
|
|
|
|
switch (and_media_codec[i].codec_id) {
|
|
|
|
case AND_AUD_CODEC_AMRNB:
|
|
#if PJMEDIA_HAS_AND_MEDIA_AMRNB
|
|
enc_name = &AMRNB_encoder[0];
|
|
dec_name = &AMRNB_decoder[0];
|
|
num_enc = PJ_ARRAY_SIZE(AMRNB_encoder);
|
|
num_dec = PJ_ARRAY_SIZE(AMRNB_decoder);
|
|
#endif
|
|
break;
|
|
case AND_AUD_CODEC_AMRWB:
|
|
#if PJMEDIA_HAS_AND_MEDIA_AMRWB
|
|
enc_name = &AMRWB_encoder[0];
|
|
dec_name = &AMRWB_decoder[0];
|
|
num_enc = PJ_ARRAY_SIZE(AMRWB_encoder);
|
|
num_dec = PJ_ARRAY_SIZE(AMRWB_decoder);
|
|
#endif
|
|
|
|
break;
|
|
default:
|
|
continue;
|
|
};
|
|
if (!enc_name || !dec_name) {
|
|
continue;
|
|
}
|
|
|
|
for (enc_idx = 0; enc_idx < num_enc ;++enc_idx, ++enc_name) {
|
|
if (codec_exists(enc_name)) {
|
|
break;
|
|
}
|
|
}
|
|
if (enc_idx == num_enc)
|
|
continue;
|
|
|
|
for (dec_idx = 0; dec_idx < num_dec ;++dec_idx, ++dec_name) {
|
|
if (codec_exists(dec_name)) {
|
|
break;
|
|
}
|
|
}
|
|
if (dec_idx == num_dec)
|
|
continue;
|
|
|
|
and_media_codec[i].encoder_name = enc_name;
|
|
and_media_codec[i].decoder_name = dec_name;
|
|
pj_bzero(&codecs[*count], sizeof(pjmedia_codec_info));
|
|
codecs[*count].encoding_name = pj_str((char*)and_media_codec[i].name);
|
|
codecs[*count].pt = and_media_codec[i].pt;
|
|
codecs[*count].type = PJMEDIA_TYPE_AUDIO;
|
|
codecs[*count].clock_rate = and_media_codec[i].clock_rate;
|
|
codecs[*count].channel_cnt = and_media_codec[i].channel_count;
|
|
and_media_codec[i].enabled = PJ_TRUE;
|
|
PJ_LOG(4, (THIS_FILE, "Found encoder [%d]: %.*s and decoder: %.*s ",
|
|
*count, (int)enc_name->slen, enc_name->ptr,
|
|
(int)dec_name->slen, dec_name->ptr));
|
|
++*count;
|
|
}
|
|
|
|
return PJ_SUCCESS;
|
|
}
|
|
|
|
static void create_codec(and_media_private_t *and_media_data)
|
|
{
|
|
char const *enc_name =
|
|
and_media_codec[and_media_data->codec_idx].encoder_name->ptr;
|
|
char const *dec_name =
|
|
and_media_codec[and_media_data->codec_idx].decoder_name->ptr;
|
|
|
|
if (!and_media_data->enc) {
|
|
and_media_data->enc = AMediaCodec_createCodecByName(enc_name);
|
|
if (!and_media_data->enc) {
|
|
PJ_LOG(4, (THIS_FILE, "Failed creating encoder: %s", enc_name));
|
|
}
|
|
PJ_LOG(4, (THIS_FILE, "Done creating encoder: %s [0x%p]", enc_name,
|
|
and_media_data->enc));
|
|
}
|
|
|
|
if (!and_media_data->dec) {
|
|
and_media_data->dec = AMediaCodec_createCodecByName(dec_name);
|
|
if (!and_media_data->dec) {
|
|
PJ_LOG(4, (THIS_FILE, "Failed creating decoder: %s", dec_name));
|
|
}
|
|
PJ_LOG(4, (THIS_FILE, "Done creating decoder: %s [0x%p]", dec_name,
|
|
and_media_data->dec));
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Allocate a new codec instance.
|
|
*/
|
|
static pj_status_t and_media_alloc_codec(pjmedia_codec_factory *factory,
|
|
const pjmedia_codec_info *id,
|
|
pjmedia_codec **p_codec)
|
|
{
|
|
and_media_private_t *codec_data;
|
|
pjmedia_codec *codec;
|
|
int idx;
|
|
pj_pool_t *pool;
|
|
unsigned i;
|
|
|
|
PJ_ASSERT_RETURN(factory && id && p_codec, PJ_EINVAL);
|
|
PJ_ASSERT_RETURN(factory == &and_media_factory.base, PJ_EINVAL);
|
|
|
|
pj_mutex_lock(and_media_factory.mutex);
|
|
|
|
/* Find codec's index */
|
|
idx = -1;
|
|
for (i = 0; i < PJ_ARRAY_SIZE(and_media_codec); ++i) {
|
|
pj_str_t name = pj_str((char*)and_media_codec[i].name);
|
|
if ((pj_stricmp(&id->encoding_name, &name) == 0) &&
|
|
(id->clock_rate == (unsigned)and_media_codec[i].clock_rate) &&
|
|
(id->channel_cnt == (unsigned)and_media_codec[i].channel_count) &&
|
|
(and_media_codec[i].enabled))
|
|
{
|
|
idx = i;
|
|
break;
|
|
}
|
|
}
|
|
if (idx == -1) {
|
|
*p_codec = NULL;
|
|
pj_mutex_unlock(and_media_factory.mutex);
|
|
return PJMEDIA_CODEC_EFAILED;
|
|
}
|
|
|
|
/* Create pool for codec instance */
|
|
pool = pjmedia_endpt_create_pool(and_media_factory.endpt, "andmedaud%p",
|
|
512, 512);
|
|
codec = PJ_POOL_ZALLOC_T(pool, pjmedia_codec);
|
|
PJ_ASSERT_RETURN(codec != NULL, PJ_ENOMEM);
|
|
codec->op = &and_media_op;
|
|
codec->factory = factory;
|
|
codec->codec_data = PJ_POOL_ZALLOC_T(pool, and_media_private_t);
|
|
codec_data = (and_media_private_t*) codec->codec_data;
|
|
|
|
/* Create PLC if codec has no internal PLC */
|
|
if (!and_media_codec[idx].has_native_plc) {
|
|
pj_status_t status;
|
|
status = pjmedia_plc_create(pool, and_media_codec[idx].clock_rate,
|
|
and_media_codec[idx].samples_per_frame, 0,
|
|
&codec_data->plc);
|
|
if (status != PJ_SUCCESS) {
|
|
goto on_error;
|
|
}
|
|
}
|
|
|
|
/* Create silence detector if codec has no internal VAD */
|
|
if (!and_media_codec[idx].has_native_vad) {
|
|
pj_status_t status;
|
|
status = pjmedia_silence_det_create(pool,
|
|
and_media_codec[idx].clock_rate,
|
|
and_media_codec[idx].samples_per_frame,
|
|
&codec_data->vad);
|
|
if (status != PJ_SUCCESS) {
|
|
goto on_error;
|
|
}
|
|
}
|
|
|
|
codec_data->pool = pool;
|
|
codec_data->codec_idx = idx;
|
|
|
|
create_codec(codec_data);
|
|
if (!codec_data->enc || !codec_data->dec) {
|
|
goto on_error;
|
|
}
|
|
pj_mutex_unlock(and_media_factory.mutex);
|
|
|
|
*p_codec = codec;
|
|
return PJ_SUCCESS;
|
|
|
|
on_error:
|
|
pj_mutex_unlock(and_media_factory.mutex);
|
|
and_media_dealloc_codec(factory, codec);
|
|
return PJMEDIA_CODEC_EFAILED;
|
|
}
|
|
|
|
/*
|
|
* Free codec.
|
|
*/
|
|
static pj_status_t and_media_dealloc_codec(pjmedia_codec_factory *factory,
|
|
pjmedia_codec *codec )
|
|
{
|
|
and_media_private_t *codec_data;
|
|
|
|
PJ_ASSERT_RETURN(factory && codec, PJ_EINVAL);
|
|
PJ_ASSERT_RETURN(factory == &and_media_factory.base, PJ_EINVAL);
|
|
|
|
/* Close codec, if it's not closed. */
|
|
codec_data = (and_media_private_t*) codec->codec_data;
|
|
if (codec_data->enc) {
|
|
AMediaCodec_stop(codec_data->enc);
|
|
AMediaCodec_delete(codec_data->enc);
|
|
codec_data->enc = NULL;
|
|
}
|
|
|
|
if (codec_data->dec) {
|
|
AMediaCodec_stop(codec_data->dec);
|
|
AMediaCodec_delete(codec_data->dec);
|
|
codec_data->dec = NULL;
|
|
}
|
|
pj_pool_release(codec_data->pool);
|
|
|
|
return PJ_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
* Init codec.
|
|
*/
|
|
static pj_status_t and_media_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 and_media_codec_open(pjmedia_codec *codec,
|
|
pjmedia_codec_param *attr)
|
|
{
|
|
and_media_private_t *codec_data = (and_media_private_t*) codec->codec_data;
|
|
struct and_media_codec *and_media_data =
|
|
&and_media_codec[codec_data->codec_idx];
|
|
pj_status_t status;
|
|
|
|
PJ_ASSERT_RETURN(codec && attr, PJ_EINVAL);
|
|
PJ_ASSERT_RETURN(codec_data != NULL, PJ_EINVALIDOP);
|
|
|
|
PJ_LOG(5,(THIS_FILE, "Opening codec.."));
|
|
|
|
codec_data->vad_enabled = (attr->setting.vad != 0);
|
|
codec_data->plc_enabled = (attr->setting.plc != 0);
|
|
and_media_data->clock_rate = attr->info.clock_rate;
|
|
|
|
#if PJMEDIA_HAS_AND_MEDIA_AMRNB
|
|
if (and_media_data->codec_id == AND_AUD_CODEC_AMRNB ||
|
|
and_media_data->codec_id == AND_AUD_CODEC_AMRWB)
|
|
{
|
|
amr_settings_t *s;
|
|
pj_uint8_t octet_align = 0;
|
|
pj_int8_t enc_mode;
|
|
unsigned i;
|
|
|
|
enc_mode = pjmedia_codec_amr_get_mode(attr->info.avg_bps);
|
|
|
|
pj_assert(enc_mode >= 0 && enc_mode <= 8);
|
|
|
|
/* Check AMR specific attributes */
|
|
for (i = 0; i < attr->setting.dec_fmtp.cnt; ++i) {
|
|
/* octet-align, one of the parameters that must have same value
|
|
* in offer & answer (RFC 4867 Section 8.3.1). Just check fmtp
|
|
* in the decoder side, since it's value is guaranteed to fulfil
|
|
* above requirement (by SDP negotiator).
|
|
*/
|
|
const pj_str_t STR_FMTP_OCTET_ALIGN = {(char *)"octet-align", 11};
|
|
|
|
if (pj_stricmp(&attr->setting.dec_fmtp.param[i].name,
|
|
&STR_FMTP_OCTET_ALIGN) == 0)
|
|
{
|
|
octet_align=(pj_uint8_t)
|
|
pj_strtoul(&attr->setting.dec_fmtp.param[i].val);
|
|
break;
|
|
}
|
|
}
|
|
for (i = 0; i < attr->setting.enc_fmtp.cnt; ++i) {
|
|
/* mode-set, encoding mode is chosen based on local default mode
|
|
* setting:
|
|
* - if local default mode is included in the mode-set, use it
|
|
* - otherwise, find the closest mode to local default mode;
|
|
* if there are two closest modes, prefer to use the higher
|
|
* one, e.g: local default mode is 4, the mode-set param
|
|
* contains '2,3,5,6', then 5 will be chosen.
|
|
*/
|
|
const pj_str_t STR_FMTP_MODE_SET = {(char *)"mode-set", 8};
|
|
|
|
if (pj_stricmp(&attr->setting.enc_fmtp.param[i].name,
|
|
&STR_FMTP_MODE_SET) == 0)
|
|
{
|
|
const char *p;
|
|
pj_size_t l;
|
|
pj_int8_t diff = 99;
|
|
|
|
p = pj_strbuf(&attr->setting.enc_fmtp.param[i].val);
|
|
l = pj_strlen(&attr->setting.enc_fmtp.param[i].val);
|
|
|
|
while (l--) {
|
|
if ((and_media_data->codec_id == AND_AUD_CODEC_AMRNB &&
|
|
*p>='0' && *p<='7') ||
|
|
(and_media_data->codec_id == AND_AUD_CODEC_AMRWB &&
|
|
*p>='0' && *p<='8'))
|
|
{
|
|
pj_int8_t tmp = (pj_int8_t)(*p - '0' - enc_mode);
|
|
|
|
if (PJ_ABS(diff) > PJ_ABS(tmp) ||
|
|
(PJ_ABS(diff) == PJ_ABS(tmp) && tmp > diff))
|
|
{
|
|
diff = tmp;
|
|
if (diff == 0) break;
|
|
}
|
|
}
|
|
++p;
|
|
}
|
|
if (diff == 99)
|
|
goto on_error;
|
|
|
|
enc_mode = (pj_int8_t)(enc_mode + diff);
|
|
|
|
break;
|
|
}
|
|
}
|
|
/* Initialize AMR specific settings */
|
|
s = PJ_POOL_ZALLOC_T(codec_data->pool, amr_settings_t);
|
|
codec_data->codec_setting = s;
|
|
|
|
s->enc_setting.amr_nb = (pj_uint8_t)
|
|
(and_media_data->codec_id == AND_AUD_CODEC_AMRNB);
|
|
s->enc_setting.octet_aligned = octet_align;
|
|
s->enc_setting.reorder = 0;
|
|
s->enc_setting.cmr = 15;
|
|
s->dec_setting.amr_nb = (pj_uint8_t)
|
|
(and_media_data->codec_id == AND_AUD_CODEC_AMRNB);
|
|
s->dec_setting.octet_aligned = octet_align;
|
|
s->dec_setting.reorder = 0;
|
|
/* Apply encoder mode/bitrate */
|
|
s->enc_mode = enc_mode;
|
|
|
|
PJ_LOG(4, (THIS_FILE, "Encoder setting octet_aligned=%d reorder=%d"
|
|
" cmr=%d enc_mode=%d",
|
|
s->enc_setting.octet_aligned, s->enc_setting.reorder,
|
|
s->enc_setting.cmr, enc_mode));
|
|
PJ_LOG(4, (THIS_FILE, "Decoder setting octet_aligned=%d reorder=%d",
|
|
s->dec_setting.octet_aligned, s->dec_setting.reorder));
|
|
}
|
|
#endif
|
|
status = configure_codec(codec_data, PJ_TRUE);
|
|
if (status != PJ_SUCCESS) {
|
|
goto on_error;
|
|
}
|
|
status = configure_codec(codec_data, PJ_FALSE);
|
|
if (status != PJ_SUCCESS) {
|
|
goto on_error;
|
|
}
|
|
|
|
return PJ_SUCCESS;
|
|
|
|
on_error:
|
|
return PJMEDIA_CODEC_EFAILED;
|
|
}
|
|
|
|
/*
|
|
* Close codec.
|
|
*/
|
|
static pj_status_t and_media_codec_close(pjmedia_codec *codec)
|
|
{
|
|
PJ_UNUSED_ARG(codec);
|
|
|
|
return PJ_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
* Modify codec settings.
|
|
*/
|
|
static pj_status_t and_media_codec_modify(pjmedia_codec *codec,
|
|
const pjmedia_codec_param *attr)
|
|
{
|
|
and_media_private_t *codec_data = (and_media_private_t*) codec->codec_data;
|
|
|
|
codec_data->vad_enabled = (attr->setting.vad != 0);
|
|
codec_data->plc_enabled = (attr->setting.plc != 0);
|
|
|
|
return PJ_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
* Get frames in the packet.
|
|
*/
|
|
static pj_status_t and_media_codec_parse(pjmedia_codec *codec,
|
|
void *pkt,
|
|
pj_size_t pkt_size,
|
|
const pj_timestamp *ts,
|
|
unsigned *frame_cnt,
|
|
pjmedia_frame frames[])
|
|
{
|
|
and_media_private_t *codec_data = (and_media_private_t*) codec->codec_data;
|
|
struct and_media_codec *and_media_data =
|
|
&and_media_codec[codec_data->codec_idx];
|
|
unsigned count = 0;
|
|
|
|
PJ_ASSERT_RETURN(frame_cnt, PJ_EINVAL);
|
|
|
|
if (and_media_data->parse != NULL) {
|
|
return and_media_data->parse(codec_data, pkt, pkt_size, ts, frame_cnt,
|
|
frames);
|
|
}
|
|
|
|
while (pkt_size >= codec_data->frame_size && count < *frame_cnt) {
|
|
frames[count].type = PJMEDIA_FRAME_TYPE_AUDIO;
|
|
frames[count].buf = pkt;
|
|
frames[count].size = codec_data->frame_size;
|
|
frames[count].timestamp.u64 = ts->u64 +
|
|
count*and_media_data->samples_per_frame;
|
|
pkt = ((char*)pkt) + codec_data->frame_size;
|
|
pkt_size -= codec_data->frame_size;
|
|
++count;
|
|
}
|
|
|
|
if (pkt_size && count < *frame_cnt) {
|
|
frames[count].type = PJMEDIA_FRAME_TYPE_AUDIO;
|
|
frames[count].buf = pkt;
|
|
frames[count].size = pkt_size;
|
|
frames[count].timestamp.u64 = ts->u64 +
|
|
count*and_media_data->samples_per_frame;
|
|
++count;
|
|
}
|
|
|
|
*frame_cnt = count;
|
|
return PJ_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
* Encode frames.
|
|
*/
|
|
static pj_status_t and_media_codec_encode(pjmedia_codec *codec,
|
|
const struct pjmedia_frame *input,
|
|
unsigned output_buf_len,
|
|
struct pjmedia_frame *output)
|
|
{
|
|
and_media_private_t *codec_data = (and_media_private_t*) codec->codec_data;
|
|
struct and_media_codec *and_media_data =
|
|
&and_media_codec[codec_data->codec_idx];
|
|
unsigned samples_per_frame;
|
|
unsigned nsamples;
|
|
unsigned nframes;
|
|
pj_size_t tx = 0;
|
|
pj_int16_t *pcm_in = (pj_int16_t*)input->buf;
|
|
pj_uint8_t *bits_out = (pj_uint8_t*) output->buf;
|
|
|
|
/* Invoke external VAD if codec has no internal VAD */
|
|
if (codec_data->vad && codec_data->vad_enabled) {
|
|
pj_bool_t is_silence;
|
|
pj_int32_t silence_duration;
|
|
|
|
silence_duration = pj_timestamp_diff32(&codec_data->last_tx,
|
|
&input->timestamp);
|
|
|
|
is_silence = pjmedia_silence_det_detect(codec_data->vad,
|
|
(const pj_int16_t*) input->buf,
|
|
(input->size >> 1),
|
|
NULL);
|
|
if (is_silence &&
|
|
(PJMEDIA_CODEC_MAX_SILENCE_PERIOD == -1 ||
|
|
silence_duration < (PJMEDIA_CODEC_MAX_SILENCE_PERIOD *
|
|
(int)and_media_data->clock_rate / 1000)))
|
|
{
|
|
output->type = PJMEDIA_FRAME_TYPE_NONE;
|
|
output->buf = NULL;
|
|
output->size = 0;
|
|
output->timestamp = input->timestamp;
|
|
return PJ_SUCCESS;
|
|
} else {
|
|
codec_data->last_tx = input->timestamp;
|
|
}
|
|
}
|
|
nsamples = input->size >> 1;
|
|
samples_per_frame = and_media_data->samples_per_frame;
|
|
nframes = nsamples / samples_per_frame;
|
|
|
|
PJ_ASSERT_RETURN(nsamples % samples_per_frame == 0,
|
|
PJMEDIA_CODEC_EPCMFRMINLEN);
|
|
|
|
/* Encode the frames */
|
|
while (nsamples >= samples_per_frame) {
|
|
pj_ssize_t buf_idx;
|
|
unsigned i;
|
|
pj_size_t output_size;
|
|
pj_uint8_t *output_buf;
|
|
AMediaCodecBufferInfo buf_info;
|
|
|
|
buf_idx = AMediaCodec_dequeueInputBuffer(codec_data->enc,
|
|
CODEC_DEQUEUE_TIMEOUT);
|
|
|
|
if (buf_idx >= 0) {
|
|
media_status_t am_status;
|
|
pj_size_t output_size;
|
|
unsigned input_size = samples_per_frame << 1;
|
|
|
|
pj_uint8_t *input_buf = AMediaCodec_getInputBuffer(codec_data->enc,
|
|
buf_idx, &output_size);
|
|
|
|
if (input_buf && output_size >= input_size) {
|
|
pj_memcpy(input_buf, pcm_in, input_size);
|
|
|
|
am_status = AMediaCodec_queueInputBuffer(codec_data->enc,
|
|
buf_idx, 0, input_size, 0, 0);
|
|
if (am_status != AMEDIA_OK) {
|
|
PJ_LOG(4, (THIS_FILE, "Encoder queueInputBuffer return %d",
|
|
am_status));
|
|
goto on_return;
|
|
}
|
|
} else {
|
|
if (!input_buf) {
|
|
PJ_LOG(4,(THIS_FILE, "Encoder getInputBuffer "
|
|
"returns no input buff"));
|
|
} else {
|
|
PJ_LOG(4,(THIS_FILE, "Encoder getInputBuffer "
|
|
"size: %lu, expecting %d.",
|
|
(unsigned long)output_size,
|
|
input_size));
|
|
}
|
|
goto on_return;
|
|
}
|
|
} else {
|
|
PJ_LOG(4,(THIS_FILE, "Encoder dequeueInputBuffer failed[%ld]",
|
|
buf_idx));
|
|
goto on_return;
|
|
}
|
|
|
|
for (i = 0; i < CODEC_WAIT_RETRY; ++i) {
|
|
buf_idx = AMediaCodec_dequeueOutputBuffer(codec_data->enc,
|
|
&buf_info,
|
|
CODEC_DEQUEUE_TIMEOUT);
|
|
if (buf_idx == -1) {
|
|
/* Timeout, wait until output buffer is availble. */
|
|
pj_thread_sleep(CODEC_THREAD_WAIT);
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (buf_idx < 0) {
|
|
PJ_LOG(4, (THIS_FILE, "Encoder dequeueOutputBuffer failed %ld",
|
|
buf_idx));
|
|
goto on_return;
|
|
}
|
|
|
|
output_buf = AMediaCodec_getOutputBuffer(codec_data->enc,
|
|
buf_idx,
|
|
&output_size);
|
|
if (!output_buf) {
|
|
PJ_LOG(4, (THIS_FILE, "Encoder failed getting output buffer, "
|
|
"buffer size=%d, flags %d",
|
|
buf_info.size, buf_info.flags));
|
|
goto on_return;
|
|
}
|
|
|
|
pj_memcpy(bits_out, output_buf, buf_info.size);
|
|
AMediaCodec_releaseOutputBuffer(codec_data->enc,
|
|
buf_idx,
|
|
0);
|
|
bits_out += buf_info.size;
|
|
tx += buf_info.size;
|
|
pcm_in += samples_per_frame;
|
|
nsamples -= samples_per_frame;
|
|
}
|
|
if (and_media_data->pack != NULL) {
|
|
and_media_data->pack(codec_data, nframes, output->buf, &tx,
|
|
output_buf_len);
|
|
}
|
|
/* Check if we don't need 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;
|
|
}
|
|
output->size = tx;
|
|
output->type = PJMEDIA_FRAME_TYPE_AUDIO;
|
|
output->timestamp = input->timestamp;
|
|
|
|
return PJ_SUCCESS;
|
|
|
|
on_return:
|
|
output->size = 0;
|
|
output->buf = NULL;
|
|
output->type = PJMEDIA_FRAME_TYPE_NONE;
|
|
output->timestamp.u64 = input->timestamp.u64;
|
|
return PJ_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
* Decode frame.
|
|
*/
|
|
static pj_status_t and_media_codec_decode(pjmedia_codec *codec,
|
|
const struct pjmedia_frame *input,
|
|
unsigned output_buf_len,
|
|
struct pjmedia_frame *output)
|
|
{
|
|
and_media_private_t *codec_data = (and_media_private_t*) codec->codec_data;
|
|
struct and_media_codec *and_media_data =
|
|
&and_media_codec[codec_data->codec_idx];
|
|
unsigned samples_per_frame;
|
|
unsigned i;
|
|
|
|
pj_ssize_t buf_idx = -1;
|
|
pj_uint8_t *input_buf;
|
|
pj_size_t input_size;
|
|
pj_size_t output_size;
|
|
media_status_t am_status;
|
|
AMediaCodecBufferInfo buf_info;
|
|
pj_uint8_t *output_buf;
|
|
pjmedia_frame input_;
|
|
|
|
pj_bzero(&input_, sizeof(pjmedia_frame));
|
|
samples_per_frame = and_media_data->samples_per_frame;
|
|
|
|
PJ_ASSERT_RETURN(output_buf_len >= samples_per_frame << 1,
|
|
PJMEDIA_CODEC_EPCMTOOSHORT);
|
|
|
|
if (input->type != PJMEDIA_FRAME_TYPE_AUDIO)
|
|
{
|
|
goto on_return;
|
|
}
|
|
|
|
buf_idx = AMediaCodec_dequeueInputBuffer(codec_data->dec,
|
|
CODEC_DEQUEUE_TIMEOUT);
|
|
|
|
if (buf_idx < 0) {
|
|
PJ_LOG(4,(THIS_FILE, "Decoder dequeueInputBuffer failed return %ld",
|
|
buf_idx));
|
|
goto on_return;
|
|
}
|
|
|
|
input_buf = AMediaCodec_getInputBuffer(codec_data->dec,
|
|
buf_idx,
|
|
&input_size);
|
|
if (input_buf == 0) {
|
|
PJ_LOG(4,(THIS_FILE, "Decoder getInputBuffer failed "
|
|
"return input_buf=%d, size=%lu", *input_buf,
|
|
(unsigned long)input_size));
|
|
goto on_return;
|
|
}
|
|
|
|
if (and_media_data->predecode) {
|
|
input_.buf = input_buf;
|
|
and_media_data->predecode(codec_data, input, &input_);
|
|
} else {
|
|
input_.size = input->size;
|
|
pj_memcpy(input_buf, input->buf, input->size);
|
|
}
|
|
|
|
am_status = AMediaCodec_queueInputBuffer(codec_data->dec,
|
|
buf_idx,
|
|
0,
|
|
input_.size,
|
|
input->timestamp.u32.lo,
|
|
0);
|
|
if (am_status != AMEDIA_OK) {
|
|
PJ_LOG(4,(THIS_FILE, "Decoder queueInputBuffer failed return %d",
|
|
am_status));
|
|
goto on_return;
|
|
}
|
|
|
|
for (i = 0; i < CODEC_WAIT_RETRY; ++i) {
|
|
buf_idx = AMediaCodec_dequeueOutputBuffer(codec_data->dec,
|
|
&buf_info,
|
|
CODEC_DEQUEUE_TIMEOUT);
|
|
if (buf_idx == -1) {
|
|
/* Timeout, wait until output buffer is availble. */
|
|
PJ_LOG(5, (THIS_FILE, "Decoder dequeueOutputBuffer timeout[%d]",
|
|
i+1));
|
|
pj_thread_sleep(CODEC_THREAD_WAIT);
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
if (buf_idx < 0) {
|
|
PJ_LOG(5, (THIS_FILE, "Decoder dequeueOutputBuffer failed [%ld]",
|
|
buf_idx));
|
|
goto on_return;
|
|
}
|
|
|
|
output_buf = AMediaCodec_getOutputBuffer(codec_data->dec,
|
|
buf_idx,
|
|
&output_size);
|
|
if (output_buf == NULL) {
|
|
am_status = AMediaCodec_releaseOutputBuffer(codec_data->dec,
|
|
buf_idx,
|
|
0);
|
|
if (am_status != AMEDIA_OK) {
|
|
PJ_LOG(4,(THIS_FILE, "Decoder releaseOutputBuffer failed %d",
|
|
am_status));
|
|
}
|
|
PJ_LOG(4,(THIS_FILE, "Decoder getOutputBuffer failed"));
|
|
goto on_return;
|
|
}
|
|
pj_memcpy(output->buf, output_buf, buf_info.size);
|
|
output->type = PJMEDIA_FRAME_TYPE_AUDIO;
|
|
output->size = buf_info.size;
|
|
output->timestamp.u64 = input->timestamp.u64;
|
|
am_status = AMediaCodec_releaseOutputBuffer(codec_data->dec,
|
|
buf_idx,
|
|
0);
|
|
|
|
/* Invoke external PLC if codec has no internal PLC */
|
|
if (codec_data->plc && codec_data->plc_enabled)
|
|
pjmedia_plc_save(codec_data->plc, (pj_int16_t*)output->buf);
|
|
|
|
return PJ_SUCCESS;
|
|
|
|
on_return:
|
|
pjmedia_zero_samples((pj_int16_t*)output->buf, samples_per_frame);
|
|
output->size = samples_per_frame << 1;
|
|
output->timestamp.u64 = input->timestamp.u64;
|
|
output->type = PJMEDIA_FRAME_TYPE_AUDIO;
|
|
return PJ_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
* Recover lost frame.
|
|
*/
|
|
static pj_status_t and_media_codec_recover(pjmedia_codec *codec,
|
|
unsigned output_buf_len,
|
|
struct pjmedia_frame *output)
|
|
{
|
|
and_media_private_t *codec_data = (and_media_private_t*) codec->codec_data;
|
|
struct and_media_codec *and_media_data =
|
|
&and_media_codec[codec_data->codec_idx];
|
|
unsigned samples_per_frame;
|
|
pj_bool_t generate_plc = (codec_data->plc_enabled && codec_data->plc);
|
|
|
|
PJ_UNUSED_ARG(output_buf_len);
|
|
|
|
samples_per_frame = and_media_data->samples_per_frame;
|
|
output->type = PJMEDIA_FRAME_TYPE_AUDIO;
|
|
output->size = samples_per_frame << 1;
|
|
|
|
if (generate_plc)
|
|
pjmedia_plc_generate(codec_data->plc, (pj_int16_t*)output->buf);
|
|
else
|
|
pjmedia_zero_samples((pj_int16_t*)output->buf, samples_per_frame);
|
|
|
|
return PJ_SUCCESS;
|
|
}
|
|
|
|
|
|
#endif /* PJMEDIA_HAS_ANDROID_MEDIACODEC */
|
|
|