Comme ça m'a pris un certain temps, et que j'utilise des contributions diverses, je poste ici une contribution dont des bouts peuvent être utiles à certains.

Par intérêt, j'ai programmé une plateforme de discussion, désormais testée, qui marche bien avec 40 personnes.

Le principe: les n personnes parlent, et via JS/PHP, des fichiers audio wav sont progressivement créés sur le serveur (j'utilise wav, car non compressé, et facile à manipuler).

Le serveur crée progressivement n fichiers audio de sorties (pour une personne i, on multiplexe les fichiers audio des autres personnes)

Le multiplexage est codé en C. Environ 50ms pour multiplexer 40 morceaux d'une seconde avec 40 personnes, donc suffisamment efficace.

Je ne mets pas les codes en question, mais si ça intéresse quelqu'un, il n'y a qu'à demander.

Ensuite, chaque personne écoute sur un port différent via un mini serveur https de diffusion (un serveur par personne, c'est plus simple. chaque mini-serveur consomme environ 0.3%CPU sur mon serveur OVH (Ubuntu 18.04). Originellement j'avais essayé en PHP: ingérable, trop de consommation CPU.

La distribution audio se fait par injection régulière de code JS qui transmets des morceaux encodés en base64, car les balises audio html5 supportent ce genre de données.

Ici, le code du mini-serveur tourne sur un morceau "dat.wav" en cours de création:

Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <signal.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#define INVALID_SOCKET -1
#define SOCKET_ERROR -1
#define closesocket(param) close(param)
 
// fonctions annexes ------------------------------------
 
int base64_encode(const unsigned char *src, size_t len, char* outStr);
void fin();
void sig_term_handler(int signum, siginfo_t *info, void *ptr);
void catch_sigterm();
void substr(char* s,int deb,int len,char* rep);
void init_openssl();
void cleanup_openssl();
SSL_CTX *create_context();
void configure_context(SSL_CTX *ctx);
int create_socket(int port);
 
 
static const unsigned char base64_table[65] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";   
 
// -------------------------variables globales pour fermeture propre si SIGTERM
 
FILE* f;
SSL_CTX *ctx;
SSL *ssl;
 
//------------------------MAIN----------------------------------
 
int main(int argc, char **argv)
{
 
	char buffer[200000];
	char out[400000];
	char out2[100];
	char entete[45];
	char tmp[100];
    	int sock;
    	srand (time (NULL));
    	int port=(int) 4400L+(((100L* rand())/ (long) RAND_MAX));
 
 //--------------------capture  SIGTERM pour fermeture propre-------------------------  
    	catch_sigterm();
 
//-------------------------lien pour test-----------------------------
 
	FILE* test=fopen("essai.php","w");
	char tmpp[100];
	sprintf(tmpp,"<META http-equiv='refresh' content='0;https://neogene2.fr:%d'>",port);
	fwrite(tmpp,strlen(tmpp),1,test);
	fflush(test);
	fclose(test);
 
//-----------------------------------------------------------------------------
    init_openssl();
    ctx = create_context();
 
    configure_context(ctx);
 
    srand (time (NULL));
 
    printf("port %d\n",port);fflush(stdout);
 
    sock = create_socket(port);
 
 
 
        struct sockaddr_in addr;
        uint len = sizeof(addr);
 
 
	printf("listen\n");fflush(stdout);
        int client = accept(sock, (struct sockaddr*)&addr, &len);
        if (client < 0) {
            perror("Erreur accept");
            exit(EXIT_FAILURE);
        }
	printf("connecté\n");fflush(stdout);
 
 
	printf("Négociation SSL 'handshake'\n");fflush(stdout);
        ssl = SSL_new(ctx);
        SSL_set_fd(ssl, client);
	printf("Lancement SSL_accept\n");fflush(stdout);
	int rep=SSL_accept(ssl);
	printf("Résultat SSL_accept\n");fflush(stdout);
 
        if (rep <= 0) { //---------------Echec négociation-----------------------------
        printf("%s",strerror(SSL_get_error(ssl,rep)));fflush(stdout);
        SSL_shutdown(ssl);
        SSL_free(ssl);
        close(client);
        close(sock);
    	SSL_CTX_free(ctx);
    	cleanup_openssl();
        return(0);
        }
     	printf("Négociation SSL OK");fflush(stdout);
 
        sprintf(buffer,"HTTP/1.1 200 OK\r\n"
                "Content-length:1000000000\r\n"
                "Content-Type: text/html\r\n\r\n"
                );
                printf(" SSL send HEADER\n");fflush(stdout);
                SSL_write(ssl, buffer,strlen(buffer));
 
                printf("Envoyé:\n%s\n\n",buffer);fflush(stdout);
                bzero(buffer,5000);  
 
                //-----------------------Pas utile, juste pour vérification----------------            
                SSL_read(ssl,buffer,sizeof(buffer));   
                usleep(100000);
                printf("Réponse du client : %s\n",buffer);fflush(stdout);              
                bzero(buffer,5000);
 
      //--------------------- Entête wav (44 octects)-----------------------------------
 
                //unsigned long nsample=48000; // 1s avec échantillonage à 48000 Hz
                //sprintf(buff,"RIFF%luWAVEfmt %lu%u%u%u%lu%u%udata%lu",36+nsample*2,16L,1,1,48000,4*48000L,2,16,nsample*2);
                //Pas bon résultat. A voir. problème little endian?
                FILE* ent=fopen("entete","rb");//entête écrit en PHP
                fread(entete,44,1,ent);
                entete[44]='\0';
                fclose(ent);
 
 
 
		bzero(buffer,100000);
                int i=0;
 
                FILE* js=fopen("ecouter.php","r");
                while (fread(&buffer[i],1,1,js)>0){i++;}
 
                SSL_write(ssl, buffer,i);
 
 
                f=fopen("../test/dat.wav","r");
 
                int tot=0;
                int num=0;             
             	int pos_lecture=0;
             	int len2;
 
                //-------------------------Calage sur la position courante------------------------------
                while (fread(buffer,96000,1,f)==1){pos_lecture+=96000;}
 
                fseek(f,pos_lecture,SEEK_SET);
 
 
                //------------------------ Entête WAV--------------------------------------------
                for (i=0;i<44;i++){buffer[i]=entete[i];}              
 
 
                while(1)
                {
                if( access( "../test/stop", F_OK ) != -1 )
                	{
                	  fin();      	
                	}        
 
                if (fread(&buffer[44],96000,1,f)!=1)
                	{fseek(f,pos_lecture,SEEK_SET);usleep(10000);continue;}
                pos_lecture+=96000;
 
                len2=base64_encode(buffer,96044,out);
 
                sprintf(out2,"<script type='text/javascript'>num_max=%d;tab[%d]='data:audio/wav;base64,",num,num);num++;
                SSL_write(ssl,out2,strlen(out2));
                SSL_write(ssl,out,len2);
                sprintf(out2,"';</script>\n");
                SSL_write(ssl,out2,strlen(out2));
                usleep(800000);
                } 
 
 
}
 
 
//--------------------------------FIN MAIN-------------------------------------
 
 
//---------------------------Création socket------------------------------------
 
int create_socket(int port)
{
    int s;
    struct sockaddr_in addr;
 
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    addr.sin_addr.s_addr = htonl(INADDR_ANY);
 
    s = socket(AF_INET, SOCK_STREAM, 0);
    if (s < 0) {
	perror("Erreur création socket");
	exit(EXIT_FAILURE);
    }
 
    if (bind(s, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
	perror("Erreur bind");
	exit(EXIT_FAILURE);
    }
 
    if (listen(s, 5) < 0) {
	perror("Erreur listen");
	exit(EXIT_FAILURE);
    }
 
    return s;
}
 
//---------------------------------------fonctions SSL-------------------------------
 
void init_openssl()
{ 
    SSL_load_error_strings();	
    OpenSSL_add_ssl_algorithms();
}
 
void cleanup_openssl()
{
    EVP_cleanup();
}
 
SSL_CTX *create_context()
{
    const SSL_METHOD *method;
    SSL_CTX *ctx;
 
    method = SSLv23_server_method();
 
    ctx = SSL_CTX_new(method);
    if (!ctx) {
	perror("Erreur création contexte SSL");
	ERR_print_errors_fp(stdout);
	exit(EXIT_FAILURE);
    }
 
    return ctx;
}
 
void configure_context(SSL_CTX *ctx)
{
    SSL_CTX_set_ecdh_auto(ctx, 1);
 
   //-----------------------------Clés du domaine-------------------
    if (SSL_CTX_use_certificate_file(ctx, "cert.pem", SSL_FILETYPE_PEM) <= 0) {
        ERR_print_errors_fp(stdout);
	exit(EXIT_FAILURE);
    }
 
    if (SSL_CTX_use_PrivateKey_file(ctx, "privkey.pem", SSL_FILETYPE_PEM) <= 0 ) {
        ERR_print_errors_fp(stdout);
	exit(EXIT_FAILURE);
    }
}
 
//--------------------------------clôture--------------------------------------------
 
void fin(){
			printf("fin\n");fflush(stdout);
 
			char out[100];
			sprintf(out,"<script type='text/javascript'>\n cont=false;</script>");
			SSL_write(ssl,out,strlen(out));
                	usleep(1000000);
                	SSL_shutdown(ssl);
        		SSL_free(ssl);
                	SSL_CTX_free(ctx);
    			cleanup_openssl();
                	fclose(f);exit(0);   
 
}
 
//-------------------------------------handler sigterm-----------------------------------
 
void sig_term_handler(int signum, siginfo_t *info, void *ptr)
{
    fin();
}
 
void catch_sigterm()
{
    static struct sigaction _sigact;
 
    memset(&_sigact, 0, sizeof(_sigact));
    _sigact.sa_sigaction = sig_term_handler;
    _sigact.sa_flags = SA_SIGINFO;
 
    sigaction(SIGTERM, &_sigact, NULL);
}
 
// --------------------------------encodage base 64---------------------------------------------
 
int base64_encode(const unsigned char *src, size_t len, char* outStr)
{
    unsigned char *out, *pos;
    const unsigned char *end, *in;
 
    int olen;
 
    olen = 4*((len + 2) / 3); // 3 octects->4 octets
 
    out = (unsigned char*)&outStr[0];
 
    end = src + len;
    in = src;
    pos = out;
    while (end - in >= 3) {
        *pos++ = base64_table[in[0] >> 2];
        *pos++ = base64_table[((in[0] & 0x03) << 4) | (in[1] >> 4)];
        *pos++ = base64_table[((in[1] & 0x0f) << 2) | (in[2] >> 6)];
        *pos++ = base64_table[in[2] & 0x3f];
        in += 3;
    }
 
    if (end - in) {
        *pos++ = base64_table[in[0] >> 2];
        if (end - in == 1) {
            *pos++ = base64_table[(in[0] & 0x03) << 4];
            *pos++ = '=';
        }
        else {
            *pos++ = base64_table[((in[0] & 0x03) << 4) |
                (in[1] >> 4)];
            *pos++ = base64_table[(in[1] & 0x0f) << 2];
        }
        *pos++ = '=';
    }
    outStr[olen]='\0';
    return olen;
}
Il y a la négociation SSL pour https, puis un envoi d'header déclarant un contenu faussement très long, puis l'envoi des données JS (plus besoin d'header)


le fichier "ecouter.php" qui contient le code JS de traitement :

Code html : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
<!DOCTYPE html>
 
<script type="text/javascript">
 
var num_audio=0;
var num=0;
var context,source;
var start_filtre=[true,true];
var vol_max=1;
var t=0;
var inter;
var tab=[];
var au;
var debut;
var num_max=0;
var cont=true;
 
async function main(){
 
console.log("debut");
 
function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}
 
await sleep(1000);
 
au=[document.getElementById("0"),document.getElementById("1")];
 
 
au[0].volume=vol_max;
 
function filtre(n){
        
        var context = new AudioContext();
        var source = context.createMediaElementSource(au[n]);
        var filter = context.createBiquadFilter();
        filter.type="bandpass";
        filter.frequency.value = 400;
        filter.Q.value=0;
        source.connect(filter);
        filter.connect(context.destination);
        console.log("filtre "+n);       
}
 
 
 
 
async function get(n){
        while (tab[n]==undefined){await sleep(50);}
}
 
console.log("get 0");
 
await get(0);
au[0].src=tab[0];
 
console.log("0 OK");
 
 
 
async function surveille()
{
                if (num>0 && num%50==0){console.clear();}
                if (!cont){return;}
                try{
                t=au[num_audio].currentTime;
                if (num_max-num>2){
                        for (var i=num;i<num_max-2;i++){tab[i]="";}
                        num=num_max-2;
                        num_audio=0;
                        au[1].pause();
                        au[0].src=tab[num];au[0].volume=vol_max;au[0].play();
                        setTimeout(function(){surveille()},10);
                        return;                 
                }
                if (num_max-num>1){au[0].playbackRate=1.3;au[1].playbackRate=1.3}
                else if (num_max-num==1){au[0].playbackRate=1.2;au[1].playbackRate=1.2}
                else if (t<0.5){au[0].playbackRate=1;au[1].playbackRate=1}
                else {au[0].playbackRate=0.9;au[1].playbackRate=0.9;}
                
                
                
                t=au[num_audio].currentTime;
                
                
                        
                if (t>0.97){
                        
                        var old_num_audio=num_audio;
                        num_audio=(num_audio+1)%2;
                        num++;
                        await get(num);
                        au[(num_audio)].volume=0
                        au[(num_audio)].src=tab[num];
                        tab[num]="";
                        var delta;
                        await sleep(10);
                        for (var i=0;i<20;i++)
                                {
                                delta=((i/19)**2)*vol_max;
                                au[num_audio].volume=delta;
                                au[old_num_audio].volume=1-delta;
                                await sleep(1);
                                }       
                        //au[num_audio].volume=vol_max;au[old_num_audio].volume=0;
                        }
                        
                        
                
                if (t>0)
                {
                        if (start_filtre[num_audio]){start_filtre[num_audio]=false;filtre(num_audio);}                          
                }       
                
                }
                catch(err){console.log(err);}
                
        setTimeout(function(){surveille()},10); 
        
}
 
surveille();
 
 
 
function volmax(){
console.log("num="+num+" num_max="+num_max+" volmax="+vol_max);
if (!cont){return;}
if (parent.volume){vol_max=parent.volume();au[num_audio].volume=vol_max;}
setTimeout(function(){volmax();},500);
}
 
volmax();
 
}
 
main();
 
</script>
 
<audio id="0" autoplay controls ></audio>
<audio id="1" controls autoplay></audio>


Notamment, vous pouvez récupérer la partie passage en HTTPS (Dans la fonction adéquate, il faut charger le certificat et la clé privée utilisée pour votre domaine. Les emplacements sont dans les virtualhosts apache (ou autre)

Et penser à cette technique d'injection de JS, qui permet à peu près tout.



EDIT: on utilise Opera (Excellent navigateur, trop méconnu. Le meilleur en gestion de JS) ou Chrome, qui gèrent plus efficacement le JS. FF et Edge sont plus lents, et il faut adapter le code JS.
La transition entre les chunks est encore un peu pourrie. Il faut que j'essaie d'autres techniques.

Bref, des trucs à régler, mais le principe fonctionne sur tous les navigateurs

Et puis, je vais implémenter un transfert direct des morceaux audio du programme de multiplexage vers les mini-serveurs par pipe nommés, ça sera plus efficace que des opérations disques évidemment.