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