blob: 1e9210b86eac617e51413cc1ce1bf44998d0a6a1 [file] [log] [blame]
// Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>
#include <set>
#include <string>
#include <fftw3.h>
#include <alsa/asoundlib.h>
#include "common.h"
#include "alsa_client.h"
#include "tone_generators.h"
/*
* The number of upper and lower frequency
* bins around the center frequncy for matched filter
*/
const int lo_bandwidth = 3;
const int hi_bandwidth = 3;
using autotest_client::audio::AlsaPlaybackClient;
using autotest_client::audio::AlsaCaptureClient;
using autotest_client::audio::SampleFormat;
using autotest_client::audio::CircularBuffer;
using autotest_client::audio::MultiToneGenerator;
using autotest_client::audio::AudioFunTestConfig;
using std::vector;
#ifndef max
template <typename T>
T max(T a, T b) { return a > b ? a : b; }
#endif
#ifndef min
template <typename T>
T min(T a, T b) { return a < b ? a : b; }
#endif
static struct option long_options[] = {
{"playback-device", 1, NULL, 'o'},
{"capture-device", 1, NULL, 'i'},
{"tone-length", 1, NULL, 'l'},
{"format", 1, NULL, 'f'},
{"sample-rate", 1, NULL, 'r'},
{"start-volume", 1, NULL, 's'},
{"end-volume", 1, NULL, 'e'},
{"channels", 1, NULL, 'c'},
{"active-channels", 1, NULL, 'a'},
{"fftsize", 1, NULL, 'n'},
{"help", 0, NULL, 'h'},
{"verbose", 0, NULL, 'v'}
};
void ParseActiveChannels(char* arg, std::set<int>* channel_list) {
char* tok = strtok(arg, ",");
do {
channel_list->insert(atoi(tok));
} while ((tok = strtok(NULL, ",")) != NULL);
}
SampleFormat ParseFormat(const char* arg) {
if (strcmp(arg, "u8") == 0) {
return SampleFormat(SampleFormat::kPcmU8);
} else if (strcmp(arg, "s16") == 0) {
return SampleFormat(SampleFormat::kPcmS16);
} else if (strcmp(arg, "s24") == 0) {
return SampleFormat(SampleFormat::kPcmS24);
} else if (strcmp(arg, "s32") == 0) {
return SampleFormat(SampleFormat::kPcmS32);
} else {
return SampleFormat(SampleFormat::kPcmInvalid);
}
}
bool ParseOptions(int argc, char* argv[], AudioFunTestConfig* config) {
int opt = 0;
int optindex = -1;
while ((opt = getopt_long(argc, argv, "o:i:l:f:r:s:e:c:a:n:vh",
long_options,
&optindex)) != -1) {
switch (opt) {
case 'o':
config->playback_alsa_device = std::string(optarg);
break;
case 'i':
config->capture_alsa_device = std::string(optarg);
break;
case 'l':
config->tone_length_sec = atof(optarg);
break;
case 'f':
config->format = ParseFormat(optarg);
break;
case 'r':
config->sample_rate = atoi(optarg);
break;
case 's':
config->start_volume = atof(optarg);
break;
case 'e':
config->end_volume = atof(optarg);
break;
case 'c':
config->channels = atoi(optarg);
break;
case 'a':
ParseActiveChannels(optarg, &config->active_channels);
break;
case 'n':
config->fftsize = atoi(optarg);
break;
case 'v':
config->verbose = true;
break;
case 'h':
return false;
default:
assert(false);
};
}
// Avoid overly short tones.
if (config->tone_length_sec < 0.01) {
fprintf(stderr, "Tone length too short. Must be 0.01s or greater.\n");
return false;
}
// Normalize the active channel set to explicitly list all channels.
if (config->active_channels.empty()) {
for (int i = 0; i < config->channels; ++i) {
config->active_channels.insert(i);
}
}
return true;
}
void PrintUsage(FILE* out, const char* name) {
AudioFunTestConfig default_config;
fprintf(out, "Usage: %s [options]\n", name);
fprintf(out,
"\t-i, --capture-device: "
"Name of alsa device to use (def %s).\n",
default_config.capture_alsa_device.c_str());
fprintf(out,
"\t-o, --playback-device: "
"Name of alsa device to use (def %s).\n",
default_config.playback_alsa_device.c_str());
fprintf(out,
"\t-l, --tone-length: "
"Decimal value of tone length in secs (def %0.2lf).\n",
default_config.tone_length_sec);
fprintf(out,
"\t-f, --format: "
"Sample format {u8, s16, s24, s32} to use "
"when talking to PA (def %s).\n",
default_config.format.to_string());
fprintf(out,
"\t-r, --sample-rate: "
"Sample rate of generated wave in HZ (def %d).\n",
default_config.sample_rate);
fprintf(out,
"\t-s, --start-volume: "
"Decimal value of start volume (def %0.2lf).\n",
default_config.start_volume);
fprintf(out,
"\t-e, --end-volume: "
"Decimal value of end volume (def %0.2lf).\n",
default_config.end_volume);
fprintf(out,
"\t-c, --channels: "
"The number of channels (def %d).\n",
default_config.channels);
fprintf(out,
"\t-n, --fftsize: "
"Longer fftsize has more carriers but longer latency. (def 1024)\n");
fprintf(out,
"\t-a, --active-channels: "
"Comma-separated list of channels to play on. (def all channels)\n");
fprintf(out,
"\t-v, --verbose: "
"Show debugging information.\n");
fprintf(out,
"\t-h, --help: "
"Show this page.\n");
}
void PrintConfig(FILE* out, const AudioFunTestConfig& config) {
fprintf(out, "Config Values:\n");
fprintf(out, "\tCapture Alsa Device: %s\n",
config.capture_alsa_device.c_str());
fprintf(out, "\tPlayback Alsa Device: %s\n",
config.playback_alsa_device.c_str());
fprintf(out, "\tFormat: %s\n", config.format.to_string());
fprintf(out, "\tTone Length (sec): %0.2lf\n", config.tone_length_sec);
fprintf(out, "\tSample Rate (HZ): %d\n", config.sample_rate);
fprintf(out, "\tStart Volume (0-1.0): %0.2lf\n", config.start_volume);
fprintf(out, "\tEnd Volume (0-1.0): %0.2lf\n", config.end_volume);
fprintf(out, "\tChannels: %d\n", config.channels);
fprintf(out, "\tFFTsize: %d\n", config.fftsize);
fprintf(out, "\tActive Channels: ");
for (std::set<int>::const_iterator it = config.active_channels.begin();
it != config.active_channels.end();
++it) {
fprintf(out, "%d ", *it);
}
fprintf(out, "\n");
}
void *PlayToneThreadEntry(void *arg) {
AlsaPlaybackClient *client = static_cast<AlsaPlaybackClient *>(arg);
client->PlayTones();
return 0;
}
void *CaptureThreadEntry(void *arg) {
AlsaCaptureClient *client = static_cast<AlsaCaptureClient *>(arg);
client->Capture();
return 0;
}
struct Carrier {
void InitMatchedFilter(int low, int hi, double *data) {
double mean = 0.0;
double std = 0.0;
lo_bin = low;
hi_bin = hi;
matched_filter.clear();
for (int b = low; b <= hi; ++b) {
matched_filter.push_back(data[b]);
mean += data[b];
std += data[b] * data[b];
}
mean /= matched_filter.size();
std /= matched_filter.size();
std -= mean * mean;
std = sqrt(std);
for (unsigned i = 0; i < matched_filter.size(); ++i) {
matched_filter[i] -= mean;
matched_filter[i] /= std;
}
}
double MatchedFilterConfidence(double *data) {
double confidence = 0.0;
double mean = 0.0;
double std = 0.0;
double power_ratio = 0.0;
for (unsigned i = 0; i < matched_filter.size(); ++i) {
double sample = data[lo_bin + i];
confidence += sample * matched_filter[i];
mean += sample;
std += sample * sample;
}
power_ratio = data[center_bin] / mean;
mean /= matched_filter.size();
std = sqrt(std / matched_filter.size() - mean * mean);
confidence /= std * matched_filter.size();
return power_ratio * confidence;
}
int center_bin;
int lo_bin;
int hi_bin;
vector<double> matched_filter;
};
struct LoopParam {
LoopParam(const AlsaCaptureClient& capture_client,
const double low_cutoff = 1600.0,
const double hi_cutoff = 10000.0) {
num_frames = NumFrames(*capture_client.Buffer(), capture_client.Format(),
capture_client.NumChannel());
num_freq = num_frames / 2 + 1;
num_bin = num_frames / 4;
freq_resol = 2.0 * capture_client.SampRate() / num_frames;
bin_start = ceil(low_cutoff/ freq_resol);
bin_end = ceil(hi_cutoff / freq_resol);
if (bin_end > num_bin) bin_end = num_bin;
num_used_bin = bin_end - bin_start;
carriers.resize(num_used_bin / 2);
for (int i = 0; i < num_used_bin / 2; ++i) {
carriers[i].center_bin = bin_start + 2 * i;
}
target_carrier = 0;
hwparams = capture_client.get_hw_params();
}
bool SetTargetCarrier(int c) {
if (c < 0 ||
static_cast<unsigned>(c) >= carriers.size()) {
return false;
} else {
target_carrier = c;
frequencies.resize(1);
frequencies[0] = carriers[target_carrier].center_bin * freq_resol;
return true;
}
}
double TargetCarrierConfidence(double *data) {
return carriers[target_carrier].MatchedFilterConfidence(data);
}
int TargetCarrierCenterBin() {
return carriers[target_carrier].center_bin;
}
void Print(FILE* fp) {
snd_output_t *log;
fprintf(fp, "LoopParam::Print()\n");
fprintf(fp, " num_frames = %d\n", num_frames);
fprintf(fp, " num_freq = %d\n", num_freq);
fprintf(fp, " num_bin = %d\n", num_bin);
fprintf(fp, " freq_resol = %f\n", freq_resol);
fprintf(fp, " bin_start = %d\n", bin_start);
fprintf(fp, " bin_end = %d\n", bin_end);
fprintf(fp, " num_used_bin = %d\n", num_used_bin);
fprintf(fp, " targte_carrier = %d\n", target_carrier);
fprintf(fp, " carriers = {\n");
for (unsigned int i = 0; i < carriers.size(); ++i) {
fprintf(fp, " %d: @%d(%.f) (%d, %d): {",
i, carriers[i].center_bin, carriers[i].center_bin * freq_resol,
carriers[i].lo_bin, carriers[i].hi_bin);
for (unsigned int j = 0; j < carriers[i].matched_filter.size(); ++j) {
fprintf(fp, " %d:%.3f",
carriers[i].lo_bin + j,
carriers[i].matched_filter[j]);
}
fprintf(fp, "}\n");
}
fprintf(fp, " }\n");
fprintf(fp, "hw_params =\n");
snd_output_stdio_attach(&log, fp, 0);
snd_pcm_hw_params_dump(hwparams, log);
}
int num_frames;
int num_freq;
int num_bin;
double freq_resol;
int bin_start;
int bin_end;
int num_used_bin;
vector<double> frequencies;
vector<struct Carrier> carriers;
int target_carrier;
snd_pcm_hw_params_t *hwparams;
};
static double sqmag(double x[2]) {
return x[0] * x[0] + x[1] * x[1];
}
/*
* Estimate matched filter by giving 1 in the center
* frequency and 0 otherwise. Then substract mean and
* normalize variance, so the frequency response in the
* filter has unit length (if viewed as a vector)
*/
void EstimateFilter(struct LoopParam* parm) {
vector<struct Carrier>& carriers = parm->carriers;
/* Create estimated filter for each carrier */
double *double_cell = new double[parm->num_bin];
for (int b = 0; b < parm->num_bin; ++b) {
double_cell[b] = 0.0;
}
for (unsigned c = 0; c < carriers.size(); ++c) {
int low = max(carriers[c].center_bin - lo_bandwidth, 0);
int hi = min(carriers[c].center_bin + hi_bandwidth, parm->num_bin - 1);
double_cell[carriers[c].center_bin] = 1.0;
carriers[c].InitMatchedFilter(low, hi, double_cell);
double_cell[carriers[c].center_bin] = 0.0;
}
delete [] double_cell;
}
/*
* Meausre matched filter by playing tone and capturing the
* response in frequency domain. Only apply it in very silent
* evironment or in the presence of static noise.
*/
void MeasureFilter(struct LoopParam* parm,
AlsaPlaybackClient* play_cli,
AlsaCaptureClient* cap_cli,
MultiToneGenerator* gen) {
vector<struct Carrier>& carriers = parm->carriers;
/* Spectrum analyzer */
CircularBuffer<double> double_buffer(carriers.size(), parm->num_frames);
fftw_plan plan;
fftw_complex *spectrum = static_cast<double(*)[2]>(
fftw_malloc(sizeof(fftw_complex) * (parm->num_freq)));
/* Start playback and capture threads */
pthread_t capture_thread;
pthread_create(&capture_thread, NULL, CaptureThreadEntry, cap_cli);
pthread_t playback_thread;
pthread_create(&playback_thread, NULL, PlayToneThreadEntry, play_cli);
for (unsigned c = 0; c < carriers.size(); ++c) {
parm->frequencies.clear();
gen->Reset(parm->frequencies);
parm->SetTargetCarrier(c);
gen->Reset(parm->frequencies);
usleep(300000); // Wait 300ms for playback to capture delay
char *sample_cell = cap_cli->Buffer()->LockCellToRead();
double *double_cell = double_buffer.LockCellToWrite();
// save frequency response in double_buffer
SampleCellToDoubleCell(static_cast<void *>(sample_cell),
double_cell,
parm->num_frames,
cap_cli->Format(),
cap_cli->NumChannel());
cap_cli->Buffer()->UnlockCellToRead();
plan = fftw_plan_dft_r2c_1d(parm->num_frames, double_cell, spectrum,
FFTW_ESTIMATE);
fftw_execute(plan);
for (int b = 0; b < parm->num_bin; ++b) {
double_cell[b] = sqmag(spectrum[b]) / parm->num_frames;
}
double_buffer.UnlockCellToWrite();
}
play_cli->set_state(AlsaPlaybackClient::kTerminated);
cap_cli->set_state(AlsaCaptureClient::kTerminated);
void *status;
pthread_join(playback_thread, &status);
pthread_join(capture_thread, &status);
/* Create matched filter for each carrier */
for (unsigned c = 0; c < carriers.size(); ++c) {
double *double_cell = double_buffer.LockCellToWrite();
int low = max(carriers[c].center_bin - lo_bandwidth, 0);
int hi = min(carriers[c].center_bin + hi_bandwidth, parm->num_bin - 1);
carriers[c].InitMatchedFilter(low, hi, double_cell);
double_buffer.UnlockCellToWrite();
}
parm->Print(stderr);
}
int LoopControl(AudioFunTestConfig& config) {
srand(time(NULL) + getpid());
/* AlsaCaptureClient */
AlsaCaptureClient capture_client(config.capture_alsa_device);
if (!capture_client.Init(config.sample_rate,
config.format,
config.channels,
2,
config.fftsize)) {
fprintf(stderr, "Unable to initialize AlsaCaputreClient: %d\n",
capture_client.last_error());
return 2;
}
if (config.verbose) capture_client.Print(stderr);
/* AlsaPlaybackClient */
AlsaPlaybackClient playback_client(config.playback_alsa_device);
if (!playback_client.Init(config.sample_rate,
config.format,
config.channels,
&config.active_channels,
config.fftsize)) {
fprintf(stderr, "Unable to initialize AlsaPlaybackClient: %d\n",
playback_client.last_error());
return 3;
}
if (config.verbose) playback_client.Print(stderr);
/* Tone generator */
MultiToneGenerator tone_generator(config.sample_rate,
config.tone_length_sec);
tone_generator.SetVolumes(config.start_volume, config.end_volume);
playback_client.SetPlayObj(&tone_generator);
/* Loop parameter */
struct LoopParam loop_parm(capture_client);
/*
MeasureFilter(&loop_parm, &playback_client, &capture_client,
&tone_generator);
*/
EstimateFilter(&loop_parm);
if (config.verbose) loop_parm.Print(stderr);
/* Spectrum analyzer */
CircularBuffer<double> double_buffer(1, loop_parm.num_frames);
fftw_plan plan;
fftw_complex *spectrum = static_cast<double(*)[2]>(
fftw_malloc(sizeof(fftw_complex) * (loop_parm.num_freq)));
/* Start capture and playback threads */
playback_client.set_state(AlsaPlaybackClient::kReady);
capture_client.set_state(AlsaCaptureClient::kReady);
pthread_t capture_thread;
pthread_create(&capture_thread, NULL, CaptureThreadEntry, &capture_client);
pthread_t playback_thread;
pthread_create(&playback_thread, NULL, PlayToneThreadEntry, &playback_client);
loop_parm.SetTargetCarrier(rand() % loop_parm.carriers.size());
tone_generator.Reset(loop_parm.frequencies);
/* Start feedback */
int success = 0, fail = 0;
int delay = 0;
double accum_confidence = 0.0;
/* Analyze cell by cell */
while(playback_client.state() ==
autotest_client::audio::AlsaPlaybackClient::kReady) {
char *sample_cell = capture_client.Buffer()->LockCellToRead();
double *double_cell = double_buffer.LockCellToWrite();
SampleCellToDoubleCell(static_cast<void *>(sample_cell),
double_cell,
loop_parm.num_frames,
capture_client.Format(),
capture_client.NumChannel());
capture_client.Buffer()->UnlockCellToRead();
plan = fftw_plan_dft_r2c_1d(loop_parm.num_frames, double_cell, spectrum,
FFTW_ESTIMATE);
fftw_execute(plan);
// Calculate spectrum energy
for (int i = 0; i < loop_parm.num_bin; ++i) {
double_cell[i] = sqmag(spectrum[i]) / loop_parm.num_frames;
}
double confidence = loop_parm.TargetCarrierConfidence(double_cell);
if (confidence > 0.0) accum_confidence += confidence;
double_buffer.UnlockCellToWrite();
++delay;
if (accum_confidence >= 3.0) { // success
++success;
fprintf(stderr, "O");
} else if (delay < 15) { // accum_confidence < 3.0, delaying
continue;
} else { // accum_confidence < 3.0 and time out
++fail;
fprintf(stderr, "X");
}
fprintf(stderr, ": carrier = %2d, delay = %2d, "
"success = %3d, fail = %3d, rate = %.1f\n",
loop_parm.target_carrier, delay, success, fail,
100.0 * success / (success + fail));
/* Either success or fail */
delay = 0;
accum_confidence = 0.0;
int new_target_carrier;
do {
new_target_carrier = rand() % loop_parm.carriers.size();
} while (new_target_carrier == loop_parm.target_carrier);
loop_parm.SetTargetCarrier(new_target_carrier);
tone_generator.Reset(loop_parm.frequencies);
}
playback_client.set_state(AlsaPlaybackClient::kTerminated);
capture_client.set_state(AlsaCaptureClient::kTerminated);
/* Play recorded sounds */
void *status;
pthread_join(playback_thread, &status);
pthread_join(capture_thread, &status);
return 0;
}
int main(int argc, char* argv[]) {
AudioFunTestConfig config;
if (!ParseOptions(argc, argv, &config)) {
fprintf(stderr, "\n");
PrintUsage(stderr, argv[0]);
return 1;
}
PrintConfig(stderr, config);
LoopControl(config);
return 0;
}