mirror of
http://www.pogo.org.uk/~mark/trx.git
synced 2024-09-30 07:25:05 +02:00
Compare commits
No commits in common. "master" and "0.2" have entirely different histories.
2
README
2
README
@ -1,6 +1,6 @@
|
|||||||
trx: Realtime audio over IP
|
trx: Realtime audio over IP
|
||||||
|
|
||||||
(C) Copyright 2020 Mark Hills <mark@xwax.org>
|
(C) Copyright 2012 Mark Hills <mark@xwax.org>
|
||||||
|
|
||||||
See the COPYING file for licensing terms.
|
See the COPYING file for licensing terms.
|
||||||
|
|
||||||
|
10
device.c
10
device.c
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2020 Mark Hills <mark@xwax.org>
|
* Copyright (C) 2012 Mark Hills <mark@xwax.org>
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or
|
* This program is free software; you can redistribute it and/or
|
||||||
* modify it under the terms of the GNU General Public License
|
* modify it under the terms of the GNU General Public License
|
||||||
@ -53,7 +53,7 @@ int set_alsa_hw(snd_pcm_t *pcm,
|
|||||||
r = snd_pcm_hw_params_set_access(pcm, hw, SND_PCM_ACCESS_RW_INTERLEAVED);
|
r = snd_pcm_hw_params_set_access(pcm, hw, SND_PCM_ACCESS_RW_INTERLEAVED);
|
||||||
CHK("snd_pcm_hw_params_set_access", r);
|
CHK("snd_pcm_hw_params_set_access", r);
|
||||||
|
|
||||||
r = snd_pcm_hw_params_set_format(pcm, hw, SND_PCM_FORMAT_S16);
|
r = snd_pcm_hw_params_set_format(pcm, hw, SND_PCM_FORMAT_FLOAT);
|
||||||
CHK("snd_pcm_hw_params_set_format", r);
|
CHK("snd_pcm_hw_params_set_format", r);
|
||||||
|
|
||||||
r = snd_pcm_hw_params_set_rate(pcm, hw, rate, 0);
|
r = snd_pcm_hw_params_set_rate(pcm, hw, rate, 0);
|
||||||
@ -62,9 +62,9 @@ int set_alsa_hw(snd_pcm_t *pcm,
|
|||||||
r = snd_pcm_hw_params_set_channels(pcm, hw, channels);
|
r = snd_pcm_hw_params_set_channels(pcm, hw, channels);
|
||||||
CHK("snd_pcm_hw_params_set_channels", r);
|
CHK("snd_pcm_hw_params_set_channels", r);
|
||||||
|
|
||||||
dir = -1;
|
dir = 0;
|
||||||
r = snd_pcm_hw_params_set_buffer_time_near(pcm, hw, &buffer, &dir);
|
r = snd_pcm_hw_params_set_buffer_time_max(pcm, hw, &buffer, &dir);
|
||||||
CHK("snd_pcm_hw_params_set_buffer_time_near", r);
|
CHK("snd_pcm_hw_params_set_buffer_time_max", r);
|
||||||
|
|
||||||
r = snd_pcm_hw_params(pcm, hw);
|
r = snd_pcm_hw_params(pcm, hw);
|
||||||
CHK("hw_params", r);
|
CHK("hw_params", r);
|
||||||
|
2
device.h
2
device.h
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2020 Mark Hills <mark@xwax.org>
|
* Copyright (C) 2012 Mark Hills <mark@xwax.org>
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or
|
* This program is free software; you can redistribute it and/or
|
||||||
* modify it under the terms of the GNU General Public License
|
* modify it under the terms of the GNU General Public License
|
||||||
|
2
notice.h
2
notice.h
@ -1,6 +1,6 @@
|
|||||||
#ifndef NOTICE_H
|
#ifndef NOTICE_H
|
||||||
#define NOTICE_H
|
#define NOTICE_H
|
||||||
|
|
||||||
#define COPYRIGHT "trx (C) Copyright 2020 Mark Hills <mark@xwax.org>"
|
#define COPYRIGHT "trx (C) Copyright 2012 Mark Hills <mark@xwax.org>"
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
43
rx.c
43
rx.c
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2020 Mark Hills <mark@xwax.org>
|
* Copyright (C) 2012 Mark Hills <mark@xwax.org>
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or
|
* This program is free software; you can redistribute it and/or
|
||||||
* modify it under the terms of the GNU General Public License
|
* modify it under the terms of the GNU General Public License
|
||||||
@ -32,7 +32,7 @@
|
|||||||
|
|
||||||
static unsigned int verbose = DEFAULT_VERBOSE;
|
static unsigned int verbose = DEFAULT_VERBOSE;
|
||||||
|
|
||||||
static void timestamp_jump(RtpSession *session, void *a, void *b, void *c)
|
static void timestamp_jump(RtpSession *session, ...)
|
||||||
{
|
{
|
||||||
if (verbose > 1)
|
if (verbose > 1)
|
||||||
fputc('|', stderr);
|
fputc('|', stderr);
|
||||||
@ -45,13 +45,13 @@ static RtpSession* create_rtp_recv(const char *addr_desc, const int port,
|
|||||||
RtpSession *session;
|
RtpSession *session;
|
||||||
|
|
||||||
session = rtp_session_new(RTP_SESSION_RECVONLY);
|
session = rtp_session_new(RTP_SESSION_RECVONLY);
|
||||||
rtp_session_set_scheduling_mode(session, FALSE);
|
rtp_session_set_scheduling_mode(session, 0);
|
||||||
rtp_session_set_blocking_mode(session, FALSE);
|
rtp_session_set_blocking_mode(session, 0);
|
||||||
rtp_session_set_local_addr(session, addr_desc, port, -1);
|
rtp_session_set_local_addr(session, addr_desc, port, -1);
|
||||||
rtp_session_set_connected_mode(session, FALSE);
|
rtp_session_set_connected_mode(session, FALSE);
|
||||||
rtp_session_enable_adaptive_jitter_compensation(session, TRUE);
|
rtp_session_enable_adaptive_jitter_compensation(session, TRUE);
|
||||||
rtp_session_set_jitter_compensation(session, jitter); /* ms */
|
rtp_session_set_jitter_compensation(session, jitter); /* ms */
|
||||||
rtp_session_set_time_jump_limit(session, jitter * 16); /* ms */
|
rtp_session_set_time_jump_limit(session, jitter * 2); /* ms */
|
||||||
if (rtp_session_set_payload_type(session, 0) != 0)
|
if (rtp_session_set_payload_type(session, 0) != 0)
|
||||||
abort();
|
abort();
|
||||||
if (rtp_session_signal_connect(session, "timestamp_jump",
|
if (rtp_session_signal_connect(session, "timestamp_jump",
|
||||||
@ -60,15 +60,6 @@ static RtpSession* create_rtp_recv(const char *addr_desc, const int port,
|
|||||||
abort();
|
abort();
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* oRTP in RECVONLY mode attempts to send RTCP packets and
|
|
||||||
* segfaults (v4.3.0 tested)
|
|
||||||
*
|
|
||||||
* https://stackoverflow.com/questions/43591690/receiving-rtcp-issues-within-ortp-library
|
|
||||||
*/
|
|
||||||
|
|
||||||
rtp_session_enable_rtcp(session, FALSE);
|
|
||||||
|
|
||||||
return session;
|
return session;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,15 +70,15 @@ static int play_one_frame(void *packet,
|
|||||||
const unsigned int channels)
|
const unsigned int channels)
|
||||||
{
|
{
|
||||||
int r;
|
int r;
|
||||||
int16_t *pcm;
|
float *pcm;
|
||||||
snd_pcm_sframes_t f, samples = 1920;
|
snd_pcm_sframes_t f, samples = 1920;
|
||||||
|
|
||||||
pcm = alloca(sizeof(*pcm) * samples * channels);
|
pcm = alloca(sizeof(float) * samples * channels);
|
||||||
|
|
||||||
if (packet == NULL) {
|
if (packet == NULL) {
|
||||||
r = opus_decode(decoder, NULL, 0, pcm, samples, 1);
|
r = opus_decode_float(decoder, NULL, 0, pcm, samples, 1);
|
||||||
} else {
|
} else {
|
||||||
r = opus_decode(decoder, packet, len, pcm, samples, 0);
|
r = opus_decode_float(decoder, packet, len, pcm, samples, 0);
|
||||||
}
|
}
|
||||||
if (r < 0) {
|
if (r < 0) {
|
||||||
fprintf(stderr, "opus_decode: %s\n", opus_strerror(r));
|
fprintf(stderr, "opus_decode: %s\n", opus_strerror(r));
|
||||||
@ -95,14 +86,10 @@ static int play_one_frame(void *packet,
|
|||||||
}
|
}
|
||||||
|
|
||||||
f = snd_pcm_writei(snd, pcm, r);
|
f = snd_pcm_writei(snd, pcm, r);
|
||||||
if (f < 0) {
|
|
||||||
f = snd_pcm_recover(snd, f, 0);
|
|
||||||
if (f < 0) {
|
if (f < 0) {
|
||||||
aerror("snd_pcm_writei", f);
|
aerror("snd_pcm_writei", f);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
if (f < r)
|
if (f < r)
|
||||||
fprintf(stderr, "Short write %ld\n", f);
|
fprintf(stderr, "Short write %ld\n", f);
|
||||||
|
|
||||||
@ -171,10 +158,9 @@ static void usage(FILE *fd)
|
|||||||
fprintf(fd, " -c <n> Number of channels (default %d)\n",
|
fprintf(fd, " -c <n> Number of channels (default %d)\n",
|
||||||
DEFAULT_CHANNELS);
|
DEFAULT_CHANNELS);
|
||||||
|
|
||||||
fprintf(fd, "\nProgram parameters:\n");
|
fprintf(fd, "\nDisplay parameters:\n");
|
||||||
fprintf(fd, " -v <n> Verbosity level (default %d)\n",
|
fprintf(fd, " -v <n> Verbosity level (default %d)\n",
|
||||||
DEFAULT_VERBOSE);
|
DEFAULT_VERBOSE);
|
||||||
fprintf(fd, " -D <file> Run as a daemon, writing process ID to the given file\n");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(int argc, char *argv[])
|
int main(int argc, char *argv[])
|
||||||
@ -186,8 +172,7 @@ int main(int argc, char *argv[])
|
|||||||
|
|
||||||
/* command-line options */
|
/* command-line options */
|
||||||
const char *device = DEFAULT_DEVICE,
|
const char *device = DEFAULT_DEVICE,
|
||||||
*addr = DEFAULT_ADDR,
|
*addr = DEFAULT_ADDR;
|
||||||
*pid = NULL;
|
|
||||||
unsigned int buffer = DEFAULT_BUFFER,
|
unsigned int buffer = DEFAULT_BUFFER,
|
||||||
rate = DEFAULT_RATE,
|
rate = DEFAULT_RATE,
|
||||||
jitter = DEFAULT_JITTER,
|
jitter = DEFAULT_JITTER,
|
||||||
@ -227,9 +212,6 @@ int main(int argc, char *argv[])
|
|||||||
case 'v':
|
case 'v':
|
||||||
verbose = atoi(optarg);
|
verbose = atoi(optarg);
|
||||||
break;
|
break;
|
||||||
case 'D':
|
|
||||||
pid = optarg;
|
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
usage(stderr);
|
usage(stderr);
|
||||||
return -1;
|
return -1;
|
||||||
@ -258,9 +240,6 @@ int main(int argc, char *argv[])
|
|||||||
if (set_alsa_sw(snd) == -1)
|
if (set_alsa_sw(snd) == -1)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
if (pid)
|
|
||||||
go_daemon(pid);
|
|
||||||
|
|
||||||
go_realtime();
|
go_realtime();
|
||||||
r = run_rx(session, decoder, snd, channels, rate);
|
r = run_rx(session, decoder, snd, channels, rate);
|
||||||
|
|
||||||
|
48
sched.c
48
sched.c
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2020 Mark Hills <mark@xwax.org>
|
* Copyright (C) 2012 Mark Hills <mark@xwax.org>
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or
|
* This program is free software; you can redistribute it and/or
|
||||||
* modify it under the terms of the GNU General Public License
|
* modify it under the terms of the GNU General Public License
|
||||||
@ -17,11 +17,8 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <errno.h>
|
|
||||||
#include <pthread.h>
|
|
||||||
#include <sched.h>
|
#include <sched.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <unistd.h>
|
|
||||||
|
|
||||||
#include "sched.h"
|
#include "sched.h"
|
||||||
|
|
||||||
@ -30,48 +27,23 @@
|
|||||||
int go_realtime(void)
|
int go_realtime(void)
|
||||||
{
|
{
|
||||||
int max_pri;
|
int max_pri;
|
||||||
const struct sched_param sp = {
|
struct sched_param sp;
|
||||||
.sched_priority = REALTIME_PRIORITY,
|
|
||||||
};
|
if (sched_getparam(0, &sp)) {
|
||||||
|
perror("sched_getparam");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
max_pri = sched_get_priority_max(SCHED_FIFO);
|
max_pri = sched_get_priority_max(SCHED_FIFO);
|
||||||
|
sp.sched_priority = REALTIME_PRIORITY;
|
||||||
|
|
||||||
if (sp.sched_priority > max_pri) {
|
if (sp.sched_priority > max_pri) {
|
||||||
fprintf(stderr, "Invalid priority (maximum %d)\n", max_pri);
|
fprintf(stderr, "Invalid priority (maximum %d)\n", max_pri);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
errno = pthread_setschedparam(pthread_self(), SCHED_FIFO, &sp);
|
if (sched_setscheduler(0, SCHED_FIFO, &sp)) {
|
||||||
if (errno) {
|
perror("sched_setscheduler");
|
||||||
perror("pthread_setschedparam");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int go_daemon(const char *pid_file)
|
|
||||||
{
|
|
||||||
FILE *f;
|
|
||||||
|
|
||||||
if (daemon(0, 0) == -1) {
|
|
||||||
perror("daemon");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!pid_file)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
f = fopen(pid_file, "w");
|
|
||||||
if (!f) {
|
|
||||||
perror("fopen");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
fprintf(f, "%d", getpid());
|
|
||||||
|
|
||||||
if (fclose(f) != 0) {
|
|
||||||
perror("fclose");
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
3
sched.h
3
sched.h
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2020 Mark Hills <mark@xwax.org>
|
* Copyright (C) 2012 Mark Hills <mark@xwax.org>
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or
|
* This program is free software; you can redistribute it and/or
|
||||||
* modify it under the terms of the GNU General Public License
|
* modify it under the terms of the GNU General Public License
|
||||||
@ -21,6 +21,5 @@
|
|||||||
#define MISC_H
|
#define MISC_H
|
||||||
|
|
||||||
int go_realtime(void);
|
int go_realtime(void);
|
||||||
int go_daemon(const char *pid_file);
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
41
tx.c
41
tx.c
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2020 Mark Hills <mark@xwax.org>
|
* Copyright (C) 2012 Mark Hills <mark@xwax.org>
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or
|
* This program is free software; you can redistribute it and/or
|
||||||
* modify it under the terms of the GNU General Public License
|
* modify it under the terms of the GNU General Public License
|
||||||
@ -48,8 +48,6 @@ static RtpSession* create_rtp_send(const char *addr_desc, const int port)
|
|||||||
abort();
|
abort();
|
||||||
if (rtp_session_set_multicast_ttl(session, 16) != 0)
|
if (rtp_session_set_multicast_ttl(session, 16) != 0)
|
||||||
abort();
|
abort();
|
||||||
if (rtp_session_set_dscp(session, 40) != 0)
|
|
||||||
abort();
|
|
||||||
|
|
||||||
return session;
|
return session;
|
||||||
}
|
}
|
||||||
@ -62,38 +60,24 @@ static int send_one_frame(snd_pcm_t *snd,
|
|||||||
const unsigned int ts_per_frame,
|
const unsigned int ts_per_frame,
|
||||||
RtpSession *session)
|
RtpSession *session)
|
||||||
{
|
{
|
||||||
int16_t *pcm;
|
float *pcm;
|
||||||
void *packet;
|
void *packet;
|
||||||
ssize_t z;
|
ssize_t z;
|
||||||
snd_pcm_sframes_t f;
|
snd_pcm_sframes_t f;
|
||||||
static unsigned int ts = 0;
|
static unsigned int ts = 0;
|
||||||
|
|
||||||
pcm = alloca(sizeof(*pcm) * samples * channels);
|
pcm = alloca(sizeof(float) * samples * channels);
|
||||||
packet = alloca(bytes_per_frame);
|
packet = alloca(bytes_per_frame);
|
||||||
|
|
||||||
f = snd_pcm_readi(snd, pcm, samples);
|
f = snd_pcm_readi(snd, pcm, samples);
|
||||||
if (f < 0) {
|
|
||||||
if (f == -ESTRPIPE)
|
|
||||||
ts = 0;
|
|
||||||
|
|
||||||
f = snd_pcm_recover(snd, f, 0);
|
|
||||||
if (f < 0) {
|
if (f < 0) {
|
||||||
aerror("snd_pcm_readi", f);
|
aerror("snd_pcm_readi", f);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
return 0;
|
if (f < samples)
|
||||||
}
|
|
||||||
|
|
||||||
/* Opus encoder requires a complete frame, so if we xrun
|
|
||||||
* mid-frame then we discard the incomplete audio. The next
|
|
||||||
* read will catch the error condition and recover */
|
|
||||||
|
|
||||||
if (f < samples) {
|
|
||||||
fprintf(stderr, "Short read, %ld\n", f);
|
fprintf(stderr, "Short read, %ld\n", f);
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
z = opus_encode(encoder, pcm, samples, packet, bytes_per_frame);
|
z = opus_encode_float(encoder, pcm, samples, packet, bytes_per_frame);
|
||||||
if (z < 0) {
|
if (z < 0) {
|
||||||
fprintf(stderr, "opus_encode_float: %s\n", opus_strerror(z));
|
fprintf(stderr, "opus_encode_float: %s\n", opus_strerror(z));
|
||||||
return -1;
|
return -1;
|
||||||
@ -154,10 +138,9 @@ static void usage(FILE *fd)
|
|||||||
fprintf(fd, " -b <kbps> Bitrate (approx., default %d)\n",
|
fprintf(fd, " -b <kbps> Bitrate (approx., default %d)\n",
|
||||||
DEFAULT_BITRATE);
|
DEFAULT_BITRATE);
|
||||||
|
|
||||||
fprintf(fd, "\nProgram parameters:\n");
|
fprintf(fd, "\nDisplay parameters:\n");
|
||||||
fprintf(fd, " -v <n> Verbosity level (default %d)\n",
|
fprintf(fd, " -v <n> Verbosity level (default %d)\n",
|
||||||
DEFAULT_VERBOSE);
|
DEFAULT_VERBOSE);
|
||||||
fprintf(fd, " -D <file> Run as a daemon, writing process ID to the given file\n");
|
|
||||||
|
|
||||||
fprintf(fd, "\nAllowed frame sizes (-f) are defined by the Opus codec. For example,\n"
|
fprintf(fd, "\nAllowed frame sizes (-f) are defined by the Opus codec. For example,\n"
|
||||||
"at 48000Hz the permitted values are 120, 240, 480 or 960.\n");
|
"at 48000Hz the permitted values are 120, 240, 480 or 960.\n");
|
||||||
@ -174,8 +157,7 @@ int main(int argc, char *argv[])
|
|||||||
|
|
||||||
/* command-line options */
|
/* command-line options */
|
||||||
const char *device = DEFAULT_DEVICE,
|
const char *device = DEFAULT_DEVICE,
|
||||||
*addr = DEFAULT_ADDR,
|
*addr = DEFAULT_ADDR;
|
||||||
*pid = NULL;
|
|
||||||
unsigned int buffer = DEFAULT_BUFFER,
|
unsigned int buffer = DEFAULT_BUFFER,
|
||||||
rate = DEFAULT_RATE,
|
rate = DEFAULT_RATE,
|
||||||
channels = DEFAULT_CHANNELS,
|
channels = DEFAULT_CHANNELS,
|
||||||
@ -188,7 +170,7 @@ int main(int argc, char *argv[])
|
|||||||
for (;;) {
|
for (;;) {
|
||||||
int c;
|
int c;
|
||||||
|
|
||||||
c = getopt(argc, argv, "b:c:d:f:h:m:p:r:v:D:");
|
c = getopt(argc, argv, "b:c:d:f:h:m:p:r:v:");
|
||||||
if (c == -1)
|
if (c == -1)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -220,9 +202,6 @@ int main(int argc, char *argv[])
|
|||||||
case 'v':
|
case 'v':
|
||||||
verbose = atoi(optarg);
|
verbose = atoi(optarg);
|
||||||
break;
|
break;
|
||||||
case 'D':
|
|
||||||
pid = optarg;
|
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
usage(stderr);
|
usage(stderr);
|
||||||
return -1;
|
return -1;
|
||||||
@ -245,6 +224,7 @@ int main(int argc, char *argv[])
|
|||||||
|
|
||||||
ortp_init();
|
ortp_init();
|
||||||
ortp_scheduler_init();
|
ortp_scheduler_init();
|
||||||
|
ortp_set_log_level_mask(ORTP_MESSAGE|ORTP_WARNING|ORTP_ERROR);
|
||||||
session = create_rtp_send(addr, port);
|
session = create_rtp_send(addr, port);
|
||||||
assert(session != NULL);
|
assert(session != NULL);
|
||||||
|
|
||||||
@ -258,9 +238,6 @@ int main(int argc, char *argv[])
|
|||||||
if (set_alsa_sw(snd) == -1)
|
if (set_alsa_sw(snd) == -1)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
if (pid)
|
|
||||||
go_daemon(pid);
|
|
||||||
|
|
||||||
go_realtime();
|
go_realtime();
|
||||||
r = run_tx(snd, channels, frame, encoder, bytes_per_frame,
|
r = run_tx(snd, channels, frame, encoder, bytes_per_frame,
|
||||||
ts_per_frame, session);
|
ts_per_frame, session);
|
||||||
|
Loading…
Reference in New Issue
Block a user