2011-09-19 06:13:30 +02:00
/*
2013-01-09 18:30:01 +01:00
* Copyright ( C ) 2011 - 2013 Michael Niedermayer ( michaelni @ gmx . at )
2011-09-19 06:13:30 +02:00
*
* This file is part of libswresample
*
* libswresample is free software ; you can redistribute it and / or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation ; either
* version 2.1 of the License , or ( at your option ) any later version .
*
* libswresample 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
* Lesser General Public License for more details .
*
* You should have received a copy of the GNU Lesser General Public
* License along with libswresample ; if not , write to the Free Software
* Foundation , Inc . , 51 Franklin Street , Fifth Floor , Boston , MA 02110 - 1301 USA
*/
# include "libavutil/opt.h"
# include "swresample_internal.h"
# include "audioconvert.h"
# include "libavutil/avassert.h"
2012-11-13 14:16:48 +01:00
# include "libavutil/channel_layout.h"
2011-09-19 06:13:30 +02:00
2012-05-19 18:44:34 +02:00
# include <float.h>
2012-05-19 17:45:12 +02:00
# define ALIGN 32
2011-09-19 06:13:30 +02:00
2014-12-19 18:04:40 +01:00
# include "libavutil/ffversion.h"
const char swr_ffversion [ ] = " FFmpeg version " FFMPEG_VERSION ;
2011-12-22 03:08:47 +01:00
unsigned swresample_version ( void )
{
2011-12-22 03:09:41 +01:00
av_assert0 ( LIBSWRESAMPLE_VERSION_MICRO > = 100 ) ;
2011-12-23 23:26:02 +01:00
return LIBSWRESAMPLE_VERSION_INT ;
2011-12-22 03:08:47 +01:00
}
const char * swresample_configuration ( void )
{
return FFMPEG_CONFIGURATION ;
}
const char * swresample_license ( void )
{
# define LICENSE_PREFIX "libswresample license: "
return LICENSE_PREFIX FFMPEG_LICENSE + sizeof ( LICENSE_PREFIX ) - 1 ;
}
2011-11-17 15:06:35 +01:00
int swr_set_channel_mapping ( struct SwrContext * s , const int * channel_map ) {
if ( ! s | | s - > in_convert ) // s needs to be allocated but not initialized
return AVERROR ( EINVAL ) ;
s - > channel_map = channel_map ;
return 0 ;
}
2011-11-16 19:50:33 +01:00
struct SwrContext * swr_alloc_set_opts ( struct SwrContext * s ,
int64_t out_ch_layout , enum AVSampleFormat out_sample_fmt , int out_sample_rate ,
int64_t in_ch_layout , enum AVSampleFormat in_sample_fmt , int in_sample_rate ,
2011-11-17 15:06:35 +01:00
int log_offset , void * log_ctx ) {
2011-09-19 06:13:30 +02:00
if ( ! s ) s = swr_alloc ( ) ;
if ( ! s ) return NULL ;
s - > log_level_offset = log_offset ;
s - > log_ctx = log_ctx ;
2014-08-11 01:34:34 +02:00
if ( av_opt_set_int ( s , " ocl " , out_ch_layout , 0 ) < 0 )
goto fail ;
if ( av_opt_set_int ( s , " osf " , out_sample_fmt , 0 ) < 0 )
goto fail ;
if ( av_opt_set_int ( s , " osr " , out_sample_rate , 0 ) < 0 )
goto fail ;
if ( av_opt_set_int ( s , " icl " , in_ch_layout , 0 ) < 0 )
goto fail ;
if ( av_opt_set_int ( s , " isf " , in_sample_fmt , 0 ) < 0 )
goto fail ;
if ( av_opt_set_int ( s , " isr " , in_sample_rate , 0 ) < 0 )
goto fail ;
if ( av_opt_set_int ( s , " tsf " , AV_SAMPLE_FMT_NONE , 0 ) < 0 )
goto fail ;
if ( av_opt_set_int ( s , " ich " , av_get_channel_layout_nb_channels ( s - > in_ch_layout ) , 0 ) < 0 )
goto fail ;
if ( av_opt_set_int ( s , " och " , av_get_channel_layout_nb_channels ( s - > out_ch_layout ) , 0 ) < 0 )
goto fail ;
2011-11-24 15:37:30 +01:00
av_opt_set_int ( s , " uch " , 0 , 0 ) ;
2011-09-19 06:13:30 +02:00
return s ;
2014-08-11 01:34:34 +02:00
fail :
av_log ( s , AV_LOG_ERROR , " Failed to set option \n " ) ;
swr_free ( & s ) ;
return NULL ;
2011-09-19 06:13:30 +02:00
}
2012-04-29 15:29:28 +02:00
static void set_audiodata_fmt ( AudioData * a , enum AVSampleFormat fmt ) {
2012-04-29 15:30:07 +02:00
a - > fmt = fmt ;
2012-04-29 15:29:28 +02:00
a - > bps = av_get_bytes_per_sample ( fmt ) ;
a - > planar = av_sample_fmt_is_planar ( fmt ) ;
2014-08-04 03:05:05 +02:00
if ( a - > ch_count = = 1 )
a - > planar = 1 ;
2012-04-29 15:29:28 +02:00
}
2011-09-19 06:13:30 +02:00
static void free_temp ( AudioData * a ) {
av_free ( a - > data ) ;
memset ( a , 0 , sizeof ( * a ) ) ;
}
2014-02-22 21:36:30 +01:00
static void clear_context ( SwrContext * s ) {
2011-09-19 06:13:30 +02:00
s - > in_buffer_index = 0 ;
s - > in_buffer_count = 0 ;
s - > resample_in_constraint = 0 ;
2014-02-22 21:36:30 +01:00
memset ( s - > in . ch , 0 , sizeof ( s - > in . ch ) ) ;
memset ( s - > out . ch , 0 , sizeof ( s - > out . ch ) ) ;
2011-09-19 06:13:30 +02:00
free_temp ( & s - > postin ) ;
free_temp ( & s - > midbuf ) ;
free_temp ( & s - > preout ) ;
free_temp ( & s - > in_buffer ) ;
2013-01-13 15:26:04 +01:00
free_temp ( & s - > silence ) ;
2013-01-13 15:21:33 +01:00
free_temp ( & s - > drop_temp ) ;
2013-01-09 18:41:12 +01:00
free_temp ( & s - > dither . noise ) ;
2013-01-10 18:51:30 +01:00
free_temp ( & s - > dither . temp ) ;
2011-11-16 08:00:31 +01:00
swri_audio_convert_free ( & s - > in_convert ) ;
swri_audio_convert_free ( & s - > out_convert ) ;
swri_audio_convert_free ( & s - > full_convert ) ;
2012-05-01 20:19:28 +02:00
swri_rematrix_free ( s ) ;
2011-09-19 06:13:30 +02:00
2012-03-25 05:58:31 +02:00
s - > flushed = 0 ;
2014-02-22 21:36:30 +01:00
}
av_cold void swr_free ( SwrContext * * ss ) {
SwrContext * s = * ss ;
if ( s ) {
clear_context ( s ) ;
if ( s - > resampler )
s - > resampler - > free ( & s - > resample ) ;
}
av_freep ( ss ) ;
}
2014-05-15 18:27:23 +02:00
av_cold void swr_close ( SwrContext * s ) {
clear_context ( s ) ;
}
2014-02-22 21:36:30 +01:00
av_cold int swr_init ( struct SwrContext * s ) {
int ret ;
clear_context ( s ) ;
2012-03-25 05:58:31 +02:00
2011-10-01 01:39:17 +02:00
if ( s - > in_sample_fmt > = AV_SAMPLE_FMT_NB ) {
2011-11-24 16:05:14 +01:00
av_log ( s , AV_LOG_ERROR , " Requested input sample format %d is invalid \n " , s - > in_sample_fmt ) ;
2011-10-01 01:39:17 +02:00
return AVERROR ( EINVAL ) ;
}
if ( s - > out_sample_fmt > = AV_SAMPLE_FMT_NB ) {
2011-11-24 16:05:14 +01:00
av_log ( s , AV_LOG_ERROR , " Requested output sample format %d is invalid \n " , s - > out_sample_fmt ) ;
2011-10-01 01:39:17 +02:00
return AVERROR ( EINVAL ) ;
}
2015-04-12 19:43:08 +02:00
s - > out . ch_count = s - > user_out_ch_count ;
s - > in . ch_count = s - > user_in_ch_count ;
s - > used_ch_count = s - > user_used_ch_count ;
2013-02-13 17:49:16 +01:00
if ( av_get_channel_layout_nb_channels ( s - > in_ch_layout ) > SWR_CH_MAX ) {
av_log ( s , AV_LOG_WARNING , " Input channel layout 0x% " PRIx64 " is invalid or unsupported. \n " , s - > in_ch_layout ) ;
s - > in_ch_layout = 0 ;
}
if ( av_get_channel_layout_nb_channels ( s - > out_ch_layout ) > SWR_CH_MAX ) {
av_log ( s , AV_LOG_WARNING , " Output channel layout 0x% " PRIx64 " is invalid or unsupported. \n " , s - > out_ch_layout ) ;
s - > out_ch_layout = 0 ;
}
2013-02-04 04:00:12 +01:00
switch ( s - > engine ) {
# if CONFIG_LIBSOXR
2015-02-27 19:20:43 +01:00
case SWR_ENGINE_SOXR : s - > resampler = & swri_soxr_resampler ; break ;
2013-02-04 04:00:12 +01:00
# endif
case SWR_ENGINE_SWR : s - > resampler = & swri_resampler ; break ;
default :
av_log ( s , AV_LOG_ERROR , " Requested resampling engine is unavailable \n " ) ;
return AVERROR ( EINVAL ) ;
}
if ( ! s - > used_ch_count )
s - > used_ch_count = s - > in . ch_count ;
if ( s - > used_ch_count & & s - > in_ch_layout & & s - > used_ch_count ! = av_get_channel_layout_nb_channels ( s - > in_ch_layout ) ) {
av_log ( s , AV_LOG_WARNING , " Input channel layout has a different number of channels than the number of used channels, ignoring layout \n " ) ;
s - > in_ch_layout = 0 ;
}
if ( ! s - > in_ch_layout )
s - > in_ch_layout = av_get_default_channel_layout ( s - > used_ch_count ) ;
if ( ! s - > out_ch_layout )
s - > out_ch_layout = av_get_default_channel_layout ( s - > out . ch_count ) ;
s - > rematrix = s - > out_ch_layout ! = s - > in_ch_layout | | s - > rematrix_volume ! = 1.0 | |
s - > rematrix_custom ;
2012-05-02 00:51:06 +02:00
if ( s - > int_sample_fmt = = AV_SAMPLE_FMT_NONE ) {
if ( av_get_planar_sample_fmt ( s - > in_sample_fmt ) < = AV_SAMPLE_FMT_S16P ) {
s - > int_sample_fmt = AV_SAMPLE_FMT_S16P ;
2013-02-04 02:36:33 +01:00
} else if ( av_get_planar_sample_fmt ( s - > in_sample_fmt ) = = AV_SAMPLE_FMT_S32P
& & av_get_planar_sample_fmt ( s - > out_sample_fmt ) = = AV_SAMPLE_FMT_S32P
& & ! s - > rematrix
& & s - > engine ! = SWR_ENGINE_SOXR ) {
s - > int_sample_fmt = AV_SAMPLE_FMT_S32P ;
2012-05-02 00:51:06 +02:00
} else if ( av_get_planar_sample_fmt ( s - > in_sample_fmt ) < = AV_SAMPLE_FMT_FLTP ) {
s - > int_sample_fmt = AV_SAMPLE_FMT_FLTP ;
} else {
2012-06-01 08:06:36 +02:00
av_log ( s , AV_LOG_DEBUG , " Using double precision mode \n " ) ;
2012-05-02 00:51:06 +02:00
s - > int_sample_fmt = AV_SAMPLE_FMT_DBLP ;
}
}
2011-09-19 06:13:30 +02:00
2012-04-28 11:19:22 +02:00
if ( s - > int_sample_fmt ! = AV_SAMPLE_FMT_S16P
& & s - > int_sample_fmt ! = AV_SAMPLE_FMT_S32P
2012-05-02 00:50:00 +02:00
& & s - > int_sample_fmt ! = AV_SAMPLE_FMT_FLTP
& & s - > int_sample_fmt ! = AV_SAMPLE_FMT_DBLP ) {
av_log ( s , AV_LOG_ERROR , " Requested sample format %s is not supported internally, S16/S32/FLT/DBL is supported \n " , av_get_sample_fmt_name ( s - > int_sample_fmt ) ) ;
2012-04-10 13:19:29 +02:00
return AVERROR ( EINVAL ) ;
}
2011-09-19 06:13:30 +02:00
2012-04-29 15:29:28 +02:00
set_audiodata_fmt ( & s - > in , s - > in_sample_fmt ) ;
set_audiodata_fmt ( & s - > out , s - > out_sample_fmt ) ;
2013-01-21 00:52:14 +01:00
if ( s - > firstpts_in_samples ! = AV_NOPTS_VALUE ) {
if ( ! s - > async & & s - > min_compensation > = FLT_MAX / 2 )
s - > async = 1 ;
s - > firstpts =
s - > outpts = s - > firstpts_in_samples * s - > out_sample_rate ;
2013-02-25 03:04:03 +01:00
} else
s - > firstpts = AV_NOPTS_VALUE ;
2013-01-21 00:52:14 +01:00
2012-12-22 23:53:45 +01:00
if ( s - > async ) {
if ( s - > min_compensation > = FLT_MAX / 2 )
s - > min_compensation = 0.001 ;
if ( s - > async > 1.0001 ) {
s - > max_soft_compensation = s - > async / ( double ) s - > in_sample_rate ;
}
}
2011-09-19 06:13:30 +02:00
if ( s - > out_sample_rate ! = s - > in_sample_rate | | ( s - > flags & SWR_FLAG_RESAMPLE ) ) {
2012-12-11 21:43:42 +01:00
s - > resample = s - > resampler - > init ( s - > resample , s - > out_sample_rate , s - > in_sample_rate , s - > filter_size , s - > phase_shift , s - > linear_interp , s - > cutoff , s - > int_sample_fmt , s - > filter_type , s - > kaiser_beta , s - > precision , s - > cheby ) ;
2011-09-19 06:13:30 +02:00
} else
2012-12-11 18:36:58 +01:00
s - > resampler - > free ( & s - > resample ) ;
2012-04-28 11:19:22 +02:00
if ( s - > int_sample_fmt ! = AV_SAMPLE_FMT_S16P
& & s - > int_sample_fmt ! = AV_SAMPLE_FMT_S32P
& & s - > int_sample_fmt ! = AV_SAMPLE_FMT_FLTP
2012-05-02 00:48:44 +02:00
& & s - > int_sample_fmt ! = AV_SAMPLE_FMT_DBLP
2012-04-10 13:19:29 +02:00
& & s - > resample ) {
2012-05-02 00:48:44 +02:00
av_log ( s , AV_LOG_ERROR , " Resampling only supported with internal s16/s32/flt/dbl \n " ) ;
2011-09-19 06:13:30 +02:00
return - 1 ;
}
# define RSC 1 //FIXME finetune
if ( ! s - > in . ch_count )
s - > in . ch_count = av_get_channel_layout_nb_channels ( s - > in_ch_layout ) ;
2011-11-04 18:54:01 +01:00
if ( ! s - > used_ch_count )
s - > used_ch_count = s - > in . ch_count ;
2011-09-19 06:13:30 +02:00
if ( ! s - > out . ch_count )
s - > out . ch_count = av_get_channel_layout_nb_channels ( s - > out_ch_layout ) ;
2011-12-20 11:23:46 +01:00
if ( ! s - > in . ch_count ) {
av_assert0 ( ! s - > in_ch_layout ) ;
av_log ( s , AV_LOG_ERROR , " Input channel count and layout are unset \n " ) ;
return - 1 ;
}
2012-03-23 12:10:08 +01:00
if ( ( ! s - > out_ch_layout | | ! s - > in_ch_layout ) & & s - > used_ch_count ! = s - > out . ch_count & & ! s - > rematrix_custom ) {
2012-12-31 12:12:23 +01:00
char l1 [ 1024 ] , l2 [ 1024 ] ;
av_get_channel_layout_string ( l1 , sizeof ( l1 ) , s - > in . ch_count , s - > in_ch_layout ) ;
av_get_channel_layout_string ( l2 , sizeof ( l2 ) , s - > out . ch_count , s - > out_ch_layout ) ;
av_log ( s , AV_LOG_ERROR , " Rematrix is needed between %s and %s "
" but there is not enough information to do it \n " , l1 , l2 ) ;
2012-03-23 12:10:08 +01:00
return - 1 ;
}
2011-11-04 18:54:01 +01:00
av_assert0 ( s - > used_ch_count ) ;
2011-09-19 06:13:30 +02:00
av_assert0 ( s - > out . ch_count ) ;
s - > resample_first = RSC * s - > out . ch_count / s - > in . ch_count - RSC < s - > out_sample_rate / ( float ) s - > in_sample_rate - 1.0 ;
2012-03-25 21:04:48 +02:00
s - > in_buffer = s - > in ;
2013-01-13 15:26:04 +01:00
s - > silence = s - > in ;
2013-01-13 15:21:33 +01:00
s - > drop_temp = s - > out ;
2011-09-19 06:13:30 +02:00
2013-01-09 18:41:12 +01:00
if ( ! s - > resample & & ! s - > rematrix & & ! s - > channel_map & & ! s - > dither . method ) {
2011-11-16 08:00:31 +01:00
s - > full_convert = swri_audio_convert_alloc ( s - > out_sample_fmt ,
s - > in_sample_fmt , s - > in . ch_count , NULL , 0 ) ;
2011-10-05 23:46:50 +02:00
return 0 ;
}
2011-11-16 08:00:31 +01:00
s - > in_convert = swri_audio_convert_alloc ( s - > int_sample_fmt ,
s - > in_sample_fmt , s - > used_ch_count , s - > channel_map , 0 ) ;
s - > out_convert = swri_audio_convert_alloc ( s - > out_sample_fmt ,
s - > int_sample_fmt , s - > out . ch_count , NULL , 0 ) ;
2011-09-19 06:13:30 +02:00
2013-01-10 20:26:28 +01:00
if ( ! s - > in_convert | | ! s - > out_convert )
return AVERROR ( ENOMEM ) ;
2011-09-19 06:13:30 +02:00
s - > postin = s - > in ;
s - > preout = s - > out ;
s - > midbuf = s - > in ;
2012-03-25 21:04:48 +02:00
2011-11-04 18:54:01 +01:00
if ( s - > channel_map ) {
s - > postin . ch_count =
2012-03-25 21:04:48 +02:00
s - > midbuf . ch_count = s - > used_ch_count ;
if ( s - > resample )
s - > in_buffer . ch_count = s - > used_ch_count ;
2011-11-04 18:54:01 +01:00
}
2011-09-19 06:13:30 +02:00
if ( ! s - > resample_first ) {
s - > midbuf . ch_count = s - > out . ch_count ;
2012-03-25 21:04:48 +02:00
if ( s - > resample )
s - > in_buffer . ch_count = s - > out . ch_count ;
2011-09-19 06:13:30 +02:00
}
2012-04-29 15:29:28 +02:00
set_audiodata_fmt ( & s - > postin , s - > int_sample_fmt ) ;
set_audiodata_fmt ( & s - > midbuf , s - > int_sample_fmt ) ;
set_audiodata_fmt ( & s - > preout , s - > int_sample_fmt ) ;
2011-09-19 06:13:30 +02:00
2012-03-25 21:04:48 +02:00
if ( s - > resample ) {
2012-04-29 15:29:28 +02:00
set_audiodata_fmt ( & s - > in_buffer , s - > int_sample_fmt ) ;
2012-03-25 21:04:48 +02:00
}
2011-09-19 06:13:30 +02:00
2013-01-09 22:46:32 +01:00
if ( ( ret = swri_dither_init ( s , s - > out_sample_fmt , s - > int_sample_fmt ) ) < 0 )
return ret ;
2012-04-10 19:52:42 +02:00
2013-01-09 18:41:12 +01:00
if ( s - > rematrix | | s - > dither . method )
2011-11-17 11:20:50 +01:00
return swri_rematrix_init ( s ) ;
2011-09-19 06:13:30 +02:00
return 0 ;
}
2012-12-11 21:15:19 +01:00
int swri_realloc_audio ( AudioData * a , int count ) {
2011-09-19 06:13:30 +02:00
int i , countb ;
AudioData old ;
2012-05-22 17:15:07 +02:00
if ( count < 0 | | count > INT_MAX / 2 / a - > bps / a - > ch_count )
return AVERROR ( EINVAL ) ;
2011-09-19 06:13:30 +02:00
if ( a - > count > = count )
return 0 ;
count * = 2 ;
2012-05-19 17:45:12 +02:00
countb = FFALIGN ( count * a - > bps , ALIGN ) ;
2011-09-19 06:13:30 +02:00
old = * a ;
av_assert0 ( a - > bps ) ;
av_assert0 ( a - > ch_count ) ;
2015-03-30 22:59:09 +02:00
a - > data = av_mallocz_array ( countb , a - > ch_count ) ;
2011-09-19 06:13:30 +02:00
if ( ! a - > data )
return AVERROR ( ENOMEM ) ;
for ( i = 0 ; i < a - > ch_count ; i + + ) {
a - > ch [ i ] = a - > data + i * ( a - > planar ? countb : a - > bps ) ;
if ( a - > planar ) memcpy ( a - > ch [ i ] , old . ch [ i ] , a - > count * a - > bps ) ;
}
2012-03-25 21:50:00 +02:00
if ( ! a - > planar ) memcpy ( a - > ch [ 0 ] , old . ch [ 0 ] , a - > count * a - > ch_count * a - > bps ) ;
2013-09-16 22:44:15 +02:00
av_freep ( & old . data ) ;
2011-09-19 06:13:30 +02:00
a - > count = count ;
return 1 ;
}
static void copy ( AudioData * out , AudioData * in ,
int count ) {
av_assert0 ( out - > planar = = in - > planar ) ;
av_assert0 ( out - > bps = = in - > bps ) ;
av_assert0 ( out - > ch_count = = in - > ch_count ) ;
if ( out - > planar ) {
int ch ;
for ( ch = 0 ; ch < out - > ch_count ; ch + + )
memcpy ( out - > ch [ ch ] , in - > ch [ ch ] , count * out - > bps ) ;
} else
memcpy ( out - > ch [ 0 ] , in - > ch [ 0 ] , count * out - > ch_count * out - > bps ) ;
}
2011-09-29 04:53:50 +02:00
static void fill_audiodata ( AudioData * out , uint8_t * in_arg [ SWR_CH_MAX ] ) {
int i ;
2012-05-19 17:44:40 +02:00
if ( ! in_arg ) {
memset ( out - > ch , 0 , sizeof ( out - > ch ) ) ;
} else if ( out - > planar ) {
2011-09-29 04:53:50 +02:00
for ( i = 0 ; i < out - > ch_count ; i + + )
out - > ch [ i ] = in_arg [ i ] ;
} else {
for ( i = 0 ; i < out - > ch_count ; i + + )
out - > ch [ i ] = in_arg [ 0 ] + i * out - > bps ;
}
}
2012-05-19 17:46:41 +02:00
static void reversefill_audiodata ( AudioData * out , uint8_t * in_arg [ SWR_CH_MAX ] ) {
int i ;
if ( out - > planar ) {
for ( i = 0 ; i < out - > ch_count ; i + + )
in_arg [ i ] = out - > ch [ i ] ;
} else {
in_arg [ 0 ] = out - > ch [ 0 ] ;
}
}
2011-11-17 15:09:52 +01:00
/**
*
* out may be equal in .
*/
static void buf_set ( AudioData * out , AudioData * in , int count ) {
2012-04-20 23:06:25 +02:00
int ch ;
2011-11-17 15:09:52 +01:00
if ( in - > planar ) {
for ( ch = 0 ; ch < out - > ch_count ; ch + + )
out - > ch [ ch ] = in - > ch [ ch ] + count * out - > bps ;
2012-04-20 23:06:25 +02:00
} else {
2012-05-19 17:45:41 +02:00
for ( ch = out - > ch_count - 1 ; ch > = 0 ; ch - - )
2012-04-20 23:06:25 +02:00
out - > ch [ ch ] = in - > ch [ 0 ] + ( ch + count * out - > ch_count ) * out - > bps ;
}
2011-11-17 15:09:52 +01:00
}
/**
*
* @ return number of samples output per channel
*/
static int resample ( SwrContext * s , AudioData * out_param , int out_count ,
const AudioData * in_param , int in_count ) {
AudioData in , out , tmp ;
int ret_sum = 0 ;
int border = 0 ;
2013-12-04 20:25:43 +01:00
int padless = ARCH_X86 & & s - > engine = = SWR_ENGINE_SWR ? 7 : 0 ;
2011-11-17 15:09:52 +01:00
2012-05-19 17:47:06 +02:00
av_assert1 ( s - > in_buffer . ch_count = = in_param - > ch_count ) ;
av_assert1 ( s - > in_buffer . planar = = in_param - > planar ) ;
av_assert1 ( s - > in_buffer . fmt = = in_param - > fmt ) ;
2011-11-17 15:09:52 +01:00
tmp = out = * out_param ;
in = * in_param ;
2014-06-14 01:06:30 +02:00
border = s - > resampler - > invert_initial_buffer ( s - > resample , & s - > in_buffer ,
& in , in_count , & s - > in_buffer_index , & s - > in_buffer_count ) ;
2014-10-16 18:18:40 +02:00
if ( border = = INT_MAX ) {
return 0 ;
} else if ( border < 0 ) {
return border ;
} else if ( border ) {
buf_set ( & in , & in , border ) ;
in_count - = border ;
s - > resample_in_constraint = 0 ;
}
2014-06-14 01:06:30 +02:00
2011-11-17 15:09:52 +01:00
do {
int ret , size , consumed ;
if ( ! s - > resample_in_constraint & & s - > in_buffer_count ) {
buf_set ( & tmp , & s - > in_buffer , s - > in_buffer_index ) ;
2012-12-11 18:36:58 +01:00
ret = s - > resampler - > multiple_resample ( s - > resample , & out , out_count , & tmp , s - > in_buffer_count , & consumed ) ;
2011-11-17 15:09:52 +01:00
out_count - = ret ;
ret_sum + = ret ;
buf_set ( & out , & out , ret ) ;
s - > in_buffer_count - = consumed ;
s - > in_buffer_index + = consumed ;
if ( ! in_count )
break ;
if ( s - > in_buffer_count < = border ) {
buf_set ( & in , & in , - s - > in_buffer_count ) ;
in_count + = s - > in_buffer_count ;
s - > in_buffer_count = 0 ;
s - > in_buffer_index = 0 ;
border = 0 ;
}
}
2013-12-04 20:25:43 +01:00
if ( ( s - > flushed | | in_count > padless ) & & ! s - > in_buffer_count ) {
2011-11-17 15:09:52 +01:00
s - > in_buffer_index = 0 ;
2013-12-04 20:25:43 +01:00
ret = s - > resampler - > multiple_resample ( s - > resample , & out , out_count , & in , FFMAX ( in_count - padless , 0 ) , & consumed ) ;
2011-11-17 15:09:52 +01:00
out_count - = ret ;
ret_sum + = ret ;
buf_set ( & out , & out , ret ) ;
in_count - = consumed ;
buf_set ( & in , & in , consumed ) ;
}
//TODO is this check sane considering the advanced copy avoidance below
size = s - > in_buffer_index + s - > in_buffer_count + in_count ;
if ( size > s - > in_buffer . count
& & s - > in_buffer_count + in_count < = s - > in_buffer_index ) {
buf_set ( & tmp , & s - > in_buffer , s - > in_buffer_index ) ;
copy ( & s - > in_buffer , & tmp , s - > in_buffer_count ) ;
s - > in_buffer_index = 0 ;
} else
2012-12-11 21:15:19 +01:00
if ( ( ret = swri_realloc_audio ( & s - > in_buffer , size ) ) < 0 )
2011-11-17 15:09:52 +01:00
return ret ;
if ( in_count ) {
int count = in_count ;
if ( s - > in_buffer_count & & s - > in_buffer_count + 2 < count & & out_count ) count = s - > in_buffer_count + 2 ;
buf_set ( & tmp , & s - > in_buffer , s - > in_buffer_index + s - > in_buffer_count ) ;
copy ( & tmp , & in , /*in_*/ count ) ;
s - > in_buffer_count + = count ;
in_count - = count ;
border + = count ;
buf_set ( & in , & in , count ) ;
s - > resample_in_constraint = 0 ;
if ( s - > in_buffer_count ! = count | | in_count )
continue ;
2013-12-04 20:25:43 +01:00
if ( padless ) {
padless = 0 ;
continue ;
}
2011-11-17 15:09:52 +01:00
}
break ;
} while ( 1 ) ;
s - > resample_in_constraint = ! ! out_count ;
return ret_sum ;
}
2012-04-01 22:10:40 +02:00
static int swr_convert_internal ( struct SwrContext * s , AudioData * out , int out_count ,
AudioData * in , int in_count ) {
2011-09-19 06:13:30 +02:00
AudioData * postin , * midbuf , * preout ;
2011-10-21 00:34:37 +02:00
int ret /*, in_max*/ ;
2011-09-19 06:13:30 +02:00
AudioData preout_tmp , midbuf_tmp ;
2011-10-05 23:46:50 +02:00
if ( s - > full_convert ) {
av_assert0 ( ! s - > resample ) ;
2011-11-16 08:00:31 +01:00
swri_audio_convert ( s - > full_convert , out , in , in_count ) ;
2011-10-05 23:46:50 +02:00
return out_count ;
}
2011-09-19 06:13:30 +02:00
// in_max= out_count*(int64_t)s->in_sample_rate / s->out_sample_rate + resample_filter_taps;
// in_count= FFMIN(in_count, in_in + 2 - s->hist_buffer_count);
2012-12-11 21:15:19 +01:00
if ( ( ret = swri_realloc_audio ( & s - > postin , in_count ) ) < 0 )
2011-09-19 06:13:30 +02:00
return ret ;
if ( s - > resample_first ) {
2011-11-04 18:54:01 +01:00
av_assert0 ( s - > midbuf . ch_count = = s - > used_ch_count ) ;
2012-12-11 21:15:19 +01:00
if ( ( ret = swri_realloc_audio ( & s - > midbuf , out_count ) ) < 0 )
2011-09-19 06:13:30 +02:00
return ret ;
} else {
av_assert0 ( s - > midbuf . ch_count = = s - > out . ch_count ) ;
2012-12-11 21:15:19 +01:00
if ( ( ret = swri_realloc_audio ( & s - > midbuf , in_count ) ) < 0 )
2011-09-19 06:13:30 +02:00
return ret ;
}
2012-12-11 21:15:19 +01:00
if ( ( ret = swri_realloc_audio ( & s - > preout , out_count ) ) < 0 )
2011-09-19 06:13:30 +02:00
return ret ;
postin = & s - > postin ;
midbuf_tmp = s - > midbuf ;
midbuf = & midbuf_tmp ;
preout_tmp = s - > preout ;
preout = & preout_tmp ;
2012-07-16 14:18:19 +02:00
if ( s - > int_sample_fmt = = s - > in_sample_fmt & & s - > in . planar & & ! s - > channel_map )
2011-09-19 06:13:30 +02:00
postin = in ;
if ( s - > resample_first ? ! s - > resample : ! s - > rematrix )
midbuf = postin ;
if ( s - > resample_first ? ! s - > rematrix : ! s - > resample )
preout = midbuf ;
2013-06-05 02:49:00 +02:00
if ( s - > int_sample_fmt = = s - > out_sample_fmt & & s - > out . planar
& & ! ( s - > out_sample_fmt = = AV_SAMPLE_FMT_S32P & & ( s - > dither . output_sample_bits & 31 ) ) ) {
2011-09-19 06:13:30 +02:00
if ( preout = = in ) {
2012-03-12 23:30:13 +01:00
out_count = FFMIN ( out_count , in_count ) ; //TODO check at the end if this is needed or redundant
2011-09-19 06:13:30 +02:00
av_assert0 ( s - > in . planar ) ; //we only support planar internally so it has to be, we support copying non planar though
copy ( out , in , out_count ) ;
return out_count ;
}
else if ( preout = = postin ) preout = midbuf = postin = out ;
else if ( preout = = midbuf ) preout = midbuf = out ;
else preout = out ;
}
if ( in ! = postin ) {
2011-11-16 08:00:31 +01:00
swri_audio_convert ( s - > in_convert , postin , in , in_count ) ;
2011-09-19 06:13:30 +02:00
}
if ( s - > resample_first ) {
if ( postin ! = midbuf )
out_count = resample ( s , midbuf , out_count , postin , in_count ) ;
if ( midbuf ! = preout )
2011-11-16 08:12:48 +01:00
swri_rematrix ( s , preout , midbuf , out_count , preout = = out ) ;
2011-09-19 06:13:30 +02:00
} else {
if ( postin ! = midbuf )
2011-11-16 08:12:48 +01:00
swri_rematrix ( s , midbuf , postin , in_count , midbuf = = out ) ;
2011-09-19 06:13:30 +02:00
if ( midbuf ! = preout )
out_count = resample ( s , preout , out_count , midbuf , in_count ) ;
}
2012-03-25 11:48:09 +02:00
if ( preout ! = out & & out_count ) {
2013-01-10 18:51:30 +01:00
AudioData * conv_src = preout ;
2013-01-09 18:41:12 +01:00
if ( s - > dither . method ) {
2013-01-09 18:41:40 +01:00
int ch ;
2012-04-11 13:26:32 +02:00
int dither_count = FFMAX ( out_count , 1 < < 16 ) ;
2013-01-10 18:51:30 +01:00
if ( preout = = in ) {
conv_src = & s - > dither . temp ;
if ( ( ret = swri_realloc_audio ( & s - > dither . temp , dither_count ) ) < 0 )
return ret ;
}
2012-04-10 19:52:42 +02:00
2013-01-09 18:41:12 +01:00
if ( ( ret = swri_realloc_audio ( & s - > dither . noise , dither_count ) ) < 0 )
2012-04-10 19:52:42 +02:00
return ret ;
if ( ret )
2013-01-09 18:41:12 +01:00
for ( ch = 0 ; ch < s - > dither . noise . ch_count ; ch + + )
2013-01-09 23:20:24 +01:00
swri_get_dither ( s , s - > dither . noise . ch [ ch ] , s - > dither . noise . count , 12345678913579 < < ch , s - > dither . noise . fmt ) ;
2013-01-09 18:41:12 +01:00
av_assert0 ( s - > dither . noise . ch_count = = preout - > ch_count ) ;
2012-04-10 19:52:42 +02:00
2013-01-10 18:01:26 +01:00
if ( s - > dither . noise_pos + out_count > s - > dither . noise . count )
s - > dither . noise_pos = 0 ;
2012-05-01 20:20:21 +02:00
2013-01-09 18:41:12 +01:00
if ( s - > dither . method < SWR_DITHER_NS ) {
2013-01-09 18:03:49 +01:00
if ( s - > mix_2_1_simd ) {
int len1 = out_count & ~ 15 ;
int off = len1 * preout - > bps ;
if ( len1 )
for ( ch = 0 ; ch < preout - > ch_count ; ch + + )
2013-06-04 23:38:28 +02:00
s - > mix_2_1_simd ( conv_src - > ch [ ch ] , preout - > ch [ ch ] , s - > dither . noise . ch [ ch ] + s - > dither . noise . bps * s - > dither . noise_pos , s - > native_simd_one , 0 , 0 , len1 ) ;
2013-01-09 18:03:49 +01:00
if ( out_count ! = len1 )
for ( ch = 0 ; ch < preout - > ch_count ; ch + + )
2013-01-10 18:51:30 +01:00
s - > mix_2_1_f ( conv_src - > ch [ ch ] + off , preout - > ch [ ch ] + off , s - > dither . noise . ch [ ch ] + s - > dither . noise . bps * s - > dither . noise_pos + off + len1 , s - > native_one , 0 , 0 , out_count - len1 ) ;
2013-01-09 18:03:49 +01:00
} else {
2013-01-09 02:05:35 +01:00
for ( ch = 0 ; ch < preout - > ch_count ; ch + + )
2013-01-10 18:51:30 +01:00
s - > mix_2_1_f ( conv_src - > ch [ ch ] , preout - > ch [ ch ] , s - > dither . noise . ch [ ch ] + s - > dither . noise . bps * s - > dither . noise_pos , s - > native_one , 0 , 0 , out_count ) ;
2013-01-09 18:03:49 +01:00
}
2013-01-09 02:05:35 +01:00
} else {
2013-01-09 18:03:49 +01:00
switch ( s - > int_sample_fmt ) {
2013-01-10 18:51:30 +01:00
case AV_SAMPLE_FMT_S16P : swri_noise_shaping_int16 ( s , conv_src , preout , & s - > dither . noise , out_count ) ; break ;
case AV_SAMPLE_FMT_S32P : swri_noise_shaping_int32 ( s , conv_src , preout , & s - > dither . noise , out_count ) ; break ;
case AV_SAMPLE_FMT_FLTP : swri_noise_shaping_float ( s , conv_src , preout , & s - > dither . noise , out_count ) ; break ;
case AV_SAMPLE_FMT_DBLP : swri_noise_shaping_double ( s , conv_src , preout , & s - > dither . noise , out_count ) ; break ;
2013-01-09 18:03:49 +01:00
}
2013-01-09 02:05:35 +01:00
}
2013-01-10 18:01:26 +01:00
s - > dither . noise_pos + = out_count ;
2012-04-10 19:52:42 +02:00
}
2013-06-29 02:24:26 +02:00
//FIXME packed doesn't need more than 1 chan here!
2013-01-10 18:51:30 +01:00
swri_audio_convert ( s - > out_convert , out , conv_src , out_count ) ;
2011-09-19 06:13:30 +02:00
}
return out_count ;
}
2014-02-22 22:28:24 +01:00
int swr_is_initialized ( struct SwrContext * s ) {
return ! ! s - > in_buffer . ch_count ;
}
2012-03-25 21:04:48 +02:00
int swr_convert ( struct SwrContext * s , uint8_t * out_arg [ SWR_CH_MAX ] , int out_count ,
const uint8_t * in_arg [ SWR_CH_MAX ] , int in_count ) {
AudioData * in = & s - > in ;
AudioData * out = & s - > out ;
2014-02-22 22:29:51 +01:00
if ( ! swr_is_initialized ( s ) ) {
av_log ( s , AV_LOG_ERROR , " Context has not been initialized \n " ) ;
return AVERROR ( EINVAL ) ;
}
2013-01-13 15:53:01 +01:00
while ( s - > drop_output > 0 ) {
2012-05-19 18:42:11 +02:00
int ret ;
uint8_t * tmp_arg [ SWR_CH_MAX ] ;
2013-01-13 15:53:01 +01:00
# define MAX_DROP_STEP 16384
if ( ( ret = swri_realloc_audio ( & s - > drop_temp , FFMIN ( s - > drop_output , MAX_DROP_STEP ) ) ) < 0 )
2012-05-19 18:42:11 +02:00
return ret ;
2013-01-13 15:21:33 +01:00
reversefill_audiodata ( & s - > drop_temp , tmp_arg ) ;
2012-05-19 18:42:11 +02:00
s - > drop_output * = - 1 ; //FIXME find a less hackish solution
2013-06-29 02:24:26 +02:00
ret = swr_convert ( s , tmp_arg , FFMIN ( - s - > drop_output , MAX_DROP_STEP ) , in_arg , in_count ) ; //FIXME optimize but this is as good as never called so maybe it doesn't matter
2012-05-19 18:42:11 +02:00
s - > drop_output * = - 1 ;
2013-01-13 15:53:01 +01:00
in_count = 0 ;
if ( ret > 0 ) {
2012-05-19 18:42:11 +02:00
s - > drop_output - = ret ;
2014-10-06 01:08:20 +02:00
if ( ! s - > drop_output & & ! out_arg )
return 0 ;
2013-01-13 15:53:01 +01:00
continue ;
}
2012-05-19 18:42:11 +02:00
2014-10-06 01:29:15 +02:00
av_assert0 ( s - > drop_output ) ;
return 0 ;
2012-05-19 18:42:11 +02:00
}
2012-03-25 21:04:48 +02:00
if ( ! in_arg ) {
2012-12-11 21:19:39 +01:00
if ( s - > resample ) {
if ( ! s - > flushed )
s - > resampler - > flush ( s ) ;
s - > resample_in_constraint = 0 ;
s - > flushed = 1 ;
} else if ( ! s - > in_buffer_count ) {
2012-03-25 21:04:48 +02:00
return 0 ;
}
} else
fill_audiodata ( in , ( void * ) in_arg ) ;
fill_audiodata ( out , out_arg ) ;
if ( s - > resample ) {
2012-05-19 18:44:34 +02:00
int ret = swr_convert_internal ( s , out , out_count , in , in_count ) ;
if ( ret > 0 & & ! s - > drop_output )
s - > outpts + = ret * ( int64_t ) s - > in_sample_rate ;
return ret ;
2012-03-25 21:04:48 +02:00
} else {
AudioData tmp = * in ;
int ret2 = 0 ;
int ret , size ;
size = FFMIN ( out_count , s - > in_buffer_count ) ;
if ( size ) {
buf_set ( & tmp , & s - > in_buffer , s - > in_buffer_index ) ;
ret = swr_convert_internal ( s , out , size , & tmp , size ) ;
if ( ret < 0 )
return ret ;
ret2 = ret ;
s - > in_buffer_count - = ret ;
s - > in_buffer_index + = ret ;
buf_set ( out , out , ret ) ;
out_count - = ret ;
if ( ! s - > in_buffer_count )
s - > in_buffer_index = 0 ;
}
if ( in_count ) {
size = s - > in_buffer_index + s - > in_buffer_count + in_count - out_count ;
if ( in_count > out_count ) { //FIXME move after swr_convert_internal
if ( size > s - > in_buffer . count
& & s - > in_buffer_count + in_count - out_count < = s - > in_buffer_index ) {
buf_set ( & tmp , & s - > in_buffer , s - > in_buffer_index ) ;
copy ( & s - > in_buffer , & tmp , s - > in_buffer_count ) ;
s - > in_buffer_index = 0 ;
} else
2012-12-11 21:15:19 +01:00
if ( ( ret = swri_realloc_audio ( & s - > in_buffer , size ) ) < 0 )
2012-03-25 21:04:48 +02:00
return ret ;
}
if ( out_count ) {
size = FFMIN ( in_count , out_count ) ;
ret = swr_convert_internal ( s , out , size , in , size ) ;
if ( ret < 0 )
return ret ;
buf_set ( in , in , ret ) ;
in_count - = ret ;
ret2 + = ret ;
}
if ( in_count ) {
2012-05-19 18:37:12 +02:00
buf_set ( & tmp , & s - > in_buffer , s - > in_buffer_index + s - > in_buffer_count ) ;
2012-03-25 21:04:48 +02:00
copy ( & tmp , in , in_count ) ;
s - > in_buffer_count + = in_count ;
}
}
2012-05-19 18:44:34 +02:00
if ( ret2 > 0 & & ! s - > drop_output )
s - > outpts + = ret2 * ( int64_t ) s - > in_sample_rate ;
2012-03-25 21:04:48 +02:00
return ret2 ;
}
}
2012-05-19 18:42:11 +02:00
int swr_drop_output ( struct SwrContext * s , int count ) {
2014-11-04 16:54:14 +01:00
const uint8_t * tmp_arg [ SWR_CH_MAX ] ;
2012-05-19 18:42:11 +02:00
s - > drop_output + = count ;
if ( s - > drop_output < = 0 )
return 0 ;
av_log ( s , AV_LOG_VERBOSE , " discarding %d audio samples \n " , count ) ;
2014-11-04 16:54:14 +01:00
return swr_convert ( s , NULL , s - > drop_output , tmp_arg , 0 ) ;
2012-05-19 18:42:11 +02:00
}
2012-05-19 18:39:12 +02:00
int swr_inject_silence ( struct SwrContext * s , int count ) {
int ret , i ;
uint8_t * tmp_arg [ SWR_CH_MAX ] ;
if ( count < = 0 )
return 0 ;
2013-01-13 15:57:56 +01:00
# define MAX_SILENCE_STEP 16384
while ( count > MAX_SILENCE_STEP ) {
if ( ( ret = swr_inject_silence ( s , MAX_SILENCE_STEP ) ) < 0 )
return ret ;
count - = MAX_SILENCE_STEP ;
}
2013-01-13 15:26:04 +01:00
if ( ( ret = swri_realloc_audio ( & s - > silence , count ) ) < 0 )
2012-05-19 18:39:12 +02:00
return ret ;
2013-01-13 15:26:04 +01:00
if ( s - > silence . planar ) for ( i = 0 ; i < s - > silence . ch_count ; i + + ) {
memset ( s - > silence . ch [ i ] , s - > silence . bps = = 1 ? 0x80 : 0 , count * s - > silence . bps ) ;
2012-05-19 18:39:12 +02:00
} else
2013-01-13 15:26:04 +01:00
memset ( s - > silence . ch [ 0 ] , s - > silence . bps = = 1 ? 0x80 : 0 , count * s - > silence . bps * s - > silence . ch_count ) ;
2012-05-19 18:39:12 +02:00
2013-01-13 15:26:04 +01:00
reversefill_audiodata ( & s - > silence , tmp_arg ) ;
2012-05-19 18:39:12 +02:00
av_log ( s , AV_LOG_VERBOSE , " adding %d audio samples of silence \n " , count ) ;
ret = swr_convert ( s , NULL , 0 , ( const uint8_t * * ) tmp_arg , count ) ;
return ret ;
}
2012-05-19 18:44:34 +02:00
2012-12-11 18:36:58 +01:00
int64_t swr_get_delay ( struct SwrContext * s , int64_t base ) {
if ( s - > resampler & & s - > resample ) {
return s - > resampler - > get_delay ( s , base ) ;
} else {
return ( s - > in_buffer_count * base + ( s - > in_sample_rate > > 1 ) ) / s - > in_sample_rate ;
}
}
int swr_set_compensation ( struct SwrContext * s , int sample_delta , int compensation_distance ) {
int ret ;
if ( ! s | | compensation_distance < 0 )
return AVERROR ( EINVAL ) ;
if ( ! compensation_distance & & sample_delta )
return AVERROR ( EINVAL ) ;
if ( ! s - > resample ) {
s - > flags | = SWR_FLAG_RESAMPLE ;
ret = swr_init ( s ) ;
if ( ret < 0 )
return ret ;
}
if ( ! s - > resampler - > set_compensation ) {
return AVERROR ( EINVAL ) ;
} else {
return s - > resampler - > set_compensation ( s - > resample , sample_delta , compensation_distance ) ;
}
}
2012-05-19 18:44:34 +02:00
int64_t swr_next_pts ( struct SwrContext * s , int64_t pts ) {
if ( pts = = INT64_MIN )
return s - > outpts ;
2013-02-25 03:04:03 +01:00
if ( s - > firstpts = = AV_NOPTS_VALUE )
s - > outpts = s - > firstpts = pts ;
2012-05-19 18:44:34 +02:00
if ( s - > min_compensation > = FLT_MAX ) {
return ( s - > outpts = pts - swr_get_delay ( s , s - > in_sample_rate * ( int64_t ) s - > out_sample_rate ) ) ;
} else {
2013-01-13 18:39:06 +01:00
int64_t delta = pts - swr_get_delay ( s , s - > in_sample_rate * ( int64_t ) s - > out_sample_rate ) - s - > outpts + s - > drop_output * ( int64_t ) s - > in_sample_rate ;
2012-05-19 18:44:34 +02:00
double fdelta = delta / ( double ) ( s - > in_sample_rate * ( int64_t ) s - > out_sample_rate ) ;
if ( fabs ( fdelta ) > s - > min_compensation ) {
2013-01-21 00:52:14 +01:00
if ( s - > outpts = = s - > firstpts | | fabs ( fdelta ) > s - > min_hard_compensation ) {
2012-05-22 18:54:38 +02:00
int ret ;
if ( delta > 0 ) ret = swr_inject_silence ( s , delta / s - > out_sample_rate ) ;
else ret = swr_drop_output ( s , - delta / s - > in_sample_rate ) ;
if ( ret < 0 ) {
av_log ( s , AV_LOG_ERROR , " Failed to compensate for timestamp delta of %f \n " , fdelta ) ;
}
2012-05-19 22:42:32 +02:00
} else if ( s - > soft_compensation_duration & & s - > max_soft_compensation ) {
2012-05-19 18:44:34 +02:00
int duration = s - > out_sample_rate * s - > soft_compensation_duration ;
2012-05-30 03:32:32 +02:00
double max_soft_compensation = s - > max_soft_compensation / ( s - > max_soft_compensation < 0 ? - s - > in_sample_rate : 1 ) ;
int comp = av_clipf ( fdelta , - max_soft_compensation , max_soft_compensation ) * duration ;
2012-05-19 18:44:34 +02:00
av_log ( s , AV_LOG_VERBOSE , " compensating audio timestamp drift:%f compensation:%d in:%d \n " , fdelta , comp , duration ) ;
swr_set_compensation ( s , comp , duration ) ;
}
}
return s - > outpts ;
}
}