说明:本文适用于 RK 作为蓝牙设备的蓝牙通话设计。硬件上,BT 芯片的 PCM 时钟和数据线直接连接到 RK 芯片端的一组 i2s 。软件上,需要将 BT 芯片注册成一个声卡。
Downlink:
远端信号 -> 蓝牙端 -> AP -> BT SoundCard -> PCM -> SOC SoundCard -> 输出设备
Uplink:
远端信号 <- 蓝牙端 <- AP <- BT SoundCard <- PCM <- SOC SoundCard <- 输入设备
(注:以下处理以 RK3326_ANDROID8.1 为例,其他安卓版本均可参考下述代码实现)
1、蓝牙端 i2s 作主,提供 PCM 时钟:
bt-sound {
compatible = "simple-audio-card";
simple-audio-card,format = "dsp_b";
simple-audio-card,bitclock-inversion = <1>;
simple-audio-card,mclk-fs = <256>;
simple-audio-card,name = "rockchip,bt";
simple-audio-card,bitclock-master = <&sound2_master>;
simple-audio-card,frame-master = <&sound2_master>;
simple-audio-card,cpu {
sound-dai = <&i2s2_2ch>;//RK3326 i2s2_2ch
};
sound2_master:simple-audio-card,codec {
sound-dai = <&bt_sco>;
};
};
bt_sco: bt-sco {
compatible = "delta,dfbmcs320";
#sound-dai-cells = <0>;
status = "okay";
};
2、RK i2s 作主,提供 PCM 时钟:
bt-sound {
compatible = "simple-audio-card";
simple-audio-card,format = "dsp_b";
simple-audio-card,bitclock-inversion = <1>;
simple-audio-card,mclk-fs = <256>;
simple-audio-card,name = "rockchip,bt";
simple-audio-card,cpu {
sound-dai = <&i2s2_2ch>;//RK3326 i2s2_2ch
};
simple-audio-card,codec {
sound-dai = <&bt_sco>;
};
};
bt_sco: bt-sco {
compatible = "delta,dfbmcs320";
#sound-dai-cells = <0>;
status = "okay";
};
hardware/rockchip/audio/tinyalsa_hal
diff --git a/tinyalsa_hal/audio_hw.c b/tinyalsa_hal/audio_hw.c
index 0a35b80..8fef004 100755
--- a/tinyalsa_hal/audio_hw.c
+++ b/tinyalsa_hal/audio_hw.c
@@ -36,7 +36,7 @@
* @date 2015-08-24
*/
-//#define LOG_NDEBUG 0
+#define LOG_NDEBUG 0
#define LOG_TAG "AudioHardwareTiny"
#include "alsa_audio.h"
@@ -47,13 +47,24 @@
#include "audio_bitstream.h"
#include "audio_setting.h"
+
+#include "speex/speex_echo.h"
+#include "speex/speex_preprocess.h"
+
#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
+//#define BT_HFP_DUMP_FAR_IN //touch /data/fd_far_in.pcm 8K
+//#define BT_HFP_DUMP_NEAR_OUT //touch /data/fd_near_out.pcm 48k
+//#define BT_HFP_DUMP_NEAR_IN //touch /data/fd_near_in.pcm 48k
+//#define BT_HFP_DUMP_FAR_OUT //touch /data/fd_far_out.pcm 8k
+
//#define ALSA_DEBUG
#ifdef ALSA_IN_DEBUG
FILE *in_debug;
#endif
+bool hfp_status = false;
+
int in_dump(const struct audio_stream *stream, int fd);
int out_dump(const struct audio_stream *stream, int fd);
@@ -123,6 +134,432 @@ int get_input_source_id(audio_source_t source)
}
}
+
+int SCO_PCM_FAR_IN = 1; // read pcm date for bt pcm sound card
+int SCO_PCM_FAR_OUT = 1; // out to bt pcm sound card
+int SCO_PCM_NEAR_IN = 0; // mic in sound card
+int SCO_PCM_NEAR_OUT = 0; // speaker out sound card
+//#define SPEEX_AEC
+#ifdef SPEEX_AEC
+//#define AEC_DUMP
+#endif
+void volume_process(const void *buffer, size_t length, float volume) {
+
+ short * buffer_end = (short*)buffer + (length/2);
+ short * pcmData = (short *)buffer;
+
+ while (pcmData < buffer_end) {
+ *pcmData = (short)((float)*pcmData * volume);
+ ++pcmData;
+ }
+}
+
+#define INT16_MIN (-32768)
+#define INT16_MAX (32767)
+/*left & right channel right mix data*/
+void stereo_lr_mix(void * indata, int bytes)
+{
+ int i = 0;
+ float channell = 0.0;
+ float channelr = 0.0;
+ float channelmix = 0.0;
+ short *buffer = (short *)indata;
+
+ for (i = 0; i < bytes / 2 ; i = i + 2) {
+ channell = (float)(((short *)buffer)[i]);
+ channelr = (float)(((short *)buffer)[i + 1]);
+ if ((channell > 0.0) && (channelr > 0.0)) {
+ channelmix = (channell + channelr) - ((channell * channelr) / INT16_MAX);
+ } else if ((channell < 0.0) && (channelr < 0.0)) {
+ channelmix = (channell + channelr) - ((channell * channelr) / INT16_MIN);
+ } else {
+ channelmix = channell + channelr;
+ }
+ ((short *)buffer)[i] = ((short *)buffer)[i + 1] = (short)channelmix;
+ }
+}
+/*left channel is noise data so copy right to left*/
+void stereo_rtol(void * indata, int bytes)
+{
+ int i = 0;
+ short *buffer = (short *)indata;
+ for (i = 0; i < bytes / 2 ; i = i + 2) {
+ ((short *)buffer)[i] = ((short *)buffer)[i + 1];
+ }
+}
+
+void stereo_ltor(void * indata, int bytes)
+{
+ int i = 0;
+ short *buffer = (short *)indata;
+ for (i = 0; i < bytes / 2 ; i = i + 2) {
+ ((short *)buffer)[i + 1] = ((short *)buffer)[i];
+ }
+}
+
+
+void* run_hfp_sco(void * args) {
+
+ int rc;
+ struct audio_device * adev = (struct audio_device *)args;
+ struct resampler_itfe *resampler_8to48;
+ struct resampler_itfe *resampler_48to8;
+
+ int16_t *framebuf_far_stereo_out;
+ int16_t *framebuf_far_stereo_in;
+ int16_t *framebuf_near_stereo_out;
+ int16_t *framebuf_near_stereo_in;
+
+ size_t bytes_of_far_stereo = 0;
+ size_t bytes_of_near_stereo = 0;
+ size_t frames_of_near = 0;
+ size_t frames_of_far = 0;
+ size_t offset_in = 0;
+
+#ifdef SPEEX_AEC
+ int16_t *rec_buf;
+ int16_t *play_buf;
+ int16_t *out_buf;
+ SpeexEchoState *echo_state = NULL;
+ SpeexPreprocessState *preprocess_state = NULL;
+#endif
+
+#ifdef AEC_DUMP
+
+ static FILE* out_fd;
+
+ if(out_fd == NULL) {
+ out_fd=fopen("/data/out_buf.pcm","wb+");
+ if(out_fd == NULL) {
+ ALOGD("DEBUG open /data/out_buf.pcm error =%d ,errno = %d",out_fd,errno);
+ }
+ }
+
+ static FILE* rec_fd;
+ if(rec_fd == NULL) {
+ rec_fd=fopen("/data/rec_buf.pcm","wb+");
+ if(rec_fd == NULL) {
+ ALOGD("DEBUG open /data/rec_buf.pcm error =%d ,errno = %d",rec_fd,errno);
+ }
+ }
+
+ static FILE* play_fd;
+ if(play_fd == NULL) {
+ play_fd=fopen("/data/play_buf.pcm","wb+");
+ if(play_fd == NULL) {
+ ALOGD("DEBUG open /data/play_buf.pcm error =%d ,errno = %d",play_fd,errno);
+ }
+ }
+
+#endif
+
+#ifdef BT_HFP_DUMP_FAR_IN
+ static FILE* fd_far_in;
+#endif
+#ifdef BT_HFP_DUMP_NEAR_OUT
+ static FILE* fd_near_out;
+#endif
+#ifdef BT_HFP_DUMP_NEAR_IN
+ static FILE* fd_near_in;
+#endif
+#ifdef BT_HFP_DUMP_FAR_OUT
+ static FILE* fd_far_out;
+#endif
+
+ adev->pcm_sco_far_in = pcm_open(SCO_PCM_FAR_IN, 0, PCM_IN, &pcm_config_sco);
+ if (adev->pcm_sco_far_in == 0) {
+ ALOGD("%s: failed to allocate memory for PCM far/in", __func__);
+ return NULL;
+ } else if (!pcm_is_ready(adev->pcm_sco_far_in)){
+ pcm_close(adev->pcm_sco_far_in);
+ ALOGD("%s: failed to open PCM far/in", __func__);
+ return NULL;
+ }
+ ALOGD("%s: open sucess pcm_sco_far_in", __func__);
+ adev->pcm_sco_far_out = pcm_open(SCO_PCM_FAR_OUT, 0, PCM_OUT, &pcm_config_sco);
+ if (adev->pcm_sco_far_out == 0) {
+ ALOGD("%s: failed to allocate memory for PCM far/out", __func__);
+ return NULL;
+ } else if (!pcm_is_ready(adev->pcm_sco_far_out)){
+ pcm_close(adev->pcm_sco_far_out);
+ ALOGD("%s: failed to open PCM far/out", __func__);
+ return NULL;
+ }
+ ALOGD("%s: open sucess pcm_sco_far_out", __func__);
+
+ adev->pcm_sco_near_out = pcm_open(SCO_PCM_NEAR_OUT, 0, PCM_OUT, &pcm_config);
+ if (adev->pcm_sco_near_out == 0) {
+ ALOGD("%s: failed to allocate memory for PCM near/out", __func__);
+ return NULL;
+ } else if (!pcm_is_ready(adev->pcm_sco_near_out)){
+ pcm_close(adev->pcm_sco_near_out);
+ ALOGD("%s: failed to open PCM near/out", __func__);
+ return NULL;
+ }
+ ALOGD("%s: open sucess pcm_sco_near_out", __func__);
+
+ adev->pcm_sco_near_in = pcm_open(SCO_PCM_NEAR_IN, 0, PCM_IN, &pcm_config);
+ if (adev->pcm_sco_near_in == 0) {
+ ALOGD("%s: failed to allocate memory for PCM near/in", __func__);
+ return NULL;
+ } else if (!pcm_is_ready(adev->pcm_sco_near_in)){
+ pcm_close(adev->pcm_sco_near_in);
+ ALOGD("%s: failed to open PCM near/in", __func__);
+ return NULL;
+ }
+ ALOGD("%s: open sucess pcm_sco_near_in", __func__);
+
+ // bytes / frame: channels * bytes/sample.
+ // 2 channels * 16 bits/sample = 2 channels * 2 bytes/sample = 4 (stereo), 2 (mono)
+ // We read/write in blocks of 10 ms = samplerate / 100 = 80, 160, or 480 frames.
+
+ frames_of_near = 480;
+ frames_of_far = 80;
+
+ bytes_of_far_stereo = 4 * frames_of_far;
+ bytes_of_near_stereo = 4 * frames_of_near;
+
+ framebuf_far_stereo_out = (int16_t *)malloc(bytes_of_far_stereo);
+ framebuf_far_stereo_in = (int16_t *)malloc(bytes_of_far_stereo);
+ framebuf_near_stereo_out = (int16_t *)malloc(bytes_of_near_stereo);
+ framebuf_near_stereo_in = (int16_t *)malloc(bytes_of_near_stereo);
+
+#ifdef SPEEX_AEC
+ rec_buf = (int16_t *)malloc(bytes_of_far_stereo);
+ play_buf = (int16_t *)malloc(bytes_of_far_stereo);
+ out_buf = (int16_t *)malloc(bytes_of_far_stereo);
+
+if (rec_buf == NULL|| play_buf == NULL || out_buf == NULL) {
+ ALOGD("%s: SPEEX_AEC failed to allocate frames", __func__);
+ return NULL;
+ }
+#endif
+
+ if (framebuf_far_stereo_in == NULL || framebuf_far_stereo_out == NULL ||
+ framebuf_near_stereo_in == NULL || framebuf_near_stereo_out == NULL) {
+ ALOGD("%s: failed to allocate frames", __func__);
+ pcm_close(adev->pcm_sco_near_in);
+ pcm_close(adev->pcm_sco_near_out);
+ pcm_close(adev->pcm_sco_far_in);
+ pcm_close(adev->pcm_sco_far_out);
+ adev->pcm_sco_near_in = 0;
+ adev->pcm_sco_near_out = 0;
+ adev->pcm_sco_far_in = 0;
+ adev->pcm_sco_far_out = 0;
+ return NULL;
+ }
+
+ rc = create_resampler(8000, 48000, 2, RESAMPLER_QUALITY_DEFAULT, NULL, &resampler_8to48);
+ if (rc != 0) {
+ resampler_8to48 = NULL;
+ ALOGD("%s: () failure to create resampler %d", __func__, rc);
+ return NULL;
+ }
+
+ rc = create_resampler(48000, 8000, 2, RESAMPLER_QUALITY_DEFAULT, NULL, &resampler_48to8);
+ if (rc != 0) {
+ resampler_48to8 = NULL;
+ ALOGD("%s: () failure to create resampler %d", __func__, rc);
+ return NULL;
+ }
+#ifdef SPEEX_AEC
+ int frame_size = frames_of_far; //10ms
+ int filter_length = frames_of_far * 6; //60ms
+ int rate = 8000;
+ echo_state = speex_echo_state_init_mc(frame_size, filter_length, 2, 2);
+ frame_size *= 2; // length read each time
+ preprocess_state = speex_preprocess_state_init(frame_size, rate);
+ speex_echo_ctl(echo_state, SPEEX_ECHO_SET_SAMPLING_RATE, &rate);
+ speex_preprocess_ctl(preprocess_state, SPEEX_PREPROCESS_SET_ECHO_STATE, echo_state);
+#endif
+ ALOGD("%s: PCM SCO loop starting", __func__);
+
+ while (adev->hfp_enable && pcm_read(adev->pcm_sco_far_in, framebuf_far_stereo_in, bytes_of_far_stereo) == 0){
+
+#ifdef BT_HFP_DUMP_FAR_IN
+ if(fd_far_in == NULL) {
+ fd_far_in=fopen("/data/fd_far_in.pcm","wb+");
+ if(fd_far_in == NULL) {
+ ALOGD("DEBUG open /data/fd_far_in.pcm error =%d ,errno = %d",fd_far_in,errno);
+ offset_in = 0;
+ }
+ }
+ fwrite(framebuf_far_stereo_in,bytes_of_far_stereo,1,fd_far_in);
+ offset_in += bytes_of_far_stereo;
+ fflush(fd_far_in);
+ if(offset_in >= 10*1024*1024) {
+ fclose(fd_far_in);
+ offset_in = 0;
+ ALOGD("TEST fd_far_in.pcm end");
+ }
+#endif
+
+ stereo_lr_mix(framebuf_far_stereo_in, bytes_of_far_stereo);
+ volume_process(framebuf_far_stereo_in, bytes_of_far_stereo, adev->hfp_volume);
+
+
+ resampler_8to48->resample_from_input(resampler_8to48, (int16_t *)framebuf_far_stereo_in,
+ (size_t *)&frames_of_far,
+ (int16_t *) framebuf_near_stereo_out,
+ (size_t *)&frames_of_near);
+ pcm_write(adev->pcm_sco_near_out, framebuf_near_stereo_out, bytes_of_near_stereo);
+
+#ifdef BT_HFP_DUMP_NEAR_OUT
+ if(fd_near_out == NULL) {
+ fd_near_out=fopen("/data/fd_near_out.pcm","wb+");
+ if(fd_near_out == NULL) {
+ ALOGD("DEBUG open /data/fd_near_out.pcm error =%d ,errno = %d",fd_near_out,errno);
+ offset_in = 0;
+ }
+ }
+ fwrite(framebuf_near_stereo_out,bytes_of_near_stereo,1,fd_near_out);
+ offset_in += bytes_of_near_stereo;
+ fflush(fd_near_out);
+ if(offset_in >= 10*1024*1024) {
+ fclose(fd_near_out);
+ offset_in = 0;
+ ALOGD("TEST fd_near_out.pcm end");
+ }
+#endif
+
+ pcm_read(adev->pcm_sco_near_in, framebuf_near_stereo_in, bytes_of_near_stereo);
+
+#ifdef BT_HFP_DUMP_NEAR_IN
+ if(fd_near_in == NULL) {
+ fd_near_in=fopen("/data/fd_near_in.pcm","wb+");
+ if(fd_near_in == NULL) {
+ ALOGD("DEBUG open /data/fd_near_in.pcm error =%d ,errno = %d",fd_near_in,errno);
+ offset_in = 0;
+ }
+ }
+ fwrite(framebuf_near_stereo_in,bytes_of_near_stereo,1,fd_near_in);
+ offset_in += bytes_of_near_stereo;
+ fflush(fd_near_in);
+ if(offset_in >= 10*1024*1024) {
+ fclose(fd_near_in);
+ offset_in = 0;
+ ALOGD("TEST fd_near_in.pcm end");
+ }
+#endif
+
+ resampler_48to8->resample_from_input(resampler_48to8, (int16_t *)framebuf_near_stereo_in,
+ (size_t *)&frames_of_near,
+ (int16_t *)framebuf_far_stereo_out,
+ (size_t *)&frames_of_far);
+ //stereo_ltor(framebuf_far_stereo_out, bytes_of_far_stereo);
+ //stereo_rtol(framebuf_far_stereo_out, bytes_of_far_stereo);
+ stereo_lr_mix(framebuf_far_stereo_out, bytes_of_far_stereo);
+
+#ifdef BT_HFP_DUMP_FAR_OUT
+ if(fd_far_out == NULL) {
+ fd_far_out=fopen("/data/fd_far_out.pcm","wb+");
+ if(fd_far_out == NULL) {
+ ALOGD("DEBUG open /data/fd_far_out.pcm error =%d ,errno = %d",fd_far_out,errno);
+ offset_in = 0;
+ }
+ }
+ fwrite(framebuf_far_stereo_out,bytes_of_far_stereo,1,fd_far_out);
+ offset_in += bytes_of_far_stereo;
+ fflush(fd_far_out);
+ if(offset_in >= 10*1024*1024) {
+ fclose(fd_far_out);
+ offset_in = 0;
+ ALOGD("TEST fd_far_out.pcm end");
+ }
+#endif
+
+ volume_process(framebuf_far_stereo_out, bytes_of_far_stereo, adev->hfp_volume);
+
+#ifdef SPEEX_AEC
+ play_buf = framebuf_far_stereo_in;
+ rec_buf = framebuf_far_stereo_out;
+ speex_echo_cancellation(echo_state, rec_buf, play_buf, out_buf);
+ speex_preprocess_run(preprocess_state, out_buf);
+ pcm_write(adev->pcm_sco_far_out, out_buf, bytes_of_far_stereo);
+#else
+ pcm_write(adev->pcm_sco_far_out, framebuf_far_stereo_out, bytes_of_far_stereo);
+#endif
+
+#ifdef AEC_DUMP
+ if(rec_fd)
+ fwrite(rec_buf, bytes_of_far_stereo,1,rec_fd);
+ if(play_fd)
+ fwrite(play_buf, bytes_of_far_stereo,1,play_fd);
+ if(out_fd)
+ fwrite(out_buf, bytes_of_far_stereo,1,out_fd);
+#endif
+
+ }
+
+ hfp_status = false;
+
+#ifdef SPEEX_AEC
+ // Destroys an echo canceller state
+ speex_echo_state_destroy(echo_state);
+ speex_preprocess_state_destroy(preprocess_state);
+#endif
+
+ pcm_close(adev->pcm_sco_near_in);
+ pcm_close(adev->pcm_sco_near_out);
+ pcm_close(adev->pcm_sco_far_in);
+ pcm_close(adev->pcm_sco_far_out);
+
+ adev->pcm_sco_near_in = 0;
+ adev->pcm_sco_near_out = 0;
+ adev->pcm_sco_far_in = 0;
+ adev->pcm_sco_far_out = 0;
+ if(framebuf_far_stereo_in){
+ free(framebuf_far_stereo_in);
+ framebuf_far_stereo_in = NULL;
+ }
+ if(framebuf_far_stereo_out){
+ free(framebuf_far_stereo_out);
+ framebuf_far_stereo_out = NULL;
+ }
+ if(framebuf_near_stereo_in){
+ free(framebuf_near_stereo_in);
+ framebuf_near_stereo_in = NULL;
+ }
+ if(framebuf_near_stereo_out){
+ free(framebuf_near_stereo_out);
+ framebuf_near_stereo_out = NULL;
+ }
+#ifdef AEC_DUMP
+ if(rec_fd){
+ fflush(rec_fd);
+ fclose(rec_fd);
+ rec_fd = NULL;
+ }
+ if(play_fd){
+ fflush(play_fd);
+ fclose(play_fd);
+ play_fd = NULL;
+ }
+ if(out_fd){
+ fflush(out_fd);
+ fclose(out_fd);
+ out_fd = NULL;
+ }
+#endif
+
+#ifdef SPEEX_AEC
+ if(rec_buf){
+ free(rec_buf);
+ rec_buf = NULL;
+ }
+ if(play_buf){
+ free(play_buf);
+ play_buf = NULL;
+ }
+ if(out_buf){
+ free(out_buf);
+ out_buf = NULL;
+ }
+#endif
+ adev->hfp_sco_thread = 0;
+ ALOGD("%s: HFP PCM SCO loop terminated", __func__);
+ return NULL;
+}
+
/**
* @brief force_non_hdmi_out_standby
* must be called with hw device outputs list, all out streams, and hw device mutexes locked*/
@@ -143,6 +580,19 @@ static void force_non_hdmi_out_standby(struct audio_device *adev)
}
}
+static void force_out_standby(struct audio_device *adev)
+{
+ enum output_type type;
+ struct stream_out *out;
+
+ for (type = 0; type < OUTPUT_TOTAL; ++type) {
+ out = adev->outputs[type];
+ if (!out)
+ continue;
+ /* This will never recurse more than 2 levels deep. */
+ do_out_standby(out);
+ }
+}
/**
* @brief start_bt_sco */
@@ -659,11 +1109,11 @@ static int start_output_stream(struct stream_out *out)
}
}
- if (out->device & (AUDIO_DEVICE_OUT_SPEAKER |
+ ALOGD("%s out->device=0x%x,out->pcm_device=0x%d,hfp_status=%d\n",__FUNCTION__,out->device,hfp_status?1:0);
+ if ((hfp_status == false) && (out->device & (AUDIO_DEVICE_OUT_SPEAKER |
AUDIO_DEVICE_OUT_WIRED_HEADSET |
AUDIO_DEVICE_OUT_WIRED_HEADPHONE |
- AUDIO_DEVICE_OUT_ALL_SCO)) {
-
+ AUDIO_DEVICE_OUT_ALL_SCO))) {
out->pcm[PCM_CARD] = pcm_open(PCM_CARD, out->pcm_device,
PCM_OUT | PCM_MONOTONIC, &out->config);
if (out->pcm[PCM_CARD] && !pcm_is_ready(out->pcm[PCM_CARD])) {
@@ -1120,8 +1570,11 @@ static void do_out_standby(struct stream_out *out)
adev->voice_api->flush();
}
#endif
- route_pcm_close(PLAYBACK_OFF_ROUTE);
- ALOGD("close device");
+
+ if (hfp_status == false) {
+ route_pcm_close(PLAYBACK_OFF_ROUTE);
+ ALOGD("close device");
+ }
/* Skip resetting the mixer if no output device is active */
if (adev->out_device) {
@@ -2505,13 +2958,24 @@ static int adev_set_parameters(struct audio_hw_device *dev, const char *kvpairs)
val = str_parms_get_str(parms, "hfp_enable", value, sizeof(value));
if (0 <= val) {
if (strcmp(value, "true") == 0) {
- ALOGD("Enable HFP client feature!");
- route_pcm_open(SPEAKER_INCALL_ROUTE);
- start_bt_hfp(adev);
+ ALOGD("Enable HFP client feature!");
+ force_out_standby(adev);//Force shutdown of working sound card
+ hfp_status = true;
+ if (adev->hfp_sco_thread == 0) {
+ adev->hfp_enable = true;
+ pthread_create(&adev->hfp_sco_thread, NULL, &run_hfp_sco, adev);
+ }
+ route_pcm_open(SPEAKER_NORMAL_ROUTE);
+ route_pcm_open(MAIN_MIC_CAPTURE_ROUTE);
} else if (strcmp(value, "false") == 0) {
ALOGD("Disable HFP client feature!");
- stop_bt_hfp(adev);
- route_pcm_open(INCALL_OFF_ROUTE);
+ if (adev->hfp_sco_thread != 0) {
+ adev->hfp_enable = false; // this will cause the thread to exit the main loop and terminate.
+ adev->hfp_sco_thread = 0;
+ }
+ route_pcm_close(PLAYBACK_OFF_ROUTE);
+ route_pcm_close(CAPTURE_OFF_ROUTE);
+
} else {
ALOGE("Unknown HFP client state %s!!!", value);
ret = -EINVAL;
@@ -2549,6 +3013,12 @@ static int adev_set_parameters(struct audio_hw_device *dev, const char *kvpairs)
ret = -EINVAL;
}
}
+ ret = str_parms_get_str(parms, AUDIO_PARAMETER_KEY_HFP_VOLUME, value, sizeof(value));
+ if (ret >= 0) {
+ val = atoi(value);
+ adev->hfp_volume = val * 1.0/15;
+ ALOGD("%s hfp_volume =%lf\n", __func__, adev->hfp_volume);
+ }
}
pthread_mutex_unlock(&adev->lock);
@@ -2994,6 +3464,8 @@ static int adev_open(const hw_module_t* module, const char* name,
* selection is always applied by select_devices() */
adev->hdmi_drv_fd = -1;
+ adev->hfp_enable = false;
+ adev->hfp_volume = 1.0f;//add volume control
#ifdef AUDIO_3A
adev->voice_api = NULL;
#endif
diff --git a/tinyalsa_hal/audio_hw.h b/tinyalsa_hal/audio_hw.h
index a81c85f..a8b3a84 100755
--- a/tinyalsa_hal/audio_hw.h
+++ b/tinyalsa_hal/audio_hw.h
@@ -317,6 +317,15 @@ struct audio_device {
rk_process_api* voice_api;
#endif
+ /*for bt hfp*/
+ pthread_t hfp_sco_thread;
+ pthread_mutex_t hfp_sco_thread_lock;
+ struct pcm *pcm_sco_far_in;
+ struct pcm *pcm_sco_far_out;
+ struct pcm *pcm_sco_near_in;
+ struct pcm *pcm_sco_near_out;
+ bool hfp_enable;
+ float hfp_volume;
};
struct stream_out {