Commit 537fc537 authored by Sylvain Berfini's avatar Sylvain Berfini 🎩
Browse files

Merge branch 'dev_audio_bypass'

parents 61509e10 471f92ef
mediastreamer2 @ 12881553
Subproject commit c01ab3c2df96a2aad53edde9b4759344ed3a5bad
Subproject commit 128815537044ea17808a0a5843b2762364f8203d
......@@ -22,6 +22,7 @@
set(SOURCE_FILES
accountmanager.c
audio_bypass_tester.c
call_tester.c
complex_sip_call_tester.c
dtmf_tester.c
......
TESTER_SOUNDS = sounds/ahbahouaismaisbon.wav \
sounds/hello8000.wav \
sounds/hello44100.wav \
sounds/oldphone.wav \
sounds/sintel_trailer_opus_h264.mkv \
sounds/sintel_trailer_pcmu_h264.mkv \
......@@ -112,6 +113,7 @@ lib_LTLIBRARIES = liblinphonetester.la
liblinphonetester_la_SOURCES = \
accountmanager.c \
audio_bypass_tester.c \
call_tester.c \
complex_sip_call_tester.c \
dtmf_tester.c \
......
/*
liblinphone_tester - liblinphone test suite
Copyright (C) 2013 Belledonne Communications SARL
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "liblinphone_tester.h"
#include "private.h"
#include "audio_bypass_wav_header.h" // This is a copy of mediastreamer2/src/audiofilters/wav_header.h
/**********************************************************************
* This is a (simpler) copy of msfileplay filter in mediastreamer2 *
*********************************************************************/
struct _PlayerData{
int fd;
MSPlayerState state;
int rate;
int nchannels;
int hsize;
int loop_after;
int pause_time;
int count;
int samplesize;
char *mime;
uint32_t ts;
bool_t swap;
bool_t is_raw;
};
typedef struct _PlayerData PlayerData;
static void audio_bypass_snd_read_init(MSFilter *f) {
PlayerData *d=ms_new0(PlayerData,1);
d->fd=-1;
d->state=MSPlayerClosed;
d->swap=TRUE;
d->rate=44100;
d->nchannels=1;
d->samplesize=2;
d->mime = "L16";
d->hsize=0;
d->loop_after=-1; /*by default, don't loop*/
d->pause_time=0;
d->count=0;
d->ts=0;
d->is_raw=TRUE;
f->data=d;
}
int ms_read_wav_header_from_fd(wave_header_t *header,int fd){
int count;
int skip;
int hsize=0;
riff_t *riff_chunk=&header->riff_chunk;
format_t *format_chunk=&header->format_chunk;
data_t *data_chunk=&header->data_chunk;
unsigned long len=0;
len = read(fd, (char*)riff_chunk, sizeof(riff_t)) ;
if (len != sizeof(riff_t)){
goto not_a_wav;
}
if (0!=strncmp(riff_chunk->riff, "RIFF", 4) || 0!=strncmp(riff_chunk->wave, "WAVE", 4)){
goto not_a_wav;
}
len = read(fd, (char*)format_chunk, sizeof(format_t)) ;
if (len != sizeof(format_t)){
ms_warning("Wrong wav header: cannot read file");
goto not_a_wav;
}
if ((skip=le_uint32(format_chunk->len)-0x10)>0)
{
lseek(fd,skip,SEEK_CUR);
}
hsize=sizeof(wave_header_t)-0x10+le_uint32(format_chunk->len);
count=0;
do{
len = read(fd, data_chunk, sizeof(data_t)) ;
if (len != sizeof(data_t)){
ms_warning("Wrong wav header: cannot read file");
goto not_a_wav;
}
if (strncmp(data_chunk->data, "data", 4)!=0){
ms_warning("skipping chunk=%c%c%c%c len=%i", data_chunk->data[0],data_chunk->data[1],data_chunk->data[2],data_chunk->data[3], data_chunk->len);
lseek(fd,le_uint32(data_chunk->len),SEEK_CUR);
count++;
hsize+=len+le_uint32(data_chunk->len);
}else{
hsize+=len;
break;
}
}while(count<30);
return hsize;
not_a_wav:
/*rewind*/
lseek(fd,0,SEEK_SET);
return -1;
}
static int read_wav_header(PlayerData *d){
wave_header_t header;
format_t *format_chunk=&header.format_chunk;
int ret=ms_read_wav_header_from_fd(&header,d->fd);
d->samplesize=le_uint16(format_chunk->blockalign)/d->nchannels;
d->hsize=ret;
#ifdef WORDS_BIGENDIAN
if (le_uint16(format_chunk->blockalign)==le_uint16(format_chunk->channel) * 2)
d->swap=TRUE;
#endif
d->is_raw=FALSE;
return 0;
}
static void audio_bypass_snd_read_preprocess(MSFilter *f) {
PlayerData *d=(PlayerData*)f->data;
int fd;
const char *file=bc_tester_res("sounds/hello44100.wav");
if ((fd=open(file,O_RDONLY|O_BINARY))==-1){
ms_warning("MSFilePlayer[%p]: failed to open %s: %s",f,file,strerror(errno));
return;
}
d->state=MSPlayerPaused;
d->fd=fd;
d->ts=0;
if (read_wav_header(d)!=0 && strstr(file,".wav")){
ms_warning("File %s has .wav extension but wav header could be found.",file);
}
ms_filter_notify_no_arg(f,MS_FILTER_OUTPUT_FMT_CHANGED);
ms_message("MSFilePlayer[%p]: %s opened: rate=%i,channel=%i",f,file,d->rate,d->nchannels);
if (d->state==MSPlayerPaused)
d->state=MSPlayerPlaying;
return;
}
static void swap_bytes(unsigned char *bytes, int len){
int i;
unsigned char tmp;
for(i=0;i<len;i+=2){
tmp=bytes[i];
bytes[i]=bytes[i+1];
bytes[i+1]=tmp;
}
}
static void audio_bypass_snd_read_process(MSFilter *f) {
PlayerData *d=(PlayerData*)f->data;
int nsamples=(f->ticker->interval*d->rate*d->nchannels)/1000;
int bytes;
/*send an even number of samples each tick. At 22050Hz the number of samples per 10 ms chunk is odd.
Odd size buffer of samples cause troubles to alsa. Fixing in alsa is difficult, so workaround here.
*/
if (nsamples & 0x1 ) { //odd number of samples
if (d->count & 0x1 )
nsamples++;
else
nsamples--;
}
bytes=nsamples*d->samplesize;
d->count++;
ms_filter_lock(f);
if (d->state==MSPlayerPlaying){
{
int err;
mblk_t *om=allocb(bytes,0);
if (d->pause_time>0){
err=bytes;
memset(om->b_wptr,0,bytes);
d->pause_time-=f->ticker->interval;
}else{
err=read(d->fd,om->b_wptr,bytes);
if (d->swap) swap_bytes(om->b_wptr,bytes);
}
if (err>=0){
if (err!=0){
if (err<bytes)
memset(om->b_wptr+err,0,bytes-err);
om->b_wptr+=bytes;
mblk_set_timestamp_info(om,d->ts);
d->ts+=nsamples;
ms_queue_put(f->outputs[0],om);
}else freemsg(om);
if (err<bytes){
ms_filter_notify_no_arg(f,MS_PLAYER_EOF);
/*for compatibility:*/
lseek(d->fd,d->hsize,SEEK_SET);
/* special value for playing file only once */
if (d->loop_after<0)
{
d->state=MSPlayerPaused;
ms_filter_unlock(f);
return;
}
if (d->loop_after>=0){
d->pause_time=d->loop_after;
}
}
}else{
ms_warning("Fail to read %i bytes: %s",bytes,strerror(errno));
}
}
}
ms_filter_unlock(f);
}
static void audio_bypass_snd_read_postprocess(MSFilter *f) {
PlayerData *d=(PlayerData*)f->data;
ms_filter_lock(f);
if (d->state!=MSPlayerClosed){
d->state=MSPlayerPaused;
lseek(d->fd,d->hsize,SEEK_SET);
}
ms_filter_unlock(f);
if (d->fd!=-1) close(d->fd);
d->fd=-1;
d->state=MSPlayerClosed;
}
static void audio_bypass_snd_read_uninit(MSFilter *f) {
PlayerData *d=(PlayerData*)f->data;
ms_free(d);
}
static int audio_bypass_snd_read_set_sample_rate(MSFilter *f, void *arg) { // This is to prevent ms2 to put a resampler between this filter and the rtpsend
return 0;
}
static int audio_bypass_snd_read_set_nchannels(MSFilter *f, void *arg) { // This is to prevent ms2 to put a resampler between this filter and the rtpsend
return 0;
}
static int audio_bypass_snd_read_get_sample_rate(MSFilter *f, void *arg) {
int *sample_rate = (int *)arg;
*sample_rate = 44100;
return 0;
}
static int audio_bypass_snd_read_get_nchannels(MSFilter *f, void *arg) {
int *nchannels = (int *)arg;
*nchannels = 1;
return 0;
}
static int audio_bypass_snd_read_get_fmt(MSFilter *f, void *arg) {
MSPinFormat *pinFmt = (MSPinFormat *)arg;
pinFmt->fmt = ms_factory_get_audio_format(f->factory, "L16", 44100, 1, NULL);
return 0;
}
static MSFilterMethod audio_bypass_snd_read_methods[] = {
{ MS_FILTER_SET_SAMPLE_RATE, audio_bypass_snd_read_set_sample_rate },
{ MS_FILTER_SET_NCHANNELS, audio_bypass_snd_read_set_nchannels },
{ MS_FILTER_GET_SAMPLE_RATE, audio_bypass_snd_read_get_sample_rate },
{ MS_FILTER_GET_NCHANNELS, audio_bypass_snd_read_get_nchannels },
{ MS_FILTER_GET_OUTPUT_FMT, audio_bypass_snd_read_get_fmt },
{ 0, NULL }
};
MSFilterDesc audio_bypass_snd_read_desc = {
MS_FILTER_PLUGIN_ID,
"AudioBypassReader",
"audio bypass source",
MS_FILTER_OTHER,
NULL,
0,
1,
audio_bypass_snd_read_init,
audio_bypass_snd_read_preprocess,
audio_bypass_snd_read_process,
audio_bypass_snd_read_postprocess,
audio_bypass_snd_read_uninit,
audio_bypass_snd_read_methods
};
static void audio_bypass_snd_write_init(MSFilter *f) {
}
static void audio_bypass_snd_write_preprocess(MSFilter *f) {
}
static void audio_bypass_snd_write_process(MSFilter *f) {
mblk_t *m = ms_queue_get(f->inputs[0]);
ms_free(m);
}
static void audio_bypass_snd_write_postprocess(MSFilter *f) {
}
static void audio_bypass_snd_write_uninit(MSFilter *f) {
}
static int audio_bypass_snd_write_set_sample_rate(MSFilter *f, void *arg) { // This is to prevent ms2 to put a resampler between this filter and the rtprecv
return 0;
}
static int audio_bypass_snd_write_set_nchannels(MSFilter *f, void *arg) { // This is to prevent ms2 to put a resampler between this filter and the rtprecv
return 0;
}
static int audio_bypass_snd_write_get_sample_rate(MSFilter *f, void *arg) {
int *sample_rate = (int*)arg;
*sample_rate = 44100;
return 0;
}
static int audio_bypass_snd_write_get_nchannels(MSFilter *obj, void *arg) {
int *nchannels = (int*)arg;
*nchannels = 1;
return 0;
}
static int audio_bypass_snd_write_get_fmt(MSFilter *f, void *arg) {
MSPinFormat *pinFmt = (MSPinFormat *)arg;
pinFmt->fmt = ms_factory_get_audio_format(f->factory, "L16", 44100, 1, NULL);
return 0;
}
static MSFilterMethod audio_bypass_snd_write_methods[] = {
{ MS_FILTER_SET_SAMPLE_RATE, audio_bypass_snd_write_set_sample_rate },
{ MS_FILTER_SET_NCHANNELS, audio_bypass_snd_write_set_nchannels },
{ MS_FILTER_GET_SAMPLE_RATE, audio_bypass_snd_write_get_sample_rate },
{ MS_FILTER_GET_NCHANNELS, audio_bypass_snd_write_get_nchannels },
{ MS_FILTER_GET_OUTPUT_FMT, audio_bypass_snd_write_get_fmt },
{ 0, NULL }
};
MSFilterDesc audio_bypass_snd_write_desc = {
MS_FILTER_PLUGIN_ID,
"AudioBypassWriter",
"audio bypass output",
MS_FILTER_OTHER,
NULL,
1,
0,
audio_bypass_snd_write_init,
audio_bypass_snd_write_preprocess,
audio_bypass_snd_write_process,
audio_bypass_snd_write_postprocess,
audio_bypass_snd_write_uninit,
audio_bypass_snd_write_methods
};
static MSFilter* audio_bypass_snd_card_create_reader(MSSndCard *sndcard) {
MSFactory *factory = ms_snd_card_get_factory(sndcard);
MSFilter *f = ms_factory_create_filter_from_desc(factory, &audio_bypass_snd_read_desc);
return f;
}
static MSFilter* audio_bypass_snd_card_create_writer(MSSndCard *sndcard) {
MSFactory *factory = ms_snd_card_get_factory(sndcard);
MSFilter *f = ms_factory_create_filter_from_desc(factory, &audio_bypass_snd_write_desc);
return f;
}
static void audio_bypass_snd_card_detect(MSSndCardManager *m);
MSSndCardDesc audio_bypass_snd_card_desc = {
"audioBypass",
audio_bypass_snd_card_detect,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
audio_bypass_snd_card_create_reader,
audio_bypass_snd_card_create_writer,
NULL
};
static MSSndCard* create_audio_bypass_snd_card(void) {
MSSndCard* sndcard;
sndcard = ms_snd_card_new(&audio_bypass_snd_card_desc);
sndcard->data = NULL;
sndcard->name = ms_strdup("audio bypass sound card");
sndcard->capabilities = MS_SND_CARD_CAP_PLAYBACK | MS_SND_CARD_CAP_CAPTURE;
sndcard->latency = 0;
return sndcard;
}
#define AUDIO_BYPASS_SOUNDCARD "audioBypass: audio bypass sound card"
static void audio_bypass_snd_card_detect(MSSndCardManager *m) {
ms_snd_card_manager_add_card(m, create_audio_bypass_snd_card());
}
static void only_enable_payload(LinphoneCore *lc, const char *mime, int rate, int channels) {
const MSList *elem = linphone_core_get_audio_codecs(lc);
PayloadType *pt;
for(; elem != NULL; elem = elem->next) {
pt = (PayloadType *)elem->data;
linphone_core_enable_payload_type(lc, pt, FALSE);
}
pt = linphone_core_find_payload_type(lc, mime, rate, channels);
if (BC_ASSERT_PTR_NOT_NULL(pt)) {
linphone_core_enable_payload_type(lc, pt, TRUE);
}
}
static void audio_bypass(void) {
LinphoneCoreManager *marie = linphone_core_manager_new("marie_rc");
LinphoneCore *marie_lc = marie->lc;
MSFactory *marie_factory = linphone_core_get_ms_factory(marie_lc);
MSSndCardManager *marie_sndcard_manager = ms_factory_get_snd_card_manager(marie_factory);
LinphoneCoreManager *pauline = linphone_core_manager_new("pauline_rc");
LinphoneCore *pauline_lc = pauline->lc;
MSFactory *pauline_factory = linphone_core_get_ms_factory(pauline_lc);
MSSndCardManager *pauline_sndcard_manager = ms_factory_get_snd_card_manager(pauline_factory);
bool_t call_ok;
char *hellopath = bc_tester_res("sounds/hello44100.wav");
char *recordpath = bc_tester_file("audiobypass-record.wav");
double similar=1;
const double threshold = 0.85;
lp_config_set_string(marie_lc->config, "sound", "features", "None");
lp_config_set_string(pauline_lc->config, "sound", "features", "None");
/*make sure the record file doesn't already exists, otherwise this test will append new samples to it*/
unlink(recordpath);
// Enable L16 audio codec
only_enable_payload(marie_lc, "L16", 44100, 1);
only_enable_payload(pauline_lc, "L16", 44100, 1);
// Add our custom sound card
ms_snd_card_manager_register_desc(marie_sndcard_manager, &audio_bypass_snd_card_desc);
ms_snd_card_manager_register_desc(pauline_sndcard_manager, &audio_bypass_snd_card_desc);
linphone_core_reload_sound_devices(marie_lc);
linphone_core_reload_sound_devices(pauline_lc);
linphone_core_set_playback_device(marie_lc, AUDIO_BYPASS_SOUNDCARD);
linphone_core_set_playback_device(pauline_lc, AUDIO_BYPASS_SOUNDCARD);
linphone_core_set_capture_device(marie_lc, AUDIO_BYPASS_SOUNDCARD);
linphone_core_set_capture_device(pauline_lc, AUDIO_BYPASS_SOUNDCARD);
BC_ASSERT_STRING_EQUAL(linphone_core_get_capture_device(marie_lc), AUDIO_BYPASS_SOUNDCARD);
BC_ASSERT_STRING_EQUAL(linphone_core_get_capture_device(pauline_lc), AUDIO_BYPASS_SOUNDCARD);
BC_ASSERT_STRING_EQUAL(linphone_core_get_playback_device(marie_lc), AUDIO_BYPASS_SOUNDCARD);
BC_ASSERT_STRING_EQUAL(linphone_core_get_playback_device(pauline_lc), AUDIO_BYPASS_SOUNDCARD);
linphone_core_use_files(pauline_lc, TRUE);
linphone_core_set_play_file(pauline_lc, NULL);
linphone_core_set_record_file(pauline_lc, recordpath);
call_ok = call(marie, pauline);
BC_ASSERT_TRUE(call_ok);
if (!call_ok) goto end;
BC_ASSERT_STRING_EQUAL(linphone_call_params_get_used_audio_codec(linphone_call_get_current_params(linphone_core_get_current_call(marie_lc)))->mime_type, "L16");
wait_for_until(pauline_lc, marie_lc, NULL, 0, 22000); //hello44100.wav is 22 seconds long
end_call(marie, pauline);
BC_ASSERT_EQUAL(ms_audio_diff(hellopath, recordpath, &similar, &audio_cmp_params, NULL, NULL), 0, int, "%d");
BC_ASSERT_GREATER(similar, threshold, double, "%g");
BC_ASSERT_LOWER(similar, 1.0, double, "%g");
end:
bc_free(recordpath);
bc_free(hellopath);
linphone_core_manager_destroy(marie);
linphone_core_manager_destroy(pauline);
}
test_t audio_bypass_tests[] = {
TEST_NO_TAG("Audio Bypass", audio_bypass)
};
test_suite_t audio_bypass_suite = { "Audio Bypass", NULL, NULL,
liblinphone_tester_before_each, liblinphone_tester_after_each,
sizeof(audio_bypass_tests) / sizeof(audio_bypass_tests[0]), audio_bypass_tests };
/*
mediastreamer2 library - modular sound and video processing and streaming
Copyright (C) 2006 Simon MORLAT (simon.morlat@linphone.org)
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#ifndef waveheader_h
#define waveheader_h
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#ifdef _WIN32
# include <io.h>
# ifndef R_OK
# define R_OK 0x2
# endif
# ifndef W_OK
# define W_OK 0x6
# endif
# ifndef F_OK
# define F_OK 0x0
# endif
# ifndef S_IRUSR
# define S_IRUSR S_IREAD
# endif
# ifndef S_IWUSR
# define S_IWUSR S_IWRITE
# endif
# define open _open
# define read _read
# define write _write
# define close _close
# define access _access
# define lseek _lseek
#else /*_WIN32*/
# ifndef O_BINARY
# define O_BINARY 0
# endif
#endif /*!_WIN32*/
#ifdef swap16
#else
/* all integer in wav header must be read in least endian order */
static MS2_INLINE uint16_t swap16(uint16_t a)
{
return ((a & 0xFF) << 8) | ((a & 0xFF00) >> 8);
}
#endif
#ifdef swap32
#else
static MS2_INLINE uint32_t swap32(uint32_t a)
{
return ((a & 0xFF) << 24) | ((a & 0xFF00) << 8) |
((a & 0xFF0000) >> 8) | ((a & 0xFF000000) >> 24);
}
#endif
#ifdef WORDS_BIGENDIAN
#define le_uint32(a) (swap32((a)))
#define le_uint16(a) (swap16((a)))
#define le_int16(a) ( (int16_t) swap16((uint16_t)((a))) )
#else
#define le_uint32(a) (a)