blob: 2d134485dd8776186f2d9679cf984db5e26dbd45 [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.
*/
#include "rfcomm.h"
#include "a2dp.h"
#include "log.h"
#include "sdp.h"
#include "l2cap.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <pthread.h>
//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 PSM_AVDTP 0x0019
#define AUDIO_SINK_UUID 0x110B
#define ADVANCED_AUDIO_UUID 0x110D
typedef struct A2dpInstance {
struct A2dpState* state;
} A2dpInstance;
typedef struct A2dpState {
//L2CAP data
l2c_handle_t l2cHandle;
acl_handle_t aclHandle;
A2dpInstance* ctrlInstance;
A2dpInstance* dataInstance;
} A2dpState;
// to enforce only one connnection at a time
static A2dpState* gState = NULL;
static pthread_mutex_t gStateLock = PTHREAD_MUTEX_INITIALIZER;
static struct a2dpSinkDescriptor* gDesc = NULL;
#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
*/
//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 QUALITY_GREAT
#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 }
};
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
i = 8;
do{
FIXED_S t;
t = (FIXED_S)costab[0] * (FIXED_S)src[0];
t += (FIXED_S)costab[1] * (FIXED_S)src[1];
t += (FIXED_S)costab[2] * (FIXED_S)src[2];
t += (FIXED_S)costab[3] * (FIXED_S)src[3];
costab += 4;
*V++ = t >> NUM_FRAC_BITS_COS;
}while(--i);
V -= 8;
//calculate audio samples
j = 0;
do{
OUTSAMPLE s;
FIXED_S sample;
sample = (FIXED_S)V[ 0] * (FIXED_S)tabl[0];
sample += (FIXED_S)V[ 12] * (FIXED_S)tabl[1];
sample += (FIXED_S)V[ 16] * (FIXED_S)tabl[2];
sample += (FIXED_S)V[ 28] * (FIXED_S)tabl[3];
sample += (FIXED_S)V[ 32] * (FIXED_S)tabl[4];
sample += (FIXED_S)V[ 44] * (FIXED_S)tabl[5];
sample += (FIXED_S)V[ 48] * (FIXED_S)tabl[6];
sample += (FIXED_S)V[ 60] * (FIXED_S)tabl[7];
sample += (FIXED_S)V[ 64] * (FIXED_S)tabl[8];
sample += (FIXED_S)V[ 76] * (FIXED_S)tabl[9];
tabl += 10;
V++;
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;
if(sample <= OUT_CLIP_MIN) sample = OUT_CLIP_MIN;
s = sample;
*dst++ = s;
}while(--j);
}
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
i = 16;
do{
FIXED_S t;
t = (FIXED_S)costab[0] * (FIXED_S)src[0];
t += (FIXED_S)costab[1] * (FIXED_S)src[1];
t += (FIXED_S)costab[2] * (FIXED_S)src[2];
t += (FIXED_S)costab[3] * (FIXED_S)src[3];
t += (FIXED_S)costab[4] * (FIXED_S)src[4];
t += (FIXED_S)costab[5] * (FIXED_S)src[5];
t += (FIXED_S)costab[6] * (FIXED_S)src[6];
t += (FIXED_S)costab[7] * (FIXED_S)src[7];
costab += 8;
*V++ = t >> NUM_FRAC_BITS_COS;
}while(--i);
V -= 16;
//calculate audio samples
j = 8;
do{
OUTSAMPLE s;
FIXED_S sample;
sample = (FIXED_S)V[ 0] * (FIXED_S)tabl[0];
sample += (FIXED_S)V[ 24] * (FIXED_S)tabl[1];
sample += (FIXED_S)V[ 32] * (FIXED_S)tabl[2];
sample += (FIXED_S)V[ 56] * (FIXED_S)tabl[3];
sample += (FIXED_S)V[ 64] * (FIXED_S)tabl[4];
sample += (FIXED_S)V[ 88] * (FIXED_S)tabl[5];
sample += (FIXED_S)V[ 96] * (FIXED_S)tabl[6];
sample += (FIXED_S)V[120] * (FIXED_S)tabl[7];
sample += (FIXED_S)V[128] * (FIXED_S)tabl[8];
sample += (FIXED_S)V[152] * (FIXED_S)tabl[9];
tabl += 10;
V++;
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;
if(sample < OUT_CLIP_MIN) sample = OUT_CLIP_MIN;
s = sample;
*dst++ = s;
}while(--j);
}
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);
}
static inline int32_t mulshift(int32_t val, uint32_t bits){ //return approximately val / ((2^bits) - 1)
static const uint32_t tab[] = {
0x00000000, 0x00000000, 0x55555555, 0x24924925,
0x11111111, 0x08421084, 0x04104104, 0x02040810,
0x01010101, 0x00804020, 0x00401004, 0x00200400,
0x00100100, 0x00080040, 0x00040010, 0x00020004,
0x00010001
};
if (bits > 1)
val = (((uint64_t)(uint32_t)val) * (uint64_t)tab[bits]) >> 32; /* if all goes well, this will compile to a single UMULL instr on ARMv5+ */
return val;
}
static void a2dpSbcPlay(sg sgbuf) {
//convenience
int offset = 0;
int length = sgLength(sgbuf);
#define left (length - offset)
//workspace
ITER i, j, k, ch, numChan;
uint32_t scaleFactors[2][8];
int32_t bitneed[2][8];
uint32_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 1536 //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 uint32_t whichBuf = 0;
static uint32_t bufSamples = 0;
static FIXED V[2][160] = {{0,},};
uint8_t numFrames;
//process the packet header
if (left < 12) return; //too short of a packet header
/*
* we no longer need to parse packet header here since in A2DP the params are agreed-upon earlier
* ver = (*buf[0]) >> 6;
* pad = ((*buf[0]) >> 5) & 1;
* ext = ((*buf[0]) >> 4) & 1;
* cc = (*buf[0]) & 0x0F;
* mark = (*buf[1]) >> 7;
* pt = (*buf[1]) & 0x7F;
* seq = (((uint16_t)buf[2]) << 8) | buf[3];
* time = (((uint32_t)buf[4]) << 24) | (((uint32_t)buf[5]) << 16) | (((uint32_t)buf[6]) << 8) | buf[7];
* src = (((uint32_t)buf[8]) << 24) | (((uint32_t)buf[9]) << 16) | (((uint32_t)buf[10]) << 8) | buf[11];
*/
offset += 12;
//process the count
if(left < 1) return; //too short of a count
sgSerialize(sgbuf, offset++, 1, &numFrames);
//process packets
while (numFrames--) {
uint8_t sync, tmp, samplingRate, blocks, chanMode, snr, numSubbands, bitpoolSz, hdrCRC, bitpos = 0x80;
int8_t max_bitneed[2] = {0, 0};
//process frame header
if(left < 4) break; //too short a frame header
sgSerialize(sgbuf, offset++, 1, &sync);
if (sync != 0x9C) {
loge("sync %02X != 0x9C, bailing\n", sync);
return;
}
sgSerialize(sgbuf, offset++, 1, &tmp);
samplingRate = tmp >> 6; //see A2DP table 12.16
blocks = (tmp >> 4) & 3; //see A2DP table 12.17
chanMode = (tmp >> 2) & 3; //see A2DP table 12.18
snr = (tmp >> 1) & 1; //see A2DP table 12.19
numSubbands = tmp & 1; //see A2DP table 12.20
sgSerialize(sgbuf, offset++, 1, &bitpoolSz);
sgSerialize(sgbuf, offset++, 1, &hdrCRC);
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){
sgSerialize(sgbuf, offset, 1, &tmp);
join = tmp; //we use it as a bitfield starting at top bit.
join >>= (8 - (numSubbands - 1));
join <<= (8 - (numSubbands - 1));
if(numSubbands == 8) offset++;
else bitpos = 0x08;
}
//read scale factors
for(ch = 0; ch < numChan; ch++) for(i = 0; i < numSubbands; i++){
sgSerialize(sgbuf, offset, 1, &tmp);
if(bitpos == 0x80) {
scaleFactors[ch][i] = tmp >> 4;
bitpos = 0x08;
} else {
scaleFactors[ch][i] = tmp & 0x0F;
offset++;
bitpos = 0x80;
}
}
//calculate bitneed table and max_bitneed value (A2DP 12.6.3.1)
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++){
ITER bf = join;
for(ch = 0; ch < numChan; ch++) for(i = 0; i < numSubbands; i++){
if(bits[ch][i]){
uint32_t val = 0;
k = bits[ch][i];
sgSerialize(sgbuf, offset, 1, &tmp);
do{
val <<= 1;
if(tmp & bitpos) val++;
if(!(bitpos >>= 1)){
bitpos = 0x80;
sgSerialize(sgbuf, ++offset, 1, &tmp);
}
}while(--k);
val = (val << 1) | 1;
val <<= scaleFactors[ch][i];
#ifdef SPEED_OVER_ACCURACY
//clever arm hack to approximate division quickly
val = mulshift(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)
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
for(j = 0; j < blocks; j++){
OUTSAMPLE t_out[2][8];
ITER ch;
//do the actual synthesis
for(ch = 0; ch < numChan; ch++) synth(t_out[ch], samples[j][ch], numSubbands, V[ch]);
for(i = 0; i < numSubbands; i++) for(ch = 0; ch < 2; ch++) audio[whichBuf][bufSamples++] = t_out[numChan == 2 ? ch : 0][i]; //we always output stereo
//if buffer is full, enqueue it
if(bufSamples == bufNumSamples){
gDesc->dataCb(gDesc->userData, 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) offset++;
if(left < 0) loge("A2DP: buffer over-read: %d\n", left);
}
}
static void a2dpServiceDataRx(void* service, sg sgbuf) {
A2dpInstance* instance = (A2dpInstance *)service;
A2dpState* state = instance->state;
uint8_t trans, pktTyp, msgTyp, sigId, eid;
uint8_t buf[16];
uint16_t replSz = 0;
uint16_t reqSz = sgLength(sgbuf);
int offset = 0;
uint8_t tmp;
uint8_t newState;
if(instance == state->dataInstance) {
a2dpSbcPlay(sgbuf);
return;
}
if (reqSz < 2) {
loge("A2DP: packet too small\n");
return;
}
sgSerialize(sgbuf, offset++, 1, &tmp);
trans = (tmp & AVDTP_HDR_MASK_TRANS) >> AVDTP_HDR_SHIFT_TRANS;
pktTyp = (tmp & AVDTP_HDR_MASK_PKT_TYP) >> AVDTP_HDR_SHIFT_PKT_TYP;
msgTyp = (tmp & AVDTP_HDR_MASK_MSG_TYP) >> AVDTP_HDR_SHIFT_MSG_TYP;
sgSerialize(sgbuf, offset++, 1, &tmp);
sigId = (tmp & AVDTP_HDR_MASK_SIG_ID) >> AVDTP_HDR_SHIFT_SIG_ID;
reqSz -= 2;
#if A2DP_DBG
logd("A2DP data(trans %d, pktTyp %d msgTyp %d sigId %d %ub)\n", trans, pktTyp, msgTyp, sigId, reqSz);
#endif
switch (sigId) {
case AVDTP_SIG_DISCOVER:
logd("AVDTP_SIG_DISCOVER\n");
if(reqSz)loge("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:
logd("AVDTP_SIG_GET_CAPABILITIES\n");
if(reqSz > 1)loge("A2DP: unexpected further data in AVDTP_SIG_GET_CAPABILITIES\n");
if(reqSz < 1) {
loge("A2DP: not enough data in AVDTP_SIG_GET_CAPABILITIES\n");
break;
}
sgSerialize(sgbuf, offset++, 1, &tmp);
eid = tmp >> 2;
if(eid != MY_ENDPT_ID){
loge("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:
logd("AVDTP_SIG_SET_CONFIGURATION\n");
if(reqSz < 8){ //no valid command is shorter
loge("A2DP: not enough data in AVDTP_SIG_SET_CONFIGURATION\n");
break;
}
sgSerialize(sgbuf, offset++, 1, &tmp);
eid = tmp >> 2;
if(eid != MY_ENDPT_ID){
loge("A2DP: bad EID (%d) AVDTP_SIG_GET_CAPABILITIES\n", eid);
break;
}
offset++; //skip INT EID - we don't care
reqSz -= 2;
while(reqSz >= 2){
uint8_t cap, len;
sgSerialize(sgbuf, offset++, 1, &cap);
sgSerialize(sgbuf, offset++, 1, &len);
reqSz -= 2;
if(len > reqSz) break;
reqSz -= len;
if(cap == AVDTP_SVC_CAT_MEDIA_CODEC){
sgSerialize(sgbuf, offset++, 1, &tmp);
if(tmp != (AVDTP_MEDIA_TYP_AUDIO << 4)){
loge("A2DP: requested data is not audio\n");
break;
}
sgSerialize(sgbuf, offset++, 1, &tmp);
if(tmp != A2DP_CODEC_TYP_SBC){
loge("A2DP: requested codec is not SBC\n");
break;
} else {
static const char* strMode[] = {NULL, "Joint Stereo", "Stereo", NULL, "Dual Channel", NULL, NULL, NULL, "Mono"};
uint8_t samplingRate, channelMode, blockLen, subbands, allocMethod, minBitpool, maxBitpool;
unsigned int rate;
sgSerialize(sgbuf, offset++, 1, &tmp);
samplingRate = tmp >> 4;
channelMode = tmp & 0x0F;
sgSerialize(sgbuf, offset++, 1, &tmp);
blockLen = tmp >> 4;
subbands = (tmp >> 2) & 3;
allocMethod = tmp & 3;
sgSerialize(sgbuf, offset++, 1, &minBitpool);
sgSerialize(sgbuf, offset++, 1, &maxBitpool);
switch(samplingRate){
case 1: rate = 48000; break;
case 2: rate = 44100; break;
case 4: rate = 32000; break;
case 8: rate = 16000; break;
default:
loge("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:
loge("A2DP: invalid block len: %d\n", blockLen);
goto out;
}
switch(subbands){
case 1: subbands = 8; break;
case 2: subbands = 4; break;
default:
loge("A2DP: invalid num subbands: %d\n", subbands);
goto out;
}
switch(allocMethod){
case 1: allocMethod = 0; break;
case 2: allocMethod = 1; break;
default:
loge("A2DP: invalid allocMethod: %d\n", allocMethod);
goto out;
}
logd("call paramsCb %p rate: %d\n", gDesc->paramsCb, rate);
gDesc->paramsCb(gDesc->userData, rate, channelMode);
logd("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,
rate, strMode[channelMode], minBitpool, maxBitpool);
}
}
else offset += len;
}
if(reqSz) { //no valid command is shorter
loge("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;
}
break;
case AVDTP_SIG_OPEN:
logd("AVDTP_SIG_OPEN\n");
if(reqSz > 1) loge("A2DP: unexpected further data in AVDTP_SIG_OPEN\n");
if(reqSz < 1){
loge("A2DP: not enough data in AVDTP_SIG_OPEN\n");
break;
}
sgSerialize(sgbuf, offset++, 1, &tmp);
eid = tmp >> 2;
if(eid != MY_ENDPT_ID){
loge("A2DP: bad EID (%d) AVDTP_SIG_OPEN\n", eid);
break;
}
gDesc->stateCb(gDesc->userData, A2DP_STATE_OPEN);
//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:
logd("AVDTP_SIG_START\n");
if(reqSz > 1) loge("A2DP: unexpected further data in AVDTP_SIG_OPEN\n");
if(reqSz < 1){
loge("A2DP: not enough data in AVDTP_SIG_OPEN\n");
break;
}
sgSerialize(sgbuf, offset++, 1, &tmp);
eid = tmp >> 2;
if(eid != MY_ENDPT_ID){
loge("A2DP: bad EID (%d) AVDTP_SIG_OPEN\n", eid);
break;
}
gDesc->stateCb(gDesc->userData, A2DP_STATE_START);
//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:
logd("AVDTP_SIG_CLOSE/AVDTP_SIG_SUSPEND/AVDTP_SIG_ABORT\n");
if(reqSz > 1) loge("A2DP: unexpected further data in AVDTP_SIG_CLOSE/ABORT/SUSPEND\n");
if(reqSz < 1){
loge("A2DP: not enough data in AVDTP_SIG_CLOSE/ABORT/SUSPEND\n");
break;
}
sgSerialize(sgbuf, offset++, 1, &tmp);
eid = tmp >> 2;
if(eid != MY_ENDPT_ID){
loge("A2DP: bad EID (%d) AVDTP_SIG_CLOSE/ABORT/SUSPEND\n", eid);
break;
}
gDesc->stateCb(gDesc->userData, A2DP_STATE_STOP);
//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:
// fprintf(stderr, "A2DP data(trans %d, pktTyp %d msgTyp %d sigId %d %ub)\n", trans, pktTyp, msgTyp, sigId, reqSz);
// fprintf(stderr, "DATA: ");
// while(reqSz--) fprintf(stderr, " %02X", *req++);
// fprintf(stderr, "\n");
break;
}
out:
if (replSz) {
sg sgbuf = sgNewWithCopyData(buf, replSz);
if (sgbuf) {
l2cApiDataTx(state->l2cHandle, sgbuf);
} else {
loge("sgNewWithCopyData fail\n");
}
}
}
static void a2dpServiceClose(void* service) {
A2dpInstance* instance = (A2dpInstance *)service;
A2dpState* state = instance->state;
pthread_mutex_lock(&gStateLock);
if (state != gState) {
loge("unknown state %p\n", state);
goto out;
}
if (instance == state->ctrlInstance) {
state->ctrlInstance = NULL;
} else if (instance == state->dataInstance) {
state->dataInstance = NULL;
}
free(instance);
if (!state->ctrlInstance && !state->dataInstance) {
logd("gState closed\n");
gDesc->stateCb(gDesc->userData, A2DP_STATE_CLOSED);
free(state);
gState = NULL;
}
out:
pthread_mutex_unlock(&gStateLock);
}
static uint8_t a2dpServiceChAlloc(void *userData, l2c_handle_t handle, void **instanceP) {
A2dpState* state;
A2dpInstance* instance;
acl_handle_t aclHandle = l2cApiGetAclHandle(handle);
uint8_t ret = SVC_ALLOC_FAIL_OTHER;
logw("a2dpServiceChAlloc %lld aclHandle: %lld\n", handle, aclHandle);
pthread_mutex_lock(&gStateLock);
if (gState) {
if (gState->dataInstance == NULL && gState->aclHandle == aclHandle) {
instance = (A2dpInstance *)malloc(sizeof(*instance));
instance->state = gState;
gState->dataInstance = instance;
*instanceP = instance;
ret = SVC_ALLOC_SUCCESS;
goto out;
}
loge("a2dpServiceChAlloc rejecting second connection\n");
goto out;
}
state = (A2dpState *)malloc(sizeof(A2dpState));
instance = (A2dpInstance *)malloc(sizeof(*instance));
if (state && instance) {
state->l2cHandle = handle;
state->aclHandle = aclHandle;
state->ctrlInstance = instance;
state->dataInstance = NULL;
instance->state = state;
gState = state;
*instanceP = instance;
ret = SVC_ALLOC_SUCCESS;
}
out:
pthread_mutex_unlock(&gStateLock);
return ret;
}
static void a2dpServiceChStateCbk(void *userData, void *instance, uint8_t state, const void *data, uint32_t len) {
switch (state) {
case L2C_STATE_OPEN:
logw("L2C_STATE_OPEN handle: %lld\n", *((l2c_handle_t *)data));
if (len != sizeof(l2c_handle_t)) {
loge("invalid length for open event\n");
break;
}
break;
case L2C_STATE_MTU:
logw("L2C_STATE_MTU mtu: %d\n", *((uint16_t *)data));
if (len != sizeof(uint16_t)) {
loge("invalid length for mtu event\n");
break;
}
break;
case L2C_STATE_ENCR:
logw("L2C_STATE_ENCR\n");
break;
case L2C_STATE_RX:
// logw("L2C_STATE_RX\n");
if (len != sizeof(sg)) {
loge("invalid length for RX event\n");
break;
}
a2dpServiceDataRx(instance, *((sg *)data));
break;
case L2C_STATE_CLOSED:
logw("L2C_STATE_CLOSED\n");
a2dpServiceClose(instance);
break;
}
}
static uint8_t sdpDescrA2DP[] = {
//service class ID list
SDP_ITEM_DESC(SDP_TYPE_UINT, SDP_SZ_2), SDP_U16(SDP_ATTR_SVC_CLS_ID_LIST),
SDP_ITEM_DESC(SDP_TYPE_ARRAY, SDP_SZ_u8), 3,
SDP_ITEM_DESC(SDP_TYPE_UUID, SDP_SZ_2), SDP_U16(AUDIO_SINK_UUID),
//ServiceId
SDP_ITEM_DESC(SDP_TYPE_UINT, SDP_SZ_2), SDP_U16(SDP_ATTR_SVC_ID),
SDP_ITEM_DESC(SDP_TYPE_UUID, SDP_SZ_2), SDP_U16(AUDIO_SINK_UUID),
//ProtocolDescriptorList
SDP_ITEM_DESC(SDP_TYPE_UINT, SDP_SZ_2), SDP_U16(SDP_ATTR_PROTOCOL_DESCR_LIST),
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), SDP_U16(SDP_PROTO_L2CAP),
SDP_ITEM_DESC(SDP_TYPE_UINT, SDP_SZ_2), SDP_U16(PSM_AVDTP),
SDP_ITEM_DESC(SDP_TYPE_ARRAY, SDP_SZ_u8), 6,
SDP_ITEM_DESC(SDP_TYPE_UUID, SDP_SZ_2), SDP_U16(PSM_AVDTP),
SDP_ITEM_DESC(SDP_TYPE_UINT, SDP_SZ_2), SDP_U16(0x0100), //AVDTP version (as per spec)
//browse group list
SDP_ITEM_DESC(SDP_TYPE_UINT, SDP_SZ_2), SDP_U16(SDP_ATTR_BROWSE_GRP_LIST),
SDP_ITEM_DESC(SDP_TYPE_ARRAY, SDP_SZ_u8), 3,
SDP_ITEM_DESC(SDP_TYPE_UUID, SDP_SZ_2), SDP_U16(SDP_BROWSE_GROUP_ID_PUBLIC),
//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), SDP_U16(ADVANCED_AUDIO_UUID), // Advanced Audio
SDP_ITEM_DESC(SDP_TYPE_UINT, SDP_SZ_2), SDP_U16(0x0100), // Version 1.0
//name
SDP_ITEM_DESC(SDP_TYPE_UINT, SDP_SZ_2), 0x01, 0x00, SDP_ITEM_DESC(SDP_TYPE_TEXT, SDP_SZ_u8), 9,
'A', '2', 'D', 'P', ' ', 'S', 'i', 'n', 'k'
};
bool a2dpSinkInit(const struct a2dpSinkDescriptor* desc) {
// FIXME synchronization
if (gDesc) {
loge("A2DP sink already initialized\n");
return false;
}
gDesc = malloc(sizeof(*gDesc));
if (!gDesc)
return false;
memcpy(gDesc, desc, sizeof(*gDesc));
static const struct l2cServicePsmDescriptor a2dpService = {
.serviceInstanceAlloc = a2dpServiceChAlloc,
.serviceInstanceStateCbk = a2dpServiceChStateCbk,
.serviceConnectionlessRx = NULL,
.mtu = 0xFFFF,
};
if(!l2cApiServicePsmRegister(PSM_AVDTP, &a2dpService)) return 0;
sdpServiceDescriptorAdd(sdpDescrA2DP, sizeof(sdpDescrA2DP));
return true;
}
void a2dpSinkDeinit() {
// FIXME synchronization and L2CAP cleanup
if (gDesc) {
free(gDesc);
gDesc = NULL;
}
}