blob: 3fd9b792de1937edc269b6592313519b82ccdfc1 [file] [log] [blame]
/*
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#define ADK_INTERNAL
#include "fwk.h"
#include "btRFCOMM.h"
#include "btA2DP.h"
#include "btSDP.h"
#include "btL2CAP.h"
#include "dbg.h"
#include "Audio.h"
#include <string.h>
#include "coop.h"
#define UGLY_SCARY_DEBUGGING_CODE 0
//endpoint media types
#define AVDTP_MEDIA_TYP_AUDIO 0
#define AVDTP_MEDIA_TYP_VIDEO 1
#define AVDTP_MEDIA_TYP_MULTIMEDIA 2
//andpoint directions
#define AVDTP_DIR_SOURCE 0
#define AVDTP_DIR_SINK 1
//A2DP packet header (2bytes)
//byte 0
#define AVDTP_HDR_MASK_TRANS 0xF0
#define AVDTP_HDR_SHIFT_TRANS 4
#define AVDTP_HDR_MASK_PKT_TYP 0x0C
#define AVDTP_HDR_SHIFT_PKT_TYP 2
#define AVDTP_HDR_MASK_MSG_TYP 0x03
#define AVDTP_HDR_SHIFT_MSG_TYP 0
//byte 1
#define AVDTP_HDR_MASK_SIG_ID 0x3F
#define AVDTP_HDR_SHIFT_SIG_ID 0
//sigId
#define AVDTP_SIG_DISCOVER 0x01
#define AVDTP_SIG_GET_CAPABILITIES 0x02
#define AVDTP_SIG_SET_CONFIGURATION 0x03
#define AVDTP_SIG_GET_CONFIGURATION 0x04
#define AVDTP_SIG_RECONFIGURE 0x05
#define AVDTP_SIG_OPEN 0x06
#define AVDTP_SIG_START 0x07
#define AVDTP_SIG_CLOSE 0x08
#define AVDTP_SIG_SUSPEND 0x09
#define AVDTP_SIG_ABORT 0x0A
#define AVDTP_SIG_SECURITY_CONTROL 0x0B
//pktTyp
#define AVDTP_PKT_TYP_SINGLE 0x00
#define AVDTP_PKT_TYP_START 0x01
#define AVDTP_PKT_TYP_CONTINUE 0x02
#define AVDTP_PKT_TYP_END 0x03
//msgTyp
#define AVDTP_MSG_TYP_CMD 0x00
#define AVDTP_MSG_TYP_ACCEPT 0x02
#define AVDTP_MSG_TYP_REJ 0x03
//seid info (2 bytes)
//byte0
#define AVDTP_SEID_NFO_MASK_SEID 0xFC
#define AVDTP_SEID_NFO_SHIFT_SEID 2
#define AVDTP_SEID_NFO_MASK_INUSE 0x02
#define AVDTP_SEID_NFO_SHIFT_INUSE 1
//byte1
#define AVDTP_SEID_NFO_MASK_MEDIA_TYP 0xF0
#define AVDTP_SEID_NFO_SHIFT_MEDIA_TYP 4
#define AVDTP_SEID_NFO_MASK_TYP 0x08
#define AVDTP_SEID_NFO_SHIFT_TYP 3
//service capability categoris
#define AVDTP_SVC_CAT_MEDIA_TRANSPORT 1
#define AVDTP_SVC_CAT_REPORTING 2
#define AVDTP_SVC_CAT_RECOVERY 3
#define AVDTP_SVC_CAT_CONTENT_PROTECTION 4
#define AVDTP_SVC_CAT_HEADER_COMPRESSION 5
#define AVDTP_SVC_CAT_MULTIPLEXING 6
#define AVDTP_SVC_CAT_MEDIA_CODEC 7
//codec types
#define A2DP_CODEC_TYP_SBC 0
//others exist...done don't implement them
#define MY_ENDPT_ID 1 //we support just one, and this is it's ID
#define A2DP_CHAN_MODE_MONO 0
#define A2DP_CHAN_MODE_DUAL_CHANNEL 1
#define A2DP_CHAN_MODE_STEREO 2
#define A2DP_CHAN_MODE_JOINT_STEREO 3
#define DATA_STATE_ENCODE(state) ((void*)(((uint32_t)(state)) ^ 0x80000000))
#define IS_DATA_STATE(state) (!!(((uint32_t)(state)) & 0x80000000))
typedef struct A2DPstate{
//L2CAP data
uint16_t aclConn;
uint16_t remChan; //remote channel num for control channel
uint16_t datChan; //remote channel num for data channel
//codec data
uint16_t samplingRate;
//our state
char needAudioConn;
//link
struct A2DPstate* next;
}A2DPstate;
static A2DPstate* conns = NULL;
//copyright notice for SBC decoder
/*
Copyright (c) 2011, Dmitry Grinberg (as published on http://dmitrygr.com)
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following condition is met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
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 OWNER 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.
*/
#define QUALITY_LOWEST 1 //you may notice the quality reduction
#define QUALITY_MEDIUM 2 //pretty good
#define QUALITY_GREAT 3 //as good as it will get without an FPU
///config options begin
/*
This is a rather clever little SBC decoder that I've put together
*/
#define QUALITY QUALITY_MEDIUM
#define SPEED_OVER_ACCURACY //set to cheat a bit with shifts (saves a divide per sample)
#define ITER uint32_t //iterator up to 180 use fastest type for your platform
///config options end
#if QUALITY == QUALITY_LOWEST
#define CONST(x) (x >> 24)
#define SAMPLE_CVT(x) (x >> 8)
#define INSAMPLE int8_t
#define OUTSAMPLE uint8_t //no point producing 16-bit samples using the 8-bit decoder
#define FIXED int8_t
#define FIXED_S int16_t
#define OUT_CLIP_MAX 0x7F
#define OUT_CLIP_MIN -0x80
#define NUM_FRAC_BITS_PROTO 8
#define NUM_FRAC_BITS_COS 6
#elif QUALITY == QUALITY_MEDIUM
#define CONST(x) (x >> 16)
#define SAMPLE_CVT(x) (x)
#define INSAMPLE int16_t
#define OUTSAMPLE uint16_t
#define FIXED int16_t
#define FIXED_S int32_t
#define OUT_CLIP_MAX 0x7FFF
#define OUT_CLIP_MIN -0x8000
#define NUM_FRAC_BITS_PROTO 16
#define NUM_FRAC_BITS_COS 14
#elif QUALITY == QUALITY_GREAT
#define CONST(x) (x)
#define SAMPLE_CVT(x) (x)
#define INSAMPLE int16_t
#define OUTSAMPLE uint16_t
#define FIXED int32_t
#define FIXED_S int64_t
#define OUT_CLIP_MAX 0x7FFF
#define OUT_CLIP_MIN -0x8000
#define NUM_FRAC_BITS_PROTO 32
#define NUM_FRAC_BITS_COS 30
#else
#error "You did not define SBC decoder synthesizer quality to use"
#endif
static const FIXED proto_4_40[] =
{
CONST(0x00000000), CONST(0x00FB7991), CONST(0x02CB3E8B), CONST(0x069FDC59),
CONST(0x22B63DA5), CONST(0x4B583FE6), CONST(0xDD49C25B), CONST(0x069FDC59),
CONST(0xFD34C175), CONST(0x00FB7991), CONST(0x002329CC), CONST(0x00FF11CA),
CONST(0x053B7546), CONST(0x0191E578), CONST(0x31EAB920), CONST(0x4825E4A3),
CONST(0xEC1F5E6D), CONST(0x083DDC80), CONST(0xFF3773A8), CONST(0x00B32807),
CONST(0x0061C5A7), CONST(0x007A4737), CONST(0x07646684), CONST(0xF89F23A7),
CONST(0x3F23948D), CONST(0x3F23948D), CONST(0xF89F23A7), CONST(0x07646684),
CONST(0x007A4737), CONST(0x0061C5A7), CONST(0x00B32807), CONST(0xFF3773A8),
CONST(0x083DDC80), CONST(0xEC1F5E6D), CONST(0x4825E4A3), CONST(0x31EAB920),
CONST(0x0191E578), CONST(0x053B7546), CONST(0x00FF11CA), CONST(0x002329CC)
};
static const FIXED proto_8_80[] =
{
CONST(0x00000000), CONST(0x0083D8D4), CONST(0x0172E691), CONST(0x034FD9E0),
CONST(0x116860F5), CONST(0x259ED8EB), CONST(0xEE979F0B), CONST(0x034FD9E0),
CONST(0xFE8D196F), CONST(0x0083D8D4), CONST(0x000A42E6), CONST(0x0089DE90),
CONST(0x020E372C), CONST(0x02447D75), CONST(0x153E7D35), CONST(0x253844DE),
CONST(0xF2625120), CONST(0x03EBE849), CONST(0xFF1ACF26), CONST(0x0074E5CF),
CONST(0x00167EE3), CONST(0x0082B6EC), CONST(0x02AD6794), CONST(0x00BFA1FF),
CONST(0x18FAB36D), CONST(0x24086BF5), CONST(0xF5FF2BF8), CONST(0x04270CA8),
CONST(0xFF93E21B), CONST(0x0060C1E9), CONST(0x002458FC), CONST(0x0069F16C),
CONST(0x03436717), CONST(0xFEBDD6E5), CONST(0x1C7762DF), CONST(0x221D9DE0),
CONST(0xF950DCFC), CONST(0x0412523E), CONST(0xFFF44825), CONST(0x004AB4C5),
CONST(0x0035FF13), CONST(0x003B1FA4), CONST(0x03C04499), CONST(0xFC4086B8),
CONST(0x1F8E43F2), CONST(0x1F8E43F2), CONST(0xFC4086B8), CONST(0x03C04499),
CONST(0x003B1FA4), CONST(0x0035FF13), CONST(0x004AB4C5), CONST(0xFFF44825),
CONST(0x0412523E), CONST(0xF950DCFC), CONST(0x221D9DE0), CONST(0x1C7762DF),
CONST(0xFEBDD6E5), CONST(0x03436717), CONST(0x0069F16C), CONST(0x002458FC),
CONST(0x0060C1E9), CONST(0xFF93E21B), CONST(0x04270CA8), CONST(0xF5FF2BF8),
CONST(0x24086BF5), CONST(0x18FAB36D), CONST(0x00BFA1FF), CONST(0x02AD6794),
CONST(0x0082B6EC), CONST(0x00167EE3), CONST(0x0074E5CF), CONST(0xFF1ACF26),
CONST(0x03EBE849), CONST(0xF2625120), CONST(0x253844DE), CONST(0x153E7D35),
CONST(0x02447D75), CONST(0x020E372C), CONST(0x0089DE90), CONST(0x000A42E6)
};
static const FIXED costab_4[] =
{
CONST(0x2D413CCD), CONST(0xD2BEC333), CONST(0xD2BEC333), CONST(0x2D413CCD),
CONST(0x187DE2A7), CONST(0xC4DF2862), CONST(0x3B20D79E), CONST(0xE7821D59),
CONST(0x00000000), CONST(0x00000000), CONST(0x00000000), CONST(0x00000000),
CONST(0xE7821D59), CONST(0x3B20D79E), CONST(0xC4DF2862), CONST(0x187DE2A7),
CONST(0xD2BEC333), CONST(0x2D413CCD), CONST(0x2D413CCD), CONST(0xD2BEC333),
CONST(0xC4DF2862), CONST(0xE7821D59), CONST(0x187DE2A7), CONST(0x3B20D79E),
CONST(0xC0000000), CONST(0xC0000000), CONST(0xC0000000), CONST(0xC0000000),
CONST(0xC4DF2862), CONST(0xE7821D59), CONST(0x187DE2A7), CONST(0x3B20D79E)
};
static const FIXED costab_8[] =
{
CONST(0x2D413CCD), CONST(0xD2BEC333), CONST(0xD2BEC333), CONST(0x2D413CCD),
CONST(0x2D413CCD), CONST(0xD2BEC333), CONST(0xD2BEC333), CONST(0x2D413CCD),
CONST(0x238E7673), CONST(0xC13AD060), CONST(0x0C7C5C1E), CONST(0x3536CC52),
CONST(0xCAC933AE), CONST(0xF383A3E2), CONST(0x3EC52FA0), CONST(0xDC71898D),
CONST(0x187DE2A7), CONST(0xC4DF2862), CONST(0x3B20D79E), CONST(0xE7821D59),
CONST(0xE7821D59), CONST(0x3B20D79E), CONST(0xC4DF2862), CONST(0x187DE2A7),
CONST(0x0C7C5C1E), CONST(0xDC71898D), CONST(0x3536CC52), CONST(0xC13AD060),
CONST(0x3EC52FA0), CONST(0xCAC933AE), CONST(0x238E7673), CONST(0xF383A3E2),
CONST(0x00000000), CONST(0x00000000), CONST(0x00000000), CONST(0x00000000),
CONST(0x00000000), CONST(0x00000000), CONST(0x00000000), CONST(0x00000000),
CONST(0xF383A3E2), CONST(0x238E7673), CONST(0xCAC933AE), CONST(0x3EC52FA0),
CONST(0xC13AD060), CONST(0x3536CC52), CONST(0xDC71898D), CONST(0x0C7C5C1E),
CONST(0xE7821D59), CONST(0x3B20D79E), CONST(0xC4DF2862), CONST(0x187DE2A7),
CONST(0x187DE2A7), CONST(0xC4DF2862), CONST(0x3B20D79E), CONST(0xE7821D59),
CONST(0xDC71898D), CONST(0x3EC52FA0), CONST(0xF383A3E2), CONST(0xCAC933AE),
CONST(0x3536CC52), CONST(0x0C7C5C1E), CONST(0xC13AD060), CONST(0x238E7673),
CONST(0xD2BEC333), CONST(0x2D413CCD), CONST(0x2D413CCD), CONST(0xD2BEC333),
CONST(0xD2BEC333), CONST(0x2D413CCD), CONST(0x2D413CCD), CONST(0xD2BEC333),
CONST(0xCAC933AE), CONST(0x0C7C5C1E), CONST(0x3EC52FA0), CONST(0x238E7673),
CONST(0xDC71898D), CONST(0xC13AD060), CONST(0xF383A3E2), CONST(0x3536CC52),
CONST(0xC4DF2862), CONST(0xE7821D59), CONST(0x187DE2A7), CONST(0x3B20D79E),
CONST(0x3B20D79E), CONST(0x187DE2A7), CONST(0xE7821D59), CONST(0xC4DF2862),
CONST(0xC13AD060), CONST(0xCAC933AE), CONST(0xDC71898D), CONST(0xF383A3E2),
CONST(0x0C7C5C1E), CONST(0x238E7673), CONST(0x3536CC52), CONST(0x3EC52FA0),
CONST(0xC0000000), CONST(0xC0000000), CONST(0xC0000000), CONST(0xC0000000),
CONST(0xC0000000), CONST(0xC0000000), CONST(0xC0000000), CONST(0xC0000000),
CONST(0xC13AD060), CONST(0xCAC933AE), CONST(0xDC71898D), CONST(0xF383A3E2),
CONST(0x0C7C5C1E), CONST(0x238E7673), CONST(0x3536CC52), CONST(0x3EC52FA0),
CONST(0xC4DF2862), CONST(0xE7821D59), CONST(0x187DE2A7), CONST(0x3B20D79E),
CONST(0x3B20D79E), CONST(0x187DE2A7), CONST(0xE7821D59), CONST(0xC4DF2862),
CONST(0xCAC933AE), CONST(0x0C7C5C1E), CONST(0x3EC52FA0), CONST(0x238E7673),
CONST(0xDC71898D), CONST(0xC13AD060), CONST(0xF383A3E2), CONST(0x3536CC52)
};
static const int8_t loudness_4[4][4] =
{
{ -1, 0, 0, 0 }, { -2, 0, 0, 1 },
{ -2, 0, 0, 1 }, { -2, 0, 0, 1 }
};
static const int8_t loudness_8[4][8] =
{
{ -2, 0, 0, 0, 0, 0, 0, 1 }, { -3, 0, 0, 0, 0, 0, 1, 2 },
{ -4, 0, 0, 0, 0, 0, 1, 2 }, { -4, 0, 0, 0, 0, 0, 1, 2 }
};
#if QUALITY != QUALITY_MEDIUM //for medium we provide nice assembly routines
static void synth_4(OUTSAMPLE* dst, const INSAMPLE* src, FIXED* V){ //A2DP figure 12.3
ITER i, j;
const FIXED* tabl = proto_4_40;
const FIXED* costab = costab_4;
//shift
for(i = 79; i >= 8; i--) V[i] = V[i - 8];
//matrix
for(i = 0; i < 8; i++){
FIXED_S t = (FIXED_S)costab[0] * (FIXED_S)src[0] +
(FIXED_S)costab[1] * (FIXED_S)src[1] +
(FIXED_S)costab[2] * (FIXED_S)src[2] +
(FIXED_S)costab[3] * (FIXED_S)src[3];
costab += 4;
V[i] = t >> NUM_FRAC_BITS_COS;
}
//calculate audio samples
for(j = 0; j < 4; j++){
OUTSAMPLE s;
FIXED_S sample = (FIXED_S)V[j + 0] * (FIXED_S)tabl[0] +
(FIXED_S)V[j + 12] * (FIXED_S)tabl[1] +
(FIXED_S)V[j + 16] * (FIXED_S)tabl[2] +
(FIXED_S)V[j + 28] * (FIXED_S)tabl[3] +
(FIXED_S)V[j + 32] * (FIXED_S)tabl[4] +
(FIXED_S)V[j + 44] * (FIXED_S)tabl[5] +
(FIXED_S)V[j + 48] * (FIXED_S)tabl[6] +
(FIXED_S)V[j + 60] * (FIXED_S)tabl[7] +
(FIXED_S)V[j + 64] * (FIXED_S)tabl[8] +
(FIXED_S)V[j + 76] * (FIXED_S)tabl[9];
tabl += 10;
sample >>= (NUM_FRAC_BITS_PROTO - 1 - 2); //-2 is for the -4 we need to multiply by :)
sample = -sample;
if(sample > OUT_CLIP_MAX) sample = OUT_CLIP_MAX;
else if(sample < OUT_CLIP_MIN) sample = OUT_CLIP_MIN;
s = sample;
#ifndef DESKTOP
s += 0x8000;
s += 8;
s >>= 4;
#endif
dst[j] = s;
}
}
static void synth_8(OUTSAMPLE* dst, const INSAMPLE* src, FIXED* V){ //A2DP figure 12.3
ITER i, j;
const FIXED* tabl = proto_8_80;
const FIXED* costab = costab_8;
//shift
for(i = 159; i >= 16; i--) V[i] = V[i - 16];
//matrix
for(i = 0; i < 16; i++){
FIXED_S t = (FIXED_S)costab[0] * (FIXED_S)src[0] +
(FIXED_S)costab[1] * (FIXED_S)src[1] +
(FIXED_S)costab[2] * (FIXED_S)src[2] +
(FIXED_S)costab[3] * (FIXED_S)src[3] +
(FIXED_S)costab[4] * (FIXED_S)src[4] +
(FIXED_S)costab[5] * (FIXED_S)src[5] +
(FIXED_S)costab[6] * (FIXED_S)src[6] +
(FIXED_S)costab[7] * (FIXED_S)src[7];
costab += 8;
V[i] = t >> NUM_FRAC_BITS_COS;
}
//calculate audio samples
for(j = 0; j < 8; j++){
OUTSAMPLE s;
FIXED_S sample = (FIXED_S)V[j + 0] * (FIXED_S)tabl[0] +
(FIXED_S)V[j + 24] * (FIXED_S)tabl[1] +
(FIXED_S)V[j + 32] * (FIXED_S)tabl[2] +
(FIXED_S)V[j + 56] * (FIXED_S)tabl[3] +
(FIXED_S)V[j + 64] * (FIXED_S)tabl[4] +
(FIXED_S)V[j + 88] * (FIXED_S)tabl[5] +
(FIXED_S)V[j + 96] * (FIXED_S)tabl[6] +
(FIXED_S)V[j +120] * (FIXED_S)tabl[7] +
(FIXED_S)V[j +128] * (FIXED_S)tabl[8] +
(FIXED_S)V[j +152] * (FIXED_S)tabl[9];
tabl += 10;
sample >>= (NUM_FRAC_BITS_PROTO - 1 - 3); //-3 is for the -8 we need to multiply by :)
sample = -sample;
if(sample > OUT_CLIP_MAX) sample = OUT_CLIP_MAX;
else if(sample < OUT_CLIP_MIN) sample = OUT_CLIP_MIN;
s = sample;
#ifndef DESKTOP
s += 0x8000;
s += 8;
s >>= 4;
#endif
dst[j] = s;
}
}
static void synth(OUTSAMPLE* dst, const INSAMPLE* src, uint8_t nBands, FIXED* V){ //A2DP sigure 12.3
//efficient SBC synth by Dmitry Grinberg (as published May 26, 2012)
if(nBands == 4) synth_4(dst, src, V);
else synth_8(dst, src, V);
}
#else
static void __attribute__((naked)) synth_4_med(OUTSAMPLE* dst, const INSAMPLE* src, FIXED* V, const FIXED* costabl, const FIXED* prot_Tabl){
asm(
//prologue {8 cycles}
"push {r4-r7} \n\t"
//shifting { 9x(9+9 + 1) + 1 = 171 cycles }
"add r2, #128 \n\t"
"ldmia r2!, {r4-r7} \n\t"
"stmia r2, {r4-r7} \n\t"
"subs r2, #32 \n\t"
"ldmia r2!, {r4-r7} \n\t"
"stmia r2, {r4-r7} \n\t"
"subs r2, #32 \n\t"
"ldmia r2!, {r4-r7} \n\t"
"stmia r2, {r4-r7} \n\t"
"subs r2, #32 \n\t"
"ldmia r2!, {r4-r7} \n\t"
"stmia r2, {r4-r7} \n\t"
"subs r2, #32 \n\t"
"ldmia r2!, {r4-r7} \n\t"
"stmia r2, {r4-r7} \n\t"
"subs r2, #32 \n\t"
"ldmia r2!, {r4-r7} \n\t"
"stmia r2, {r4-r7} \n\t"
"subs r2, #32 \n\t"
"ldmia r2!, {r4-r7} \n\t"
"stmia r2, {r4-r7} \n\t"
"subs r2, #32 \n\t"
"ldmia r2!, {r4-r7} \n\t"
"stmia r2, {r4-r7} \n\t"
"subs r2, #32 \n\t"
"ldmia r2!, {r4-r7} \n\t"
"stmia r2, {r4-r7} \n\t"
"subs r2, #16 \n\t" //r2 is right back where it started
//matrixing
"movs r7, #8 \n\t" //r7 is loop index
"matrix_loop_4: \n\t"
"ldrsh r4, [r3, #0] \n\t"
"ldrsh r5, [r1, #0] \n\t"
"mul r6, r4, r5 \n\t"
"ldrsh r4, [r3, #2] \n\t"
"ldrsh r5, [r1, #2] \n\t"
"mla r6, r4, r5, r6 \n\t"
"ldrsh r4, [r3, #4] \n\t"
"ldrsh r5, [r1, #4] \n\t"
"mla r6, r4, r5, r6 \n\t"
"ldrsh r4, [r3, #6] \n\t"
"ldrsh r5, [r1, #6] \n\t"
"mla r6, r4, r5, r6 \n\t"
"adds r3, #8 \n\t"
"asrs r6, #14 \n\t"
"strh r6, [r2], #2 \n\t"
"subs r7, #1 \n\t"
"bne matrix_loop_4 \n\t"
//return r2 to point where it should
"subs r2, #16 \n\t"
//sample production
"movs r7, #4 \n\t" //r7 is loop index
"ldr r3, [sp, #16] \n\t" //r3 is now proto table
"sample_loop_4: \n\t"
"ldrsh r4, [r3, #0] \n\t"
"ldrsh r5, [r2, #0] \n\t"
"mul r6, r4, r5 \n\t"
"ldrsh r4, [r3, #2] \n\t"
"ldrsh r5, [r2, #24] \n\t"
"mla r6, r4, r5, r6 \n\t"
"ldrsh r4, [r3, #4] \n\t"
"ldrsh r5, [r2, #32] \n\t"
"mla r6, r4, r5, r6 \n\t"
"ldrsh r4, [r3, #6] \n\t"
"ldrsh r5, [r2, #56] \n\t"
"mla r6, r4, r5, r6 \n\t"
"ldrsh r4, [r3, #8] \n\t"
"ldrsh r5, [r2, #64] \n\t"
"mla r6, r4, r5, r6 \n\t"
"ldrsh r4, [r3, #10] \n\t"
"ldrsh r5, [r2, #88] \n\t"
"mla r6, r4, r5, r6 \n\t"
"ldrsh r4, [r3, #12] \n\t"
"ldrsh r5, [r2, #96] \n\t"
"mla r6, r4, r5, r6 \n\t"
"ldrsh r4, [r3, #14] \n\t"
"ldrsh r5, [r2, #120] \n\t"
"mla r6, r4, r5, r6 \n\t"
"ldrsh r4, [r3, #16] \n\t"
"ldrsh r5, [r2, #128] \n\t"
"mla r6, r4, r5, r6 \n\t"
"ldrsh r4, [r3, #18] \n\t"
"ldrsh r5, [r2, #152] \n\t"
"mla r6, r4, r5, r6 \n\t"
"adds r3, #20 \n\t"
"adds r2, #2 \n\t"
"negs r6, r6 \n\t"
"ssat r6, #12, r6, asr #16 \n\t"
"add r6, #0x0800 \n\t"
"strh r6, [r0], #2 \n\t"
"subs r7, #1 \n\t"
"bne sample_loop_4 \n\t"
//cleanup
"pop {r4-r7} \n\t"
"bx lr \n\t"
);
}
static void __attribute__((naked)) synth_8_med(OUTSAMPLE* dst, const INSAMPLE* src, FIXED* V, const FIXED* costabl, const FIXED* prot_Tabl){
asm(
//prologue {8 cycles}
"push {r4-r10} \n\t"
//shifting { 9x(9+9 + 1) + 1 = 171 cycles }
"add r2, #256 \n\t"
"ldmia r2!, {r4-r10, r12} \n\t"
"stmia r2, {r4-r10, r12} \n\t"
"subs r2, #64 \n\t"
"ldmia r2!, {r4-r10, r12} \n\t"
"stmia r2, {r4-r10, r12} \n\t"
"subs r2, #64 \n\t"
"ldmia r2!, {r4-r10, r12} \n\t"
"stmia r2, {r4-r10, r12} \n\t"
"subs r2, #64 \n\t"
"ldmia r2!, {r4-r10, r12} \n\t"
"stmia r2, {r4-r10, r12} \n\t"
"subs r2, #64 \n\t"
"ldmia r2!, {r4-r10, r12} \n\t"
"stmia r2, {r4-r10, r12} \n\t"
"subs r2, #64 \n\t"
"ldmia r2!, {r4-r10, r12} \n\t"
"stmia r2, {r4-r10, r12} \n\t"
"subs r2, #64 \n\t"
"ldmia r2!, {r4-r10, r12} \n\t"
"stmia r2, {r4-r10, r12} \n\t"
"subs r2, #64 \n\t"
"ldmia r2!, {r4-r10, r12} \n\t"
"stmia r2, {r4-r10, r12} \n\t"
"subs r2, #64 \n\t"
"ldmia r2!, {r4-r10, r12} \n\t"
"stmia r2, {r4-r10, r12} \n\t"
"subs r2, #32 \n\t" //r2 is right back where it started
//matrixing
"movs r7, #16 \n\t" //r7 is loop index
"matrix_loop: \n\t"
"ldrsh r4, [r3, #0] \n\t"
"ldrsh r5, [r1, #0] \n\t"
"mul r6, r4, r5 \n\t"
"ldrsh r4, [r3, #2] \n\t"
"ldrsh r5, [r1, #2] \n\t"
"mla r6, r4, r5, r6 \n\t"
"ldrsh r4, [r3, #4] \n\t"
"ldrsh r5, [r1, #4] \n\t"
"mla r6, r4, r5, r6 \n\t"
"ldrsh r4, [r3, #6] \n\t"
"ldrsh r5, [r1, #6] \n\t"
"mla r6, r4, r5, r6 \n\t"
"ldrsh r4, [r3, #8] \n\t"
"ldrsh r5, [r1, #8] \n\t"
"mla r6, r4, r5, r6 \n\t"
"ldrsh r4, [r3, #10] \n\t"
"ldrsh r5, [r1, #10] \n\t"
"mla r6, r4, r5, r6 \n\t"
"ldrsh r4, [r3, #12] \n\t"
"ldrsh r5, [r1, #12] \n\t"
"mla r6, r4, r5, r6 \n\t"
"ldrsh r4, [r3, #14] \n\t"
"ldrsh r5, [r1, #14] \n\t"
"mla r6, r4, r5, r6 \n\t"
"adds r3, #16 \n\t"
"asrs r6, #14 \n\t"
"strh r6, [r2], #2 \n\t"
"subs r7, #1 \n\t"
"bne matrix_loop \n\t"
//return r2 to point where it should
"subs r2, #32 \n\t"
//sample production
"movs r7, #8 \n\t" //r7 is loop index
"ldr r3, [sp, #28] \n\t" //r3 is now proto table
"sample_loop: \n\t"
"ldrsh r4, [r3, #0] \n\t"
"ldrsh r5, [r2, #0] \n\t"
"mul r6, r4, r5 \n\t"
"ldrsh r4, [r3, #2] \n\t"
"ldrsh r5, [r2, #48] \n\t"
"mla r6, r4, r5, r6 \n\t"
"ldrsh r4, [r3, #4] \n\t"
"ldrsh r5, [r2, #64] \n\t"
"mla r6, r4, r5, r6 \n\t"
"ldrsh r4, [r3, #6] \n\t"
"ldrsh r5, [r2, #112] \n\t"
"mla r6, r4, r5, r6 \n\t"
"ldrsh r4, [r3, #8] \n\t"
"ldrsh r5, [r2, #128] \n\t"
"mla r6, r4, r5, r6 \n\t"
"ldrsh r4, [r3, #10] \n\t"
"ldrsh r5, [r2, #176] \n\t"
"mla r6, r4, r5, r6 \n\t"
"ldrsh r4, [r3, #12] \n\t"
"ldrsh r5, [r2, #192] \n\t"
"mla r6, r4, r5, r6 \n\t"
"ldrsh r4, [r3, #14] \n\t"
"ldrsh r5, [r2, #240] \n\t"
"mla r6, r4, r5, r6 \n\t"
"ldrsh r4, [r3, #16] \n\t"
"ldrsh r5, [r2, #256] \n\t"
"mla r6, r4, r5, r6 \n\t"
"ldrsh r4, [r3, #18] \n\t"
"ldrsh r5, [r2, #304] \n\t"
"mla r6, r4, r5, r6 \n\t"
"adds r3, #20 \n\t"
"adds r2, #2 \n\t"
"negs r6, r6 \n\t"
"ssat r6, #12, r6, asr #16 \n\t"
"add r6, #0x0800 \n\t"
"strh r6, [r0], #2 \n\t"
"subs r7, #1 \n\t"
"bne sample_loop \n\t"
//cleanup
"pop {r4-r10} \n\t"
"bx lr \n\t"
);
}
static void synth(OUTSAMPLE* dst, const INSAMPLE* src, uint8_t nBands, FIXED* V){ //A2DP sigure 12.3
//efficient SBC synth by Dmitry Grinberg (as published May 26, 2012)
if(nBands == 4) synth_4_med(dst, src, V, costab_4, proto_4_40);
else synth_8_med(dst, src, V, costab_8, proto_8_80);
}
#endif
static void a2dpSbcPlay(const uint8_t* buf, uint16_t len){ //only supports mono (for simplicity)
//volume
int32_t vol = (uint32_t)getVolume();
//convenience
const uint8_t* end = buf + len;
#define left (end - buf)
//workspace
ITER i, j, k, ch, numChan;
uint8_t scaleFactors[2][8];
int8_t bitneed[2][8];
uint8_t bits[2][8], join = 0;
INSAMPLE samples[16][2][8];
//audio data
#define bufNumBuffers 3 //if less than 3, we're being suboptimal in speed.
#define bufNumSamples 256 //FYI: 128 samples is the maximum number of samples a single packet may have. MUST be a multiple of 8!
static OUTSAMPLE audio[bufNumBuffers][bufNumSamples];
static uint8_t whichBuf = 0;
static uint16_t bufSamples = 0;
//process the packet header
if(left < 12) return; //too short of a packet header
uint8_t ver = (*buf) >> 6;
char pad = ((*buf) >> 5) & 1;
char ext = ((*buf) >> 4) & 1;
uint8_t cc = (*buf++) & 0x0F;
char mark = (*buf) >> 7;
uint8_t pt = (*buf++) & 0x7F;
uint16_t seq = (((uint16_t)buf[0]) << 8) | buf[1];
uint32_t time = (((uint32_t)buf[2]) << 24) | (((uint32_t)buf[3]) << 16) | (((uint32_t)buf[4]) << 8) | buf[5];
uint32_t src = (((uint32_t)buf[6]) << 24) | (((uint32_t)buf[7]) << 16) | (((uint32_t)buf[8]) << 8) | buf[9];
buf += 10;
//process the count
if(left < 1) return; //too short of a count
uint8_t numFrames = *buf++;
//process packets
while(numFrames--){
//process frame header
if(left < 4) break; //too short a frame header
uint8_t sync = *buf++;
uint8_t samplingRate = (*buf) >> 6; //see A2DP table 12.16
uint8_t blocks = ((*buf) >> 4) & 3; //see A2DP table 12.17
uint8_t chanMode = ((*buf) >> 2) & 3; //see A2DP table 12.18
uint8_t snr = ((*buf) >> 1) & 1; //see A2DP table 12.19
uint8_t numSubbands = (*buf++) & 1; //see A2DP table 12.20
uint8_t bitpoolSz = *buf++;
uint8_t hdrCRC = *buf++;
uint8_t bitpos = 0x80;
numChan = (chanMode == A2DP_CHAN_MODE_MONO) ? 1 : 2;
//process some numbers based on the tables
numSubbands = numSubbands ? 8 : 4;
blocks = (blocks + 1) << 2;
//read "join" table if expected
if(chanMode == A2DP_CHAN_MODE_JOINT_STEREO){
join = *buf; //we use it as a bitfield starting at top bit.
join >>= (8 - (numSubbands - 1));
join <<= (8 - (numSubbands - 1));
if(numSubbands == 8) buf++;
else bitpos = 0x08;
}
//read scale factors
for(ch = 0; ch < numChan; ch++) for(i = 0; i < numSubbands; i++){
if(bitpos == 0x80){
scaleFactors[ch][i] = (*buf) >> 4;
bitpos = 0x08;
}
else{
scaleFactors[ch][i] = (*buf++) & 0x0F;
bitpos = 0x80;
}
}
//calculate bitneed table and max_bitneed value (A2DP 12.6.3.1)
int8_t max_bitneed[2] = {0, 0};
for(ch = 0; ch < numChan; ch++){
if(snr){
for(i = 0; i < numSubbands; i++){
bitneed[ch][i] = scaleFactors[ch][i];
if(bitneed[ch][i] > max_bitneed[ch]) max_bitneed[ch] = bitneed[ch][i];
}
}
else{
const signed char* tbl;
if(numSubbands == 4) tbl = loudness_4[samplingRate];
else tbl = loudness_8[samplingRate];
for(i = 0; i < numSubbands; i++){
if(scaleFactors[ch][i]){
int loudness = scaleFactors[ch][i] - tbl[i];
if(loudness > 0) loudness /= 2;
bitneed[ch][i] = loudness;
}
else bitneed[ch][i] = -5;
if(bitneed[ch][i] > max_bitneed[ch]) max_bitneed[ch] = bitneed[ch][i];
}
}
}
if(chanMode == A2DP_CHAN_MODE_MONO || chanMode == A2DP_CHAN_MODE_DUAL_CHANNEL){
for(ch = 0; ch < numChan; ch++){
//fit bitslices into the bitpool
int32_t bitcount = 0, slicecount = 0, bitslice = max_bitneed[ch] + 1;
do{
bitslice--;
bitcount += slicecount;
slicecount = 0;
for(i = 0; i < numSubbands; i++){
if(bitneed[ch][i] > bitslice + 1 && bitneed[ch][i] < bitslice + 16) slicecount++;
else if(bitneed[ch][i] == bitslice + 1) slicecount += 2;
}
}while(bitcount + slicecount < bitpoolSz);
//distribute bits
for(i = 0; i < numSubbands; i++){
if(bitneed[ch][i] < bitslice + 2) bits[ch][i] = 0;
else{
int8_t v = bitneed[ch][i] - bitslice;
if(v > 16) v = 16;
bits[ch][i] = v;
}
}
//allocate remaining bits
for(i = 0; i < numSubbands && bitcount < bitpoolSz; i++){
if(bits[ch][i] >= 2 && bits[ch][i] < 16){
bits[ch][i]++;
bitcount++;
}
else if(bitneed[ch][i] == bitslice + 1 && bitpoolSz > bitcount + 1){
bits[ch][i] = 2;
bitcount += 2;
}
}
for(i = 0; i < numSubbands && bitcount < bitpoolSz; i++){
if(bits[ch][i] < 16){
bits[ch][i]++;
bitcount++;
}
}
}
}
else{
//calculate max_bitneed value (A2DP 12.6.3.2)
uint8_t max_bitneed_val = max_bitneed[0] > max_bitneed[1] ? max_bitneed[0] : max_bitneed[1];
//fit bitslices into the bitpool
int32_t bitcount = 0, slicecount = 0, bitslice = max_bitneed_val + 1;
do{
bitslice--;
bitcount += slicecount;
slicecount = 0;
for(ch = 0; ch < 2; ch++) for(i = 0; i < numSubbands; i++){
if(bitneed[ch][i] > bitslice + 1 && bitneed[ch][i] < bitslice + 16) slicecount++;
else if(bitneed[ch][i] == bitslice + 1) slicecount += 2;
}
}while(bitcount + slicecount < bitpoolSz);
//distribute bits
for(ch = 0; ch < 2; ch++) for(i = 0; i < numSubbands; i++){
if(bitneed[ch][i] < bitslice + 2) bits[ch][i] = 0;
else{
int8_t v = bitneed[ch][i] - bitslice;
if(v > 16) v = 16;
bits[ch][i] = v;
}
}
//allocate remaining bits
i = 0;
ch = 0;
while(i < numSubbands && bitcount < bitpoolSz){
if(bits[ch][i] >= 2 && bits[ch][i] < 16){
bits[ch][i]++;
bitcount++;
}
else if(bitneed[ch][i] == bitslice + 1 && bitpoolSz > bitcount + 1){
bits[ch][i] = 2;
bitcount += 2;
}
if(++ch == 2){
ch = 0;
i++;
}
}
i = 0;
ch = 0;
while(i < numSubbands && bitcount < bitpoolSz){
if(bits[ch][i] < 16){
bits[ch][i]++;
bitcount++;
}
if(++ch == 2){
ch = 0;
i++;
}
}
}
//reconstruct subband samples (A2DP 12.6.4)
#ifndef SPEED_OVER_ACCURACY
int32_t levels[2][8];
for(ch = 0; ch < numChan; ch++) for(i = 0; i < numSubbands; i++) levels[ch][i] = (1 << bits[ch][i]) - 1;
#endif
for(j = 0; j < blocks; j++){
for(ch = 0; ch < numChan; ch++) for(i = 0; i < numSubbands; i++){
if(bits[ch][i]){
uint32_t val = 0;
k = bits[ch][i];
do{
val <<= 1;
if(*buf & bitpos) val++;
if(!(bitpos >>= 1)){
bitpos = 0x80;
buf++;
}
}while(--k);
val = (val << 1) | 1;
val <<= scaleFactors[ch][i];
#ifdef SPEED_OVER_ACCURACY
val >>= bits[ch][i];
#else
val /= levels[ch][i];
#endif
val -= (1 << scaleFactors[ch][i]);
samples[j][ch][i] = SAMPLE_CVT(val);
}
else samples[j][ch][i] = SAMPLE_CVT(0);
}
//joint processing (if needed - if not needed join will be 0)
ITER bf = join;
for(i = 0; i < numSubbands; i++, bf <<= 1) if(bf & 0x80){
INSAMPLE t = samples[j][0][i];
samples[j][0][i] += samples[j][1][i];
samples[j][1][i] = t - samples[j][1][i];
}
}
//synthesis
static FIXED V[160] = {0, };
for(j = 0; j < blocks; j++){
//do the actual synthesis
synth(audio[whichBuf] + bufSamples, samples[j][0], numSubbands, V);
for(k = 0; k < numSubbands; k++, bufSamples++) audio[whichBuf][bufSamples] = (((int32_t)audio[whichBuf][bufSamples]) * vol) >> 8;
//if buffer is full, enqueue it
if(bufSamples == bufNumSamples){
audioAddBuffer(AUDIO_BT, audio[whichBuf++], bufSamples);
if(whichBuf == bufNumBuffers) whichBuf = 0;
bufSamples = 0;
}
}
//if we used a byte partially, skip the rest of it, it is "padding"
if(bitpos != 0x80) buf++;
if(left < 0) dbgPrintf("A2DP: buffer over-read: %d\n", left);
}
}
static void a2dpServiceDataRx(void* service, const uint8_t* req, uint16_t reqSz){
A2DPstate* state = service;
uint8_t trans, pktTyp, msgTyp, sigId, eid;
uint8_t buf[16];
uint16_t replSz = 0;
if(IS_DATA_STATE(service)){
if(DATA_STATE_ENCODE(service) == conns) a2dpSbcPlay(req, reqSz); //only latest plays data
return;
}
if(reqSz < 2){
dbgPrintf("A2DP: packet too small\n");
return;
}
trans = (req[0] & AVDTP_HDR_MASK_TRANS) >> AVDTP_HDR_SHIFT_TRANS;
pktTyp = (req[0] & AVDTP_HDR_MASK_PKT_TYP) >> AVDTP_HDR_SHIFT_PKT_TYP;
msgTyp = (req[0] & AVDTP_HDR_MASK_MSG_TYP) >> AVDTP_HDR_SHIFT_MSG_TYP;
sigId = (req[1] & AVDTP_HDR_MASK_SIG_ID) >> AVDTP_HDR_SHIFT_SIG_ID;
reqSz -= 2;
req += 2;
#if UGLY_SCARY_DEBUGGING_CODE
dbgPrintf("A2DP (%x) data(trans %d, pktTyp %d msgTyp %d sigId %d %ub)\n", state->remChan, trans, pktTyp, msgTyp, sigId, reqSz);
#endif
switch(sigId){
case AVDTP_SIG_DISCOVER:
if(reqSz) dbgPrintf("A2DP: unexpected further data in AVDTP_SIG_DISCOVER\n");
//packet header
buf[0] = (trans << AVDTP_HDR_SHIFT_TRANS) | (AVDTP_PKT_TYP_SINGLE << AVDTP_HDR_SHIFT_PKT_TYP) | (AVDTP_MSG_TYP_ACCEPT << AVDTP_HDR_SHIFT_MSG_TYP);
buf[1] = (sigId << AVDTP_HDR_SHIFT_SIG_ID);
//return a single SEID info structure
buf[2] = (MY_ENDPT_ID << AVDTP_SEID_NFO_SHIFT_SEID) | (0 << AVDTP_SEID_NFO_MASK_INUSE);
buf[3] = (AVDTP_MEDIA_TYP_AUDIO << AVDTP_SEID_NFO_SHIFT_MEDIA_TYP) | (AVDTP_DIR_SINK << AVDTP_SEID_NFO_SHIFT_TYP);
replSz = 4;
break;
case AVDTP_SIG_GET_CAPABILITIES:
if(reqSz > 1) dbgPrintf("A2DP: unexpected further data in AVDTP_SIG_GET_CAPABILITIES\n");
if(reqSz < 1){
dbgPrintf("A2DP: not enough data in AVDTP_SIG_GET_CAPABILITIES\n");
break;
}
eid = (*req++) >> 2;
if(eid != MY_ENDPT_ID){
dbgPrintf("A2DP: bad EID (%d) AVDTP_SIG_GET_CAPABILITIES\n", eid);
break;
}
//packet header
buf[0] = (trans << AVDTP_HDR_SHIFT_TRANS) | (AVDTP_PKT_TYP_SINGLE << AVDTP_HDR_SHIFT_PKT_TYP) | (AVDTP_MSG_TYP_ACCEPT << AVDTP_HDR_SHIFT_MSG_TYP);
buf[1] = (sigId << AVDTP_HDR_SHIFT_SIG_ID);
//capability 0 - media transport
buf[2] = AVDTP_SVC_CAT_MEDIA_TRANSPORT;
buf[3] = 0;
//capability 1 - SDB codec (and its info)
buf[4] = AVDTP_SVC_CAT_MEDIA_CODEC;
buf[5] = 6; //generic data is 2 bytes, SBC data is 4 bytes (see A2DP spec section 4.3.2)
buf[6] = (AVDTP_MEDIA_TYP_AUDIO << 4);
buf[7] = A2DP_CODEC_TYP_SBC;
buf[8] = 0xFF; //spec forces us to support all of these (even though ANDROID and LINUX both do not properly support mono [try it :) ])
buf[9] = 0xFF; //spec forces us to support all of these
buf[10] = 0x02; //min allowed by spec
buf[11] = 0x50; //nice high value
replSz = 12;
break;
case AVDTP_SIG_SET_CONFIGURATION:
if(reqSz < 8){ //no valid command is shorter
dbgPrintf("A2DP: not enough data in AVDTP_SIG_SET_CONFIGURATION\n");
break;
}
eid = (*req++) >> 2;
if(eid != MY_ENDPT_ID){
dbgPrintf("A2DP: bad EID (%d) AVDTP_SIG_GET_CAPABILITIES\n", eid);
break;
}
req++; //skip INT EID - we don't care
reqSz -= 2;
while(reqSz >= 2){
uint8_t cap = *req++;
uint8_t len = *req++;
reqSz -= 2;
if(len > reqSz) break;
reqSz -= len;
if(cap == AVDTP_SVC_CAT_MEDIA_CODEC){
if(*req++ != (AVDTP_MEDIA_TYP_AUDIO << 4)){
dbgPrintf("A2DP: requested data is not audio\n");
break;
}
else if(*req++ != A2DP_CODEC_TYP_SBC){
dbgPrintf("A2DP: requested codec is not SBC\n");
break;
}
else{
uint8_t samplingRate = (*req) >> 4;
uint8_t channelMode = (*req++) & 0x0F;
uint8_t blockLen = (*req) >> 4;
uint8_t subbands = (*req >> 2) & 3;
uint8_t allocMethod = (*req++) & 3;
uint8_t minBitpool = *req++;
uint8_t maxBitpool = *req++;
switch(samplingRate){
case 1: state->samplingRate = 48000; break;
case 2: state->samplingRate = 44100; break;
case 4: state->samplingRate = 32000; break;
case 8: state->samplingRate = 16000; break;
default:
dbgPrintf("A2DP: invalid sampling rate: %d\n", samplingRate);
goto out;
}
switch(blockLen){
case 1: blockLen = 16; break;
case 2: blockLen = 12; break;
case 4: blockLen = 8; break;
case 8: blockLen = 4; break;
default:
dbgPrintf("A2DP: invalid block len: %d\n", blockLen);
goto out;
}
switch(subbands){
case 1: subbands = 8; break;
case 2: subbands = 4; break;
default:
dbgPrintf("A2DP: invalid num subbands: %d\n", subbands);
goto out;
}
switch(allocMethod){
case 1: allocMethod = 0; break;
case 2: allocMethod = 1; break;
default:
dbgPrintf("A2DP: invalid allocMethod: %d\n", allocMethod);
goto out;
}
static const char* strMode[] = {NULL, "Joint Stereo", "Stereo", NULL, "Dual Channel", NULL, NULL, NULL, "Mono"};
dbgPrintf("A2DP:\n\t* %u %s-framed frames / block\n\t* %u subbands/frame\n\t* %u KHz sampling rate\n\t* '%s' channel mode\n\t* %u-%u bits per piece per channel\n",
blockLen, allocMethod ? "SNR" : "Loudness", subbands,
state->samplingRate, strMode[channelMode], minBitpool, maxBitpool);
}
}
else req += len;
}
if(reqSz){ //no valid command is shorter
dbgPrintf("A2DP: malformed config info in AVDTP_SIG_SET_CONFIGURATION\n");
}
else{
//prepare reply
buf[0] = (trans << AVDTP_HDR_SHIFT_TRANS) | (AVDTP_PKT_TYP_SINGLE << AVDTP_HDR_SHIFT_PKT_TYP) | (AVDTP_MSG_TYP_ACCEPT << AVDTP_HDR_SHIFT_MSG_TYP);
buf[1] = (sigId << AVDTP_HDR_SHIFT_SIG_ID);
replSz = 2;
#if UGLY_SCARY_DEBUGGING_CODE
dbgPrintf("A2DP: %u samp/sec, %db block w/%d subbands in %s mode with pool sizes %d-%d\n",
state->samplingRate, state->blockLen, state->subbands, state->allocTypeSNR ? "SNR" : "Loudness",
state->minBitpool, state->maxBitpool);
#endif
}
break;
case AVDTP_SIG_OPEN:
if(reqSz > 1) dbgPrintf("A2DP: unexpected further data in AVDTP_SIG_OPEN\n");
if(reqSz < 1){
dbgPrintf("A2DP: not enough data in AVDTP_SIG_OPEN\n");
break;
}
eid = (*req++) >> 2;
if(eid != MY_ENDPT_ID){
dbgPrintf("A2DP: bad EID (%d) AVDTP_SIG_OPEN\n", eid);
break;
}
state->needAudioConn = 1;
//packet header
buf[0] = (trans << AVDTP_HDR_SHIFT_TRANS) | (AVDTP_PKT_TYP_SINGLE << AVDTP_HDR_SHIFT_PKT_TYP) | (AVDTP_MSG_TYP_ACCEPT << AVDTP_HDR_SHIFT_MSG_TYP);
buf[1] = (sigId << AVDTP_HDR_SHIFT_SIG_ID);
replSz = 2;
break;
case AVDTP_SIG_START:
if(reqSz > 1) dbgPrintf("A2DP: unexpected further data in AVDTP_SIG_OPEN\n");
if(reqSz < 1){
dbgPrintf("A2DP: not enough data in AVDTP_SIG_OPEN\n");
break;
}
eid = (*req++) >> 2;
if(eid != MY_ENDPT_ID){
dbgPrintf("A2DP: bad EID (%d) AVDTP_SIG_OPEN\n", eid);
break;
}
audioOn(AUDIO_BT, state->samplingRate);
//packet header
buf[0] = (trans << AVDTP_HDR_SHIFT_TRANS) | (AVDTP_PKT_TYP_SINGLE << AVDTP_HDR_SHIFT_PKT_TYP) | (AVDTP_MSG_TYP_ACCEPT << AVDTP_HDR_SHIFT_MSG_TYP);
buf[1] = (sigId << AVDTP_HDR_SHIFT_SIG_ID);
replSz = 2;
break;
case AVDTP_SIG_CLOSE:
case AVDTP_SIG_SUSPEND:
case AVDTP_SIG_ABORT:
if(reqSz > 1) dbgPrintf("A2DP: unexpected further data in AVDTP_SIG_CLOSE/ABORT/SUSPEND\n");
if(reqSz < 1){
dbgPrintf("A2DP: not enough data in AVDTP_SIG_CLOSE/ABORT/SUSPEND\n");
break;
}
eid = (*req++) >> 2;
if(eid != MY_ENDPT_ID){
dbgPrintf("A2DP: bad EID (%d) AVDTP_SIG_CLOSE/ABORT/SUSPEND\n", eid);
break;
}
audioOff(AUDIO_BT);
//packet header
buf[0] = (trans << AVDTP_HDR_SHIFT_TRANS) | (AVDTP_PKT_TYP_SINGLE << AVDTP_HDR_SHIFT_PKT_TYP) | (AVDTP_MSG_TYP_ACCEPT << AVDTP_HDR_SHIFT_MSG_TYP);
buf[1] = (sigId << AVDTP_HDR_SHIFT_SIG_ID);
replSz = 2;
break;
default:
dbgPrintf("A2DP (%x) data(trans %d, pktTyp %d msgTyp %d sigId %d %ub)\n", state->remChan, trans, pktTyp, msgTyp, sigId, reqSz);
dbgPrintf("DATA: ");
while(reqSz--) dbgPrintf(" %02X", *req++);
dbgPrintf("\n");
break;
}
out:
if(replSz){
sg_buf* sgbuf = sg_alloc();
if(!sgbuf) return;
if(sg_add_front(sgbuf, buf, replSz, SG_FLAG_MAKE_A_COPY)){
l2capServiceTx(state->aclConn, state->remChan, sgbuf);
}
else{
sg_free(sgbuf);
free(sgbuf);
}
}
}
static void* a2dpServiceAlloc(uint16_t conn, uint16_t chan, uint16_t remChan){
A2DPstate* state = conns;
while(state){
if(state->needAudioConn && state->aclConn == conn){
state->needAudioConn = 0;
return DATA_STATE_ENCODE(state);
}
state = state->next;
}
state = malloc(sizeof(A2DPstate));
if(state){
state->aclConn = conn;
state->remChan = remChan;
state->needAudioConn = 0;
state->next = conns;
conns = state;
}
return state;
}
static void a2dpServiceFree(void* service){
if(IS_DATA_STATE(service)) return;
A2DPstate *state = conns, *prev = NULL;
while(state){
if(state == service){
if(prev) prev->next = state->next;
else conns = state->next;
dbgPrintf("A2DP: connection %d.%d closed\n", state->aclConn, state->remChan);
if(conns) audioOn(AUDIO_BT, conns->samplingRate);
free(state);
return;
}
prev = state;
state = state->next;
}
}
static uint8_t sdpDescrA2DP[] =
{
//service class ID list
SDP_ITEM_DESC(SDP_TYPE_UINT, SDP_SZ_2), 0x00, 0x01, SDP_ITEM_DESC(SDP_TYPE_ARRAY, SDP_SZ_u8), 3,
SDP_ITEM_DESC(SDP_TYPE_UUID, SDP_SZ_2), 0x11, 0x0B, //Audio Sink
//ServiceId
SDP_ITEM_DESC(SDP_TYPE_UINT, SDP_SZ_2), 0x00, 0x03, SDP_ITEM_DESC(SDP_TYPE_UUID, SDP_SZ_2), 0x11, 0x0B, //Audio Sink
//ProtocolDescriptorList
SDP_ITEM_DESC(SDP_TYPE_UINT, SDP_SZ_2), 0x00, 0x04, SDP_ITEM_DESC(SDP_TYPE_ARRAY, SDP_SZ_u8), 16,
SDP_ITEM_DESC(SDP_TYPE_ARRAY, SDP_SZ_u8), 6,
SDP_ITEM_DESC(SDP_TYPE_UUID, SDP_SZ_2), 0x01, 0x00, // L2CAP
SDP_ITEM_DESC(SDP_TYPE_UINT, SDP_SZ_2), L2CAP_PSM_AVDTP >> 8, L2CAP_PSM_AVDTP & 0xFF, // AVDTP PSM
SDP_ITEM_DESC(SDP_TYPE_ARRAY, SDP_SZ_u8), 6,
SDP_ITEM_DESC(SDP_TYPE_UUID, SDP_SZ_2), L2CAP_PSM_AVDTP >> 8, L2CAP_PSM_AVDTP & 0xFF, // AVDTP
SDP_ITEM_DESC(SDP_TYPE_UINT, SDP_SZ_2), 0x01, 0x00, //AVDTP version (as per spec)
//browse group list
SDP_ITEM_DESC(SDP_TYPE_UINT, SDP_SZ_2), 0x00, 0x05, SDP_ITEM_DESC(SDP_TYPE_ARRAY, SDP_SZ_u8), 3,
SDP_ITEM_DESC(SDP_TYPE_UUID, SDP_SZ_2), 0x10, 0x02, // Public Browse Group
//profile descriptor list
SDP_ITEM_DESC(SDP_TYPE_UINT, SDP_SZ_2), 0x00, 0x09, SDP_ITEM_DESC(SDP_TYPE_ARRAY, SDP_SZ_u8), 8,
SDP_ITEM_DESC(SDP_TYPE_ARRAY, SDP_SZ_u8), 6,
SDP_ITEM_DESC(SDP_TYPE_UUID, SDP_SZ_2), 0x11, 0x0d, // Advanced Audio
SDP_ITEM_DESC(SDP_TYPE_UINT, SDP_SZ_2), 0x01, 0x00, // Version 1.0
//name
SDP_ITEM_DESC(SDP_TYPE_UINT, SDP_SZ_2), 0x01, 0x00, SDP_ITEM_DESC(SDP_TYPE_TEXT, SDP_SZ_u8), 8, 'A', 'D', 'K', ' ', 'A', '2', 'D', 'P'
};
char btA2dpRegister(void){
const L2capService a2dp = {L2CAP_FLAG_SUPPORT_CONNECTIONS, a2dpServiceAlloc, a2dpServiceFree, a2dpServiceDataRx};
if(!l2capServiceRegister(L2CAP_PSM_AVDTP, &a2dp)) return 0;
btSdpServiceDescriptorAdd(sdpDescrA2DP, sizeof(sdpDescrA2DP));
return 1;
}
/* original tables
proto_4_40:
0.00000000E+00,5.36548976E-04,1.49188357E-03,2.73370904E-03,
3.83720193E-03,3.89205149E-03,1.86581691E-03,-3.06012286E-03,
1.09137620E-02,2.04385087E-02,2.88757392E-02,3.21939290E-02,
2.58767811E-02,6.13245186E-03,-2.88217274E-02,-7.76463494E-02,
1.35593274E-01,1.94987841E-01,2.46636662E-01,2.81828203E-01,
2.94315332E-01,2.81828203E-01,2.46636662E-01,1.94987841E-01,
-1.35593274E-01,-7.76463494E-02,-2.88217274E-02,6.13245186E-03,
2.58767811E-02,3.21939290E-02,2.88757392E-02,2.04385087E-02,
-1.09137620E-02,-3.06012286E-03,1.86581691E-03,3.89205149E-03,
3.83720193E-03,2.73370904E-03,1.49188357E-03,5.36548976E-04
proto_8_80:
0.00000000E+00,1.56575398E-04,3.43256425E-04,5.54620202E-04,
8.23919506E-04,1.13992507E-03,1.47640169E-03,1.78371725E-03,
2.01182542E-03,2.10371989E-03,1.99454554E-03,1.61656283E-03,
9.02154502E-04,-1.78805361E-04,-1.64973098E-03,-3.49717454E-03,
5.65949473E-03,8.02941163E-03,1.04584443E-02,1.27472335E-02,
1.46525263E-02,1.59045603E-02,1.62208471E-02,1.53184106E-02,
1.29371806E-02,8.85757540E-03,2.92408442E-03,-4.91578024E-03,
-1.46404076E-02,-2.61098752E-02,-3.90751381E-02,-5.31873032E-02,
6.79989431E-02,8.29847578E-02,9.75753918E-02,1.11196689E-01,
1.23264548E-01,1.33264415E-01,1.40753505E-01,1.45389847E-01,
1.46955068E-01,1.45389847E-01,1.40753505E-01,1.33264415E-01,
1.23264548E-01,1.11196689E-01,9.75753918E-02,8.29847578E-02,
-6.79989431E-02,-5.31873032E-02,-3.90751381E-02,-2.61098752E-02,
-1.46404076E-02,-4.91578024E-03,2.92408442E-03,8.85757540E-03,
1.29371806E-02,1.53184106E-02,1.62208471E-02,1.59045603E-02,
1.46525263E-02,1.27472335E-02,1.04584443E-02,8.02941163E-03,
-5.65949473E-03,-3.49717454E-03,-1.64973098E-03,-1.78805361E-04,
9.02154502E-04,1.61656283E-03,1.99454554E-03,2.10371989E-03,
2.01182542E-03,1.78371725E-03,1.47640169E-03,1.13992507E-03,
8.23919506E-04,5.54620202E-04,3.43256425E-04,1.56575398E-04
js code to convert to fixpoint:
var xa = new Array(values here...);
var num = 0;
var perRow = 4;
var L = parseInt(xa.length);
for(i = 0; i < L; i++){
x = xa[i];
var neg = 0;
if(x < 0){
neg = 1;
x = -x;
}
x *= (1 << 26); //this 26 should be the number of fraction bits
x = parseInt(x + 0.5);
s = x >> 28
x &= 0x0FFFFFFF;
if(neg){
x = x ^ 0x0FFFFFFF;
x++;
s ^= 0x0F;
if(x & 0x10000000) s++;
x &= 0x0FFFFFFF;
s &= 0x0F;
}
x = x.toString(16);
while(x.length < 7) x = "0" + x;
x = s.toString(16) + x;
x = x.toUpperCase();
document.write("0x" + x);
if(i != L - 1) document.write(",");
if(++num == perRow){
num = 0;
document.write("<BR>");
}
else document.write(" ");
}
js code to produce costab (adjust loop variables as needed for both table vairants)
for(k = 0; k <= 7; k++) for(i = 0 ;i <= 3; i++){
document.write(Math.cos((i + 0.5) * (k + 2) * Math.PI / 4) + ", ")
}
js code to generate order tables (they are used for those strange offsets into the V array in synth_* when generating samples):
var L = 200;
var V = new Array(L);
var U = new Array(80);
var i, j;
var nBands = 4;
for(i = 0; i <L; i++) V[i] = i + 1;
for(i = 0; i <= 4; i++) for(j = 0; j < nBands; j++){
if(nBands == 4){
U[i * 8 + j] = V[i * 16 + j];
U[i * 8 + 4 + j] = V[i * 16 + 12 + j];
}
else{
U[i * 16 + j] = V[i * 32 + j];
U[i * 16 + 8 + j] = V[i * 32 + 24 + j];
}
}
for(j = 0; j < nBands; j++) for(i = 0; i < 10; i++) document.write((U[j + nBands * i] - 1) + ",\t");
C code for reordering the proto_* tables to access order. insert table values and modify nBands as needed
#include <stdio.h>
#include <stdint.h>
int32_t tabl[] =
{
table data here
};
int main(int argc, char** argv){
int i, j;
int nBands = 8;
for(j = 0; j < nBands; j++) for(i = 0; i < 10; i++) printf("0x%08X, ",tabl[j + nBands * i]);
}
*/