Created
January 20, 2018 02:16
-
-
Save lbdroid/3f91ce6ccbd5b5cc5227eb31309e17bc to your computer and use it in GitHub Desktop.
usb audio hal for hfp-client
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff --git a/modules/usbaudio/audio_hal.c b/modules/usbaudio/audio_hal.c | |
index e93396f..0b66481 100644 | |
--- a/modules/usbaudio/audio_hal.c | |
+++ b/modules/usbaudio/audio_hal.c | |
@@ -14,8 +14,8 @@ | |
* limitations under the License. | |
*/ | |
-#define LOG_TAG "modules.usbaudio.audio_hal" | |
-/*#define LOG_NDEBUG 0*/ | |
+#define LOG_TAG "modules.usbaudio_hal.hikey" | |
+//#define LOG_NDEBUG 0 | |
#include <errno.h> | |
#include <inttypes.h> | |
@@ -31,6 +31,7 @@ | |
#include <cutils/properties.h> | |
#include <hardware/audio.h> | |
+//#include "audio_alsaops.h" | |
#include <hardware/audio_alsaops.h> | |
#include <hardware/hardware.h> | |
@@ -38,17 +39,83 @@ | |
#include <tinyalsa/asoundlib.h> | |
+//#include <webrtc/modules/audio_processing/include/audio_processing.h> | |
+//#include <webrtc/modules/include/module_common_types.h> | |
+ | |
#include <audio_utils/channels.h> | |
+#include <audio_utils/resampler.h> | |
#include "alsa_device_profile.h" | |
#include "alsa_device_proxy.h" | |
#include "alsa_logging.h" | |
+//#include "proxy.h" | |
+//#include "profile.h" | |
+ | |
+#define AUDIO_PARAMETER_HFP_ENABLE "hfp_enable" | |
+#define AUDIO_PARAMETER_HFP_SET_SAMPLING_RATE "hfp_set_sampling_rate" | |
+#define AUDIO_PARAMETER_KEY_HFP_VOLUME "hfp_volume" | |
+#define AUDIO_PARAMETER_HFP_VOL_MIXER_CTL "hfp_vol_mixer_ctl" | |
+#define AUDIO_PARAMATER_HFP_VALUE_MAX 128 | |
+#define AUDIO_PARAMETER_KEY_HFP_MIC_VOLUME "hfp_mic_volume" | |
+ | |
#define DEFAULT_INPUT_BUFFER_SIZE_MS 20 | |
+/* TODO | |
+ * For multi-channel audio (> 2 channels)... | |
+ * | |
+ * This USB device is going to be (as far as Android is concerned) configured as a STEREO | |
+ * (2-channel) device, but could physically be connected to as many as 8 (7.1) speakers. | |
+ * | |
+ * When connecting to more than 2 speakers, the relationships between all the speakers | |
+ * has to be managed manually, that means interleaving the multiple streams via duplication | |
+ * and possibly performing operations on those individual streams. | |
+ * | |
+ * For instance, when we receive a STEREO stream, it will be interleaved as LRLRLRLRLR | |
+ * or 0101010101. When we play a DUAL STEREO 4-channel stream, it will be interleaved | |
+ * as 0123012301230123 where for each sample, 2==0 and 3==1. We have to copy the stream | |
+ * manually. If our speaker topology includes CENTER and/or BASE, those two channels | |
+ * will be MONO-blends of L and R, and in the case of BASE, also LPF. | |
+ * | |
+ * The USB ALSA mixer controls provide individual volume controls for all 8 output | |
+ * channels, and all input channels. It also is able to playback input source directly to | |
+ * output without having to transfer it up the USB and back, and is able to configure | |
+ * playback channel mappings. This means that FM radio playback can be done as simply as | |
+ * setting "Line Playback Switch" to 1. | |
+ * | |
+ * # tinymix -D 1 -a | |
+ * Mixer name: 'USB Sound Device' | |
+ * Number of controls: 16 | |
+ * ctl type num name value | |
+ * range/values | |
+ * 0 INT 8 Playback Channel Map 0 0 0 0 0 0 0 0 (dsrange 0->36) | |
+ * 1 INT 2 Capture Channel Map 0 0 (dsrange 0->36) | |
+ * 2 BOOL 1 Mic Playback Switch On | |
+ * 3 INT 2 Mic Playback Volume 8065 8065 (dsrange 0->8065) | |
+ * 4 BOOL 1 Line Playback Switch On | |
+ * 5 INT 2 Line Playback Volume 6144 6144 (dsrange 0->8065) | |
+ * 6 BOOL 1 Speaker Playback Switch On | |
+ * 7 INT 8 Speaker Playback Volume 197 100 100 100 0 0 0 0 (dsrange 0->197) | |
+ * 8 BOOL 1 Mic Capture Switch On | |
+ * 9 INT 2 Mic Capture Volume 4096 4096 (dsrange 0->6928) | |
+ * 10 BOOL 1 Line Capture Switch Off | |
+ * 11 INT 2 Line Capture Volume 4096 4096 (dsrange 0->6928) | |
+ * 12 BOOL 1 IEC958 In Capture Switch Off | |
+ * 13 BOOL 1 PCM Capture Switch On | |
+ * 14 INT 2 PCM Capture Volume 4096 4096 (dsrange 0->6928) | |
+ * 15 ENUM 1 PCM Capture Source >Mic Line IEC958 In Mixer | |
+ * | |
+ * We can use AudioManager.setParameters() to feed configurations to this HAL, and either | |
+ * some form of persistent storage (persistent system property? Or load on boot from | |
+ * a boot completed receiver?) | |
+ */ | |
+ | |
+ | |
/* Lock play & record samples rates at or above this threshold */ | |
#define RATELOCK_THRESHOLD 96000 | |
+//namespace webrtc { | |
+ | |
struct audio_device { | |
struct audio_hw_device hw_device; | |
@@ -69,6 +136,16 @@ struct audio_device { | |
bool mic_muted; | |
bool standby; | |
+ | |
+ pthread_t sco_thread; | |
+ pthread_mutex_t sco_thread_lock; | |
+ | |
+ struct pcm *sco_pcm_far_in; | |
+ struct pcm *sco_pcm_far_out; | |
+ struct pcm *sco_pcm_near_in; | |
+ struct pcm *sco_pcm_near_out; | |
+ | |
+ bool terminate_sco; | |
}; | |
struct stream_lock { | |
@@ -1102,11 +1179,360 @@ static void adev_close_input_stream(struct audio_hw_device *hw_dev, | |
free(stream); | |
} | |
+void stereo_to_mono(int16_t *stereo, int16_t *mono, size_t samples){ | |
+ // Converts interleaved stereo into mono by discarding second channel | |
+ int i; | |
+ for (i=0; i<samples; i++) | |
+ mono[i] = stereo[2*i]; | |
+} | |
+ | |
+void* runsco(void * args) { | |
+ int16_t *framebuf_far_stereo; | |
+ int16_t *framebuf_far_mono; | |
+ int16_t *framebuf_near_stereo; | |
+ int16_t *framebuf_near_mono; | |
+ | |
+ size_t block_len_bytes_far_mono = 0; | |
+ size_t block_len_bytes_far_stereo = 0; | |
+ size_t block_len_bytes_near_mono = 0; | |
+ size_t block_len_bytes_near_stereo = 0; | |
+ | |
+ size_t frames_per_block_near = 0; | |
+ size_t frames_per_block_far = 0; | |
+ | |
+ int rc; | |
+ int readsamples = 0; | |
+ struct audio_device * adev = (struct audio_device *)args; | |
+ struct resampler_itfe *resampler_8to48; | |
+ struct resampler_itfe *resampler_48to8; | |
+ | |
+ struct pcm_config bt_config = { | |
+ .channels = 2, | |
+ .rate = 8000, | |
+ .format = PCM_FORMAT_S16_LE, | |
+ .period_size = 1024, | |
+ .period_count = 4, | |
+ .start_threshold = 0, // 0's mean default | |
+ .silence_threshold = 0, | |
+ .stop_threshold = 0, | |
+ }; | |
+ | |
+ struct pcm_config usb_config = { | |
+ .channels = 2, | |
+ .rate = 48000, | |
+ .format = PCM_FORMAT_S16_LE, | |
+ .period_size = 1024, | |
+ .period_count = 2, | |
+ .start_threshold = 0, | |
+ .silence_threshold = 0, | |
+ .stop_threshold = 0, | |
+ }; | |
+ | |
+ adev->sco_pcm_far_in = pcm_open(0, 0, PCM_IN, &bt_config); | |
+ if (adev->sco_pcm_far_in == 0) { | |
+ ALOGD("%s: failed to allocate memory for PCM far/in", __func__); | |
+ return NULL; | |
+ } else if (!pcm_is_ready(adev->sco_pcm_far_in)){ | |
+ pcm_close(adev->sco_pcm_far_in); | |
+ ALOGD("%s: failed to open PCM far/in", __func__); | |
+ return NULL; | |
+ } | |
+ | |
+ adev->sco_pcm_far_out = pcm_open(0, 0, PCM_OUT, &bt_config); | |
+ if (adev->sco_pcm_far_out == 0) { | |
+ ALOGD("%s: failed to allocate memory for PCM far/out", __func__); | |
+ return NULL; | |
+ } else if (!pcm_is_ready(adev->sco_pcm_far_out)){ | |
+ pcm_close(adev->sco_pcm_far_out); | |
+ ALOGD("%s: failed to open PCM far/out", __func__); | |
+ return NULL; | |
+ } | |
+ | |
+ adev->sco_pcm_near_in = pcm_open(1, 0, PCM_IN, &usb_config); | |
+ if (adev->sco_pcm_near_in == 0) { | |
+ ALOGD("%s: failed to allocate memory for PCM near/in", __func__); | |
+ return NULL; | |
+ } else if (!pcm_is_ready(adev->sco_pcm_near_in)){ | |
+ pcm_close(adev->sco_pcm_near_in); | |
+ ALOGD("%s: failed to open PCM near/in", __func__); | |
+ return NULL; | |
+ } | |
+ | |
+ adev->sco_pcm_near_out = pcm_open(1, 0, PCM_OUT, &usb_config); | |
+ if (adev->sco_pcm_near_out == 0) { | |
+ ALOGD("%s: failed to allocate memory for PCM near/out", __func__); | |
+ return NULL; | |
+ } else if (!pcm_is_ready(adev->sco_pcm_near_out)){ | |
+ pcm_close(adev->sco_pcm_near_out); | |
+ ALOGD("%s: failed to open PCM near/out", __func__); | |
+ return NULL; | |
+ } | |
+ | |
+ // 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_per_block_near = 480; | |
+ frames_per_block_far = 80; | |
+ | |
+ block_len_bytes_far_mono = 2 * frames_per_block_far; // bytes/frame * frames | |
+ block_len_bytes_far_stereo = 4 * frames_per_block_far; | |
+ block_len_bytes_near_mono = 2 * frames_per_block_near; | |
+ block_len_bytes_near_stereo = 4 * frames_per_block_near; | |
+ | |
+ framebuf_far_stereo = (int16_t *)malloc(block_len_bytes_far_stereo); | |
+ framebuf_far_mono = (int16_t *)malloc(block_len_bytes_far_mono); | |
+ framebuf_near_stereo = (int16_t *)malloc(block_len_bytes_near_stereo); | |
+ framebuf_near_mono = (int16_t *)malloc(block_len_bytes_near_mono); | |
+ if (framebuf_far_stereo == NULL || framebuf_near_stereo == NULL) { | |
+ ALOGD("%s: failed to allocate frames", __func__); | |
+ pcm_close(adev->sco_pcm_near_in); | |
+ pcm_close(adev->sco_pcm_near_out); | |
+ pcm_close(adev->sco_pcm_far_in); | |
+ pcm_close(adev->sco_pcm_far_out); | |
+ adev->sco_pcm_near_in = 0; | |
+ adev->sco_pcm_near_out = 0; | |
+ adev->sco_pcm_far_in = 0; | |
+ adev->sco_pcm_far_out = 0; | |
+ return NULL; | |
+ } | |
+ | |
+ rc = create_resampler(8000, 48000, 1, RESAMPLER_QUALITY_DEFAULT, NULL, &resampler_8to48); | |
+ if (rc != 0) { | |
+ resampler_8to48 = NULL; | |
+ ALOGD("%s: echo_reference_write() failure to create resampler %d", __func__, rc); | |
+ return NULL; | |
+ } | |
+ | |
+ rc = create_resampler(48000, 8000, 1, RESAMPLER_QUALITY_DEFAULT, NULL, &resampler_48to8); | |
+ if (rc != 0) { | |
+ resampler_48to8 = NULL; | |
+ ALOGD("%s: echo_reference_write() failure to create resampler %d", __func__, rc); | |
+ return NULL; | |
+ } | |
+ | |
+ ALOGD("%s: PCM loop starting", __func__); | |
+ | |
+ while (!adev->terminate_sco && pcm_read(adev->sco_pcm_far_in, framebuf_far_stereo, frames_per_block_far) == 0){ | |
+ | |
+ ALOGD("%s: Looping...", __func__); | |
+ | |
+ stereo_to_mono(framebuf_far_stereo, framebuf_far_mono, frames_per_block_far); | |
+ | |
+ //TODO AnalyzeReverseStream | |
+ | |
+ resampler_8to48->resample_from_input(resampler_8to48, (int16_t *)framebuf_far_mono, (size_t *)&frames_per_block_far, (int16_t *) framebuf_near_mono, (size_t *)&frames_per_block_near); | |
+ | |
+ adjust_channels(framebuf_near_mono, 1, framebuf_near_stereo, 2, 2, block_len_bytes_near_mono); | |
+ | |
+ pcm_write(adev->sco_pcm_near_out, framebuf_near_stereo, frames_per_block_near); | |
+ pcm_read(adev->sco_pcm_near_in, framebuf_near_stereo, frames_per_block_near); | |
+ | |
+ stereo_to_mono(framebuf_near_stereo, framebuf_near_mono, frames_per_block_near); | |
+ | |
+ resampler_48to8->resample_from_input(resampler_48to8, (int16_t *)framebuf_near_mono, (size_t *)&frames_per_block_near, (int16_t *)framebuf_far_mono, (size_t *)&frames_per_block_far); | |
+ | |
+ //TODO ProcessStream | |
+ | |
+ adjust_channels(framebuf_far_mono, 1, framebuf_far_stereo, 2, 2, block_len_bytes_far_mono); | |
+ | |
+ pcm_write(adev->sco_pcm_far_out, framebuf_far_stereo, frames_per_block_far); | |
+ | |
+/* | |
+ * ... What to do about clock drift between the BT and USB sound cards? Maybe add buffers on one of the devices and add or drop samples occasionally | |
+ * to keep the buffers at around 50% full. Might make most sense to do these manipulations on the BT side of AEC, that way the AEC will get clean | |
+ * constant data in and out. | |
+ * ** This will require reversing the streams. The loop control will be a read from the USB card, then convert and write that to ProcessStream, | |
+ * and then write it to an output buffer before sending it to BT, then buffered read from the bluetooth, into AnalyzeReverseStream, and finally | |
+ * write to the USB. We fill and empty the BT buffers from another thread. If either of them goes too far away from 50%, drop or fill samples to | |
+ * bring it back in line, or even better squeeze or stretch the data. | |
+ */ | |
+ } | |
+ | |
+ ALOGD("%s: PCM loop terminated", __func__); | |
+ | |
+ // We're done, close the PCM's and return. | |
+ pcm_close(adev->sco_pcm_near_in); | |
+ pcm_close(adev->sco_pcm_near_out); | |
+ pcm_close(adev->sco_pcm_far_in); | |
+ pcm_close(adev->sco_pcm_far_out); | |
+ | |
+ adev->sco_pcm_near_in = 0; | |
+ adev->sco_pcm_near_out = 0; | |
+ adev->sco_pcm_far_in = 0; | |
+ adev->sco_pcm_far_out = 0; | |
+ | |
+ adev->sco_thread = 0; | |
+ | |
+ return NULL; | |
+ | |
+ /* | |
+ // Our frame manager | |
+ AudioFrame frame; | |
+ frame.num_channels_ = 1; | |
+ frame.sample_rate_hz_ = 8000; | |
+ frame.samples_per_channel_ = 8000/100; | |
+ | |
+ // Get the size of our frames | |
+ const size_t frameLength = frame.samples_per_channel_*1; | |
+ | |
+ AudioProcessing* apm = AudioProcessing::Create(); | |
+ // | |
+// apm->set_sample_rate_hz(8000); // Super-wideband processing. | |
+ // | |
+ // // Mono capture and stereo render. | |
+// apm->set_num_channels(1, 1); | |
+// apm->set_num_reverse_channels(1); | |
+ // | |
+ apm->high_pass_filter()->Enable(true); | |
+ // | |
+ //apm->echo_cancellation()->set_suppression_level( EchoCancellation::SuppressionLevel::kHighSuppression ); | |
+ apm->echo_cancellation()->enable_drift_compensation( false ); | |
+ apm->echo_cancellation()->Enable( true ); | |
+ // | |
+ apm->noise_suppression()->set_level( NoiseSuppression::Level::kHigh ); | |
+ apm->noise_suppression()->Enable( true ); | |
+ // | |
+ apm->gain_control()->set_analog_level_limits( 0, 255 ); | |
+ apm->gain_control()->set_mode( GainControl::Mode::kAdaptiveDigital ); | |
+ apm->gain_control()->Enable( true ); | |
+ // | |
+ // apm->voice_detection()->Enable(true); | |
+ // | |
+ // // Start a voice call... | |
+ | |
+// while( fread(frame._payloadData, sizeof( int16_t ), frameLength, infile )==frameLength ) | |
+ while (fread(frame.data_, sizeof(int16_t), frameLength, infile) == frameLength) | |
+ { | |
+ //apm->set_stream_delay_ms( 0 ); | |
+ | |
+//TODO: feed it an input frame read from BT | |
+ apm->AnalyzeReverseStream( &frame ); | |
+//TODO: write that frame to speakers | |
+// | |
+ // | |
+ // // ... Render frame arrives bound for the audio HAL ... | |
+ // | |
+ // // ... Capture frame arrives from the audio HAL ... | |
+ // // Call required set_stream_ functions. | |
+ // apm->gain_control()->set_stream_analog_level(analog_level); | |
+ // | |
+ | |
+//TODO: delay probably should be 2*out_get_latency | |
+ apm->set_stream_delay_ms( 300 ); | |
+ | |
+//TODO: feed it a frame read from microphone | |
+ int err = apm->ProcessStream( &frame ); | |
+ | |
+ | |
+ fprintf( stdout, "Output %i\n", err ); | |
+ // | |
+ // // Call required stream_ functions. | |
+ // analog_level = apm->gain_control()->stream_analog_level(); | |
+ // has_voice = apm->stream_has_voice(); | |
+ | |
+//TODO: write this to BT | |
+// fwrite( frame._payloadData, sizeof( int16_t ), frameLength, outfile ); | |
+ fwrite(frame.data_, sizeof(int16_t), frameLength, outfile); | |
+ } | |
+ | |
+ // | |
+ // // Repeate render and capture processing for the duration of the call... | |
+ // // Start a new call... | |
+ // apm->Initialize(); | |
+ // | |
+ // // Close the application... | |
+ //AudioProcessing::Destroy( apm ); | |
+ delete apm; | |
+ apm = NULL; | |
+ | |
+ fclose( infile ); | |
+ fclose( outfile ); | |
+*/ | |
+} | |
+ | |
/* | |
* ADEV Functions | |
*/ | |
static int adev_set_parameters(struct audio_hw_device *hw_dev, const char *kvpairs) | |
{ | |
+/* TODO | |
+ * Have to handle hfp related parameters, expecially hfp_enable and hfp_set_sampling_rate. | |
+ * If the parameter is hfp_enable, open and lock both audio devices, and start a thread (2 threads?) | |
+ * to read/write the audio streams and feed the data through webrtc. | |
+ * | |
+ * If its hfp_set_sampling_rate, we need to reset the streams, maybe kill and restart the | |
+ * thread with new parameters? | |
+ * | |
+ * I think we can use functions out_write and in_read to I/O with the USB card. | |
+ * Can use tinyalsa examples https://github.com/tinyalsa/tinyalsa/tree/master/examples for | |
+ * I/O with BT. | |
+ * Do reads and writes in 10 ms blocks... which would be 80 or 160 frames at a time for 8k or 16k | |
+ * sample rates. | |
+ * | |
+ * Since the i2s2 is in stereo with a mono stream, we will I/O it *as* stereo, but will convert | |
+ * the data from stereo to mono immediately after reading, and from mono to stereo immediately | |
+ * before writing it back. | |
+ * | |
+ * *** How do I get my hands on the input and output streams needed for the out_write and in_read functions? | |
+ * ---> Looks like other functions are casting the audio_hw_device to audio_device, which has input | |
+ * and output stream LISTS as members. Being that this is a POINTER to an audio_hw_device, and that | |
+ * audio_hw_device is the *first* member of audio_device, this makes sense. | |
+ * | |
+ * *** Identifying the alsa device corresponding to BT... the hw_dev parameter here will correspond | |
+ * to the USB sound card. As an add-on card, it should typically be enumerated as device=1, but | |
+ * occasionally have observed it enumerate as device=0. We can therefore assume that the BT device | |
+ * will be ((this_device + 1) % 2). | |
+ * | |
+ * *** LIMITATIONS/ISSUES: If there are multiple USB sound cards plugged in at the same time, | |
+ * they will all try to connect to the BT. This is obviously a very bad thing, although unlikely | |
+ * to happen since that would be a stupid thing to do. HOWEVER, we may be able to mitigate this | |
+ * problem by checking if there is both an input and output stream associated with this device. | |
+ * If not, then don't set up the BT. That idea may not work though, if no device has streams | |
+ * set up at all, or if the established streams are useless to us -- we may need to set up our own. | |
+ * | |
+ * *** Sample rate: The USB device will be locked to 48 kHz sample rate. The i2s will be 8 kHz, or if WB | |
+ * up to 16 kHz. We must resample between the two devices. Good news is that we will be operating at | |
+ * whole number multiples. 8000 * 6 = 48000, 16000 * 3 = 48000. | |
+ */ | |
+ | |
+ ALOGD("%s: kvpairs: %s", __func__, kvpairs); | |
+ | |
+ struct audio_device * adev = (struct audio_device *)hw_dev; | |
+ char value[32]; | |
+ int ret, val = 0; | |
+ struct str_parms *parms; | |
+ | |
+ parms = str_parms_create_str(kvpairs); | |
+ | |
+ ret = str_parms_get_str(parms, AUDIO_PARAMETER_HFP_SET_SAMPLING_RATE, value, sizeof(value)); | |
+ if (ret >= 0) { | |
+ val = atoi(value); | |
+ // TODO: set the audio sample rate to 'val' | |
+ } | |
+ | |
+ ret = str_parms_get_str(parms, AUDIO_PARAMETER_HFP_ENABLE, value, sizeof(value)); | |
+ if (ret >= 0) { | |
+ pthread_mutex_lock(&adev->sco_thread_lock); | |
+ if (strcmp(value, "true") == 0){ | |
+ if (adev->sco_thread == 0) { | |
+ adev->terminate_sco = false; | |
+ pthread_create(&adev->sco_thread, NULL, &runsco, adev); | |
+ } | |
+ } else { | |
+ if (adev->sco_thread != 0) { | |
+ adev->terminate_sco = true; // this will cause the thread to exit the main loop and terminate. | |
+ adev->sco_thread = 0; | |
+ } | |
+ } | |
+ pthread_mutex_unlock(&adev->sco_thread_lock); | |
+ } | |
+ | |
+ ret = str_parms_get_str(parms, AUDIO_PARAMETER_KEY_HFP_VOLUME, value, sizeof(value)); | |
+ if (ret >= 0) { | |
+ val = atoi(value); | |
+ // TODO: set the HFP volume to 'val' | |
+ } | |
+ | |
return 0; | |
} | |
@@ -1259,3 +1685,5 @@ struct audio_module HAL_MODULE_INFO_SYM = { | |
.methods = &hal_module_methods, | |
}, | |
}; | |
+ | |
+//} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment