1 /**
2 * Licence: 3-clause BSD
3 */
4
5 /* Playing and recording audio data to and from audio devices */
6 #include <portaudio.h>
7 /* Reading and writing .wav files */
8 #include <sndfile.h>
9 /* Audio filtering */
10 #include <filter_audio.h>
11
12 #include <stdlib.h>
13 #include <time.h>
14 #include <assert.h>
15 #include <string.h>
16 #include <stdbool.h>
17
18 Filter_Audio *filteraudio;
19 bool filterenabled = true;
20
21 /* Input callback. The data in 'input' buffer contains 'fcount' frames captured from the input device.
22 * We will filter that data and write it to our output file. (You can consider input and output files as
23 * peer audio feed).
24 */
in_cb(const void * input,void * o,unsigned long fcount,const PaStreamCallbackTimeInfo * c,PaStreamCallbackFlags f,void * user_data)25 int in_cb(const void *input, void *o, unsigned long fcount, const PaStreamCallbackTimeInfo* c, PaStreamCallbackFlags f, void *user_data)
26 {
27 (void) o;
28 (void) c;
29 (void) f;
30
31 SNDFILE *af_handle = user_data; // af_handle_out
32
33 /* We copy data to the mutable buffer */
34 int16_t PCM [fcount]; /* WARNING I'm not quire sure how this works with 2 channels but you'd copy the buffer however */
35 memcpy (PCM, input, sizeof(PCM));
36
37 /* Now we filter it. After the process is completed you'll get buffer filled with echo-less PCM data. */
38 if (filterenabled && filter_audio(filteraudio, PCM, fcount) == -1)
39 puts("Filtering failed");
40
41 /* Write filtered data to the file. (or send it to the peer) */
42 sf_write_short(af_handle, PCM, fcount);
43
44 return 0;
45 }
46 /* Output callback. The 'output' contains space for 'fcount' frames to be played by the output device.
47 * We will have to feed filter_audio with that data as it's a referene what sounds should be treated as echo in the input.
48 * We read the output data from the input file provided to use by the first argument (You can consider input and
49 * output files as peer audio feed).
50 */
out_cb(const void * i,void * output,unsigned long fcount,const PaStreamCallbackTimeInfo * c,PaStreamCallbackFlags f,void * user_data)51 int out_cb(const void *i, void *output, unsigned long fcount, const PaStreamCallbackTimeInfo* c, PaStreamCallbackFlags f, void *user_data)
52 {
53 (void) i;
54 (void) c;
55 (void) f;
56
57 SNDFILE *af_handle = user_data; // af_handle_in
58
59 /* Read PCM from the file */
60 int64_t count = sf_read_short(af_handle, output, fcount);
61
62 /* If some frames are read, pass them to filter_audio */
63 if (filterenabled && count > 0)
64 pass_audio_output(filteraudio, output, count);
65
66 return 0;
67 }
68
main(int argc,char ** argv)69 int main (int argc, char** argv)
70 {
71 if (argc < 2) {
72 puts("Required input .wav file path");
73 return 1;
74 }
75
76 const char* output_path = "echoes_removed.wav";
77
78 if (argc > 2)
79 output_path = argv[2];
80
81 Pa_Initialize();
82
83 /* list audio IO devices */
84 for (int i = 0; i < Pa_GetDeviceCount(); i ++)
85 puts(Pa_GetDeviceInfo(i)->name);
86
87 SNDFILE *af_handle_in, *af_handle_out;
88 SF_INFO af_info_in, af_info_out;
89
90 /* Open input audio file */
91 af_handle_in = sf_open(argv[1], SFM_READ, &af_info_in);
92
93 if (af_handle_in == NULL) {
94 puts("Failed to open input file");
95 return 1;
96 }
97
98 /* Open output audio file */
99 af_info_out = af_info_in;
100 af_handle_out = sf_open(output_path, SFM_WRITE, &af_info_out);
101
102 if (af_handle_out == NULL) {
103 puts("Failed to open output file");
104 return 1;
105 }
106
107 /* Prepare filter_audio */
108 filteraudio = new_filter_audio(af_info_in.samplerate);
109
110 /* Prepare portaudio streams */
111 PaStream *adout = NULL;
112 PaStream *adin = NULL;
113
114 /* Choose devices */
115 int in_dev = Pa_GetDefaultInputDevice();
116 int out_dev = Pa_GetDefaultOutputDevice();
117
118 /* High latency works the best but low latency will also work */
119 double inlat = Pa_GetDeviceInfo(in_dev)->defaultHighInputLatency;
120 double outlat = Pa_GetDeviceInfo(out_dev)->defaultHighOutputLatency;
121
122 PaStreamParameters output;
123 output.device = out_dev;
124 output.channelCount = af_info_in.channels;
125 output.sampleFormat = paInt16;
126 output.suggestedLatency = outlat;
127 output.hostApiSpecificStreamInfo = NULL;
128
129 PaStreamParameters input;
130 input.device = in_dev;
131 input.channelCount = af_info_in.channels;
132 input.sampleFormat = paInt16;
133 input.suggestedLatency = inlat;
134 input.hostApiSpecificStreamInfo = NULL;
135
136 int frame_duration = 20;
137 int frame_size = (af_info_in.samplerate * frame_duration / 1000) * af_info_in.channels;
138
139 PaError err = Pa_OpenStream(&adout, NULL, &output, af_info_in.samplerate, frame_size, paNoFlag, out_cb, af_handle_in);
140 assert(err == paNoError);
141
142 err = Pa_OpenStream(&adin, &input, NULL, af_info_in.samplerate, frame_size, paNoFlag, in_cb, af_handle_out);
143 assert(err == paNoError);
144
145 /* It's essential that echo delay is set correctly; it's the most important part of the
146 * echo cancellation process. If the delay is not set to the acceptable values the AEC
147 * will not be able to recover. Given that it's not that easy to figure out the exact
148 * time it takes for a signal to get from Output to the Input, setting it to suggested
149 * input device latency + frame duration works really good and gives the filter ability
150 * to adjust it internally after some time (usually up to 6-7 seconds in my tests when
151 * the error is about 20%).
152 */
153 set_echo_delay_ms(filteraudio, (inlat * 1000) + frame_duration);
154 /*
155 */
156
157 /* Start the streams */
158 err = Pa_StartStream(adout);
159 assert(err == paNoError);
160
161 err = Pa_StartStream(adin);
162 assert(err == paNoError);
163
164 /* In case you want to repeat set reps to > 1 */
165 int reps = 1;
166 for (int i = 0; i < reps; i ++)
167 {
168 /* Sleep until the whole file is read */
169 Pa_Sleep((af_info_in.frames * 1000) / (af_info_in.samplerate + 2));
170 sf_seek(af_handle_in, 0, SEEK_SET);
171 }
172
173 /* Clear everything */
174 Pa_StopStream(adout);
175 Pa_StopStream(adin);
176 kill_filter_audio(filteraudio);
177 sf_close(af_handle_in);
178 sf_close(af_handle_out);
179 Pa_Terminate();
180
181 return 0;
182 }
183