//***************************************************************************** // // voles_main.c //! @file //! //! @brief This file provides the main application for the VOLE service. //! // //***************************************************************************** //***************************************************************************** // // Copyright (c) 2020, Ambiq Micro // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // 1. Redistributions of source code must retain the above copyright notice, // this list of conditions and the following disclaimer. // // 2. Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // 3. Neither the name of the copyright holder nor the names of its // contributors may be used to endorse or promote products derived from this // software without specific prior written permission. // // Third party software included in this distribution is subject to the // additional license terms as defined in the /docs/licenses directory. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. // // This is part of revision 2.4.2 of the AmbiqSuite Development Package. // //***************************************************************************** #include #include #include #include #include "wsf_types.h" #include "wsf_assert.h" #include "wsf_trace.h" #include "wsf_buf.h" //for WsfBufAlloc and WsfBufFree #include "bstream.h" #include "att_api.h" #include "svc_ch.h" #include "svc_amvole.h" #include "app_api.h" #include "app_hw.h" #include "voles_api.h" #include "am_util_debug.h" #include "crc32.h" #include "am_mcu_apollo.h" #include "am_bsp.h" #include "am_util.h" #include "sbc.h" #include "vole_common.h" #include "att_defs.h" #include "vole_board_config.h" #include "voice_data.h" #include "ae_api.h" //***************************************************************************** // // Global variables // //***************************************************************************** eVoleCodecType g_vole_codec_type; uint8_t g_ble_data_buffer[ATT_MAX_MTU]; extern bool_t g_start_voice_send; //***************************************************************************** // // Macro definitions // //***************************************************************************** static sbc_t g_SBCInstance; int g_opusErr; uint32_t g_audioSampleRate = 16000; int32_t g_audioChannel = 1; /* Control block */ static struct { volesConn_t conn[DM_CONN_MAX]; // connection control block bool_t txReady; // TRUE if ready to send notifications wsfHandlerId_t appHandlerId; VolesCfg_t cfg; // configurable parameters voleCb_t core; } volesCb; extern bool am_app_KWD_AMA_tx_ver_exchange_send(void); extern uint16_t am_app_KWD_AMA_stream_send(uint8_t* buf, uint32_t len); extern void am_app_led_off(void); static void voles_conn_parameter_req(void) { hciConnSpec_t connSpec; connSpec.connIntervalMin = 6; //(15/1.25);//(30/1.25); connSpec.connIntervalMax = 12; //(15/1.25);//(30/1.25); connSpec.connLatency = 0; //0; connSpec.supTimeout = 400; connSpec.minCeLen = 0; connSpec.maxCeLen = 0xffff; //fixme DmConnUpdate(1, &connSpec); } //***************************************************************************** // // Connection Open event // //***************************************************************************** static void voles_conn_open(dmEvt_t *pMsg) { hciLeConnCmplEvt_t *evt = (hciLeConnCmplEvt_t*) pMsg; APP_TRACE_INFO0("connection opened\n"); APP_TRACE_INFO1("handle = 0x%x\n", evt->handle); APP_TRACE_INFO1("role = 0x%x\n", evt->role); APP_TRACE_INFO3("addrMSB = %02x%02x%02x%02x%02x%02x\n", evt->peerAddr[0], evt->peerAddr[1], evt->peerAddr[2]); APP_TRACE_INFO3("addrLSB = %02x%02x%02x%02x%02x%02x\n", evt->peerAddr[3], evt->peerAddr[4], evt->peerAddr[5]); APP_TRACE_INFO1("connInterval = %d x 1.25 ms\n", evt->connInterval); APP_TRACE_INFO1("connLatency = %d\n", evt->connLatency); APP_TRACE_INFO1("supTimeout = %d ms\n", evt->supTimeout * 10); if ( evt->connInterval > 12 ) { voles_conn_parameter_req(); } } //***************************************************************************** // // Connection Update event // //***************************************************************************** static void voles_conn_update(dmEvt_t *pMsg) { hciLeConnUpdateCmplEvt_t *evt = (hciLeConnUpdateCmplEvt_t*) pMsg; APP_TRACE_INFO1("connection update status = 0x%x", evt->status); APP_TRACE_INFO1("handle = 0x%x", evt->handle); APP_TRACE_INFO1("connInterval = 0x%x", evt->connInterval); APP_TRACE_INFO1("connLatency = 0x%x", evt->connLatency); APP_TRACE_INFO1("supTimeout = 0x%x", evt->supTimeout); if ( evt->connInterval > (15 / 1.25) ) { // retry voles_conn_parameter_req(); } } int32_t voles_msbc_encode_voice_data(uint8_t *input, uint8_t *output, uint16_t len) { int32_t i32CompressedLen = 0; if ((input == NULL) || (output == NULL)) { return 0; } sbc_encoder_encode(&g_SBCInstance, input, len, output, CODEC_MSBC_OUTPUT_SIZE, (int *)&i32CompressedLen); APP_TRACE_INFO2("voles encode, input len:%d, compressedLen:%d", len, i32CompressedLen); return i32CompressedLen; } int voles_set_codec_type(eVoleCodecType codec_type) { APP_TRACE_INFO1("set codec type:%s", ((codec_type == 0) ? "MSBC code" : "OPUS codec")); if (codec_type != VOLE_CODEC_TYPE_INVALID) { g_vole_codec_type = codec_type; return codec_type; } else { return VOLE_CODEC_TYPE_INVALID; } } eVoleCodecType voles_get_codec_type(void) { return g_vole_codec_type; } // get the size for each sending packet int voles_ble_buffer_size(void) { eVoleCodecType codec_type = voles_get_codec_type(); int buffer_size = 0; switch(codec_type) { case MSBC_CODEC_IN_USE: buffer_size = BLE_MSBC_DATA_BUFFER_SIZE; break; case OPUS_CODEC_IN_USE: buffer_size = BLE_OPUS_DATA_BUFFER_SIZE; break; default: break; } return buffer_size; } void voles_transmit_voice_data(void) { volePacket_t *txPkt = &volesCb.core.txPkt; uint32_t offset = txPkt->offset; int remain_data_len = 0; int enc_data_len = 0; uint8_t output_data[100] = {0}; int output_len = 0; static int index = 0; eVoleCodecType codec_type = voles_get_codec_type(); txPkt->len = sizeof(voice_data); remain_data_len = txPkt->len - offset; if (codec_type == MSBC_CODEC_IN_USE) { enc_data_len = (remain_data_len>CODEC_MSBC_INPUT_SIZE)?CODEC_MSBC_INPUT_SIZE:remain_data_len; } else if (codec_type == OPUS_CODEC_IN_USE) { enc_data_len = (remain_data_len>CODEC_OPUS_INPUT_SIZE)?CODEC_OPUS_INPUT_SIZE:remain_data_len; } APP_TRACE_INFO3("send %s encode data, total len:%d, offset:%d", (codec_type == MSBC_CODEC_IN_USE) ? "mSBC" : "Opus", txPkt->len, offset); if (offset >= txPkt->len) { am_app_led_off(); g_start_voice_send = FALSE; memset(output_data, 0x0, sizeof(output_data)); txPkt->offset = 0; } else { if (codec_type == MSBC_CODEC_IN_USE) { output_len = voles_msbc_encode_voice_data(&voice_data[offset + AUD_HEADER_LEN], output_data, enc_data_len); txPkt->offset += enc_data_len; } else if (codec_type == OPUS_CODEC_IN_USE) { output_len = audio_enc_encode_frame((short *)&voice_data[offset + AUD_HEADER_LEN], enc_data_len, output_data); APP_TRACE_INFO1("opus encode, output_len:%d", output_len); if (output_len == (CODEC_OPUS_OUTPUT_SIZE)) { txPkt->offset += (2*CODEC_OPUS_INPUT_SIZE); //APP_TRACE_INFO1("opus_encode: ret:%d", output_len); } else { txPkt->offset += enc_data_len; } } } if ( output_len > 0 ) { memcpy(&g_ble_data_buffer[index], output_data, output_len); index += output_len; } if ( index >= voles_ble_buffer_size() || output_len <= 0 ) { if (index >0) { am_app_KWD_AMA_stream_send(g_ble_data_buffer, index); index = 0; memset(g_ble_data_buffer, 0x0, sizeof(g_ble_data_buffer)); } } else { uint8_t output_size = ((voles_get_codec_type() == MSBC_CODEC_IN_USE) ? CODEC_MSBC_OUTPUT_SIZE : (CODEC_OPUS_OUTPUT_SIZE)); if (output_len == output_size) { voles_transmit_voice_data(); } else if (output_len>0 && output_lenhdr.status == ATT_SUCCESS) { if (g_start_voice_send) { voles_transmit_voice_data(); } } else { APP_TRACE_WARN2("cnf status = %d, hdl = 0x%x\n", pMsg->hdr.status, pMsg->handle); } } void voles_opus_encoder_init(void) { // // Opus codec init // audio_enc_init(0); APP_TRACE_INFO0("Opus encoder initialization is finished!\r\n\n"); } //***************************************************************************** //6 //! @brief initialize vole service //! //! @param handlerId - connection handle //! @param pCfg - configuration parameters //! //! @return None // //***************************************************************************** void voles_init(wsfHandlerId_t handlerId, eVoleCodecType codec_type) { memset(&volesCb, 0, sizeof(volesCb)); volesCb.appHandlerId = handlerId; volesCb.core.txPkt.data = voice_data; if (codec_type == MSBC_CODEC_IN_USE) { sbc_encode_init(&g_SBCInstance, 1); //0: SBC } else if (codec_type == OPUS_CODEC_IN_USE) { voles_opus_encoder_init(); } } static void voles_conn_close(dmEvt_t *pMsg) { hciDisconnectCmplEvt_t *evt = (hciDisconnectCmplEvt_t*) pMsg; dmConnId_t connId = evt->hdr.param; /* clear connection */ volesCb.conn[connId - 1].connId = DM_CONN_ID_NONE; volesCb.conn[connId - 1].voleToSend = FALSE; WsfTimerStop(&volesCb.core.timeoutTimer); volesCb.core.txState = VOLE_STATE_INIT; volesCb.core.rxState = VOLE_STATE_RX_IDLE; volesCb.core.lastRxPktSn = 0; volesCb.core.txPktSn = 0; VoleResetPkt(&volesCb.core.rxPkt); VoleResetPkt(&volesCb.core.txPkt); VoleResetPkt(&volesCb.core.ackPkt); } uint8_t voles_write_cback(dmConnId_t connId, uint16_t handle, uint8_t operation, uint16_t offset, uint16_t len, uint8_t *pValue, attsAttr_t *pAttr) { APP_TRACE_INFO3("write cb, len:%d, value %x %x", len, pValue[0], pValue[1]); return ATT_SUCCESS; } void voles_start(dmConnId_t connId, uint8_t timerEvt, uint8_t voleCccIdx) { // // set conn id // volesCb.conn[connId - 1].connId = connId; volesCb.conn[connId - 1].voleToSend = TRUE; volesCb.core.timeoutTimer.msg.event = timerEvt; volesCb.core.txState = VOLE_STATE_TX_IDLE; volesCb.txReady = true; volesCb.core.attMtuSize = AttGetMtu(connId); APP_TRACE_INFO1("MTU size = %d bytes", volesCb.core.attMtuSize); } void voles_stop(dmConnId_t connId) { // // clear connection // volesCb.conn[connId - 1].connId = DM_CONN_ID_NONE; volesCb.conn[connId - 1].voleToSend = FALSE; volesCb.core.txState = VOLE_STATE_INIT; volesCb.txReady = false; } void voles_proc_msg(wsfMsgHdr_t *pMsg) { if (pMsg->event == DM_CONN_OPEN_IND) { voles_conn_open((dmEvt_t *) pMsg); } else if (pMsg->event == DM_CONN_CLOSE_IND) { voles_conn_close((dmEvt_t *) pMsg); } else if (pMsg->event == DM_CONN_UPDATE_IND) { voles_conn_update((dmEvt_t *) pMsg); } else if (pMsg->event == ATTS_HANDLE_VALUE_CNF) { volesHandleValueCnf((attEvt_t *) pMsg); } }