ref: c0c963e0e5e62027ff1c91cae49c1905fc7001bb
parent: e8b6394e5aca32ae1db8cd02b0dbf508274e07a0
author: Timothy B. Terriberry <tterribe@xiph.org>
date: Sun Sep 16 17:39:09 EDT 2012
Implement stereo downmixing functions. Move this out of opusfile_example and into the API proper.
--- a/examples/opusfile_example.c
+++ b/examples/opusfile_example.c
@@ -13,45 +13,6 @@
#endif
#include <opus/opusfile.h>
-/*Matrices for downmixing from the supported channel counts to stereo.*/
-static const float DOWNMIX_MATRIX[8][8][2]={
- /*mono*/
- {
- {1.F,1.F}
- },
- /*stereo*/
- {
- {1.F,0.F},{0.F,1.F}
- },
- /*3.0*/
- {
- {0.5858F,0.F},{0.4142F,0.4142F},{0,0.5858F}
- },
- /*quadrophonic*/
- {
- {0.4226F,0.F},{0,0.4226F},{0.366F,0.2114F},{0.2114F,0.336F}
- },
- /*5.0*/
- {
- {0.651F,0.F},{0.46F,0.46F},{0,0.651F},{0.5636F,0.3254F},{0.3254F,0.5636F}
- },
- /*5.1*/
- {
- {0.529F,0.F},{0.3741F,0.3741F},{0.F,0.529F},{0.4582F,0.2645F},
- {0.2645F,0.4582F},{0.3741F,0.3741F}
- },
- /*6.1*/
- {
- {0.4553F,0.F},{0.322F,0.322F},{0.F,0.4553F},{0.3943F,0.2277F},
- {0.2277F,0.3943F},{0.2788F,0.2788F},{0.322F,0.322F}
- },
- /*7.1*/
- {
- {0.3886F,0.F},{0.2748F,0.2748F},{0.F,0.3886F},{0.3366F,0.1943F},
- {0.1943F,0.3366F},{0.3366F,0.1943F},{0.1943F,0.3366F},{0.2748F,0.2748F}
- }
-};
-
int main(int _argc,const char **_argv){
OggOpusFile *of;
ogg_int64_t pcm_offset;
@@ -75,7 +36,7 @@
}
if(strcmp(_argv[1],"-")==0){
OpusFileCallbacks cb={NULL,NULL,NULL,NULL};
- of=op_open_callbacks(op_fdopen(&cb,fileno(stdin),"rb"),&cb,NULL,0,NULL);
+ of=op_open_callbacks(op_fdopen(&cb,fileno(stdin),"rb"),&cb,NULL,0,&ret);
}
#if 0
/*For debugging: force a file to not be seekable.*/
@@ -88,10 +49,10 @@
of=op_open_callbacks(fp,&cb,NULL,0,NULL);
}
#else
- else of=op_open_file(_argv[1],NULL);
+ else of=op_open_file(_argv[1],&ret);
#endif
if(of==NULL){
- fprintf(stderr,"Failed to open file '%s'.\n",_argv[1]);
+ fprintf(stderr,"Failed to open file '%s': %i\n",_argv[1],ret);
return EXIT_FAILURE;
}
if(op_seekable(of)){
@@ -109,17 +70,15 @@
}
for(;;){
ogg_int64_t next_pcm_offset;
- float pcm[120*48*8];
- float stereo_pcm[120*48*2];
- int nchannels;
+ float pcm[120*48*2];
int li;
- int i;
- ret=op_read_float(of,pcm,sizeof(pcm)/sizeof(*pcm),&li);
+ ret=op_read_float_stereo(of,pcm,sizeof(pcm)/sizeof(*pcm));
if(ret<0){
fprintf(stderr,"Error decoding '%s': %i\n",_argv[1],ret);
ret=EXIT_FAILURE;
break;
}
+ li=op_current_link(of);
if(li!=prev_li){
const OpusHead *head;
const OpusTags *tags;
@@ -163,26 +122,7 @@
ret=EXIT_SUCCESS;
break;
}
- /*Downmix to stereo so we can have a consistent output format.*/
- nchannels=op_channel_count(of,li);
- if(nchannels<0||nchannels>8){
- fprintf(stderr,"Unsupported channel count: %i\n",nchannels);
- ret=EXIT_FAILURE;
- break;
- }
- for(i=0;i<ret;i++){
- float l;
- float r;
- int ci;
- l=r=0.F;
- for(ci=0;ci<nchannels;ci++){
- l+=DOWNMIX_MATRIX[nchannels-1][ci][0]*pcm[i*nchannels+ci];
- r+=DOWNMIX_MATRIX[nchannels-1][ci][1]*pcm[i*nchannels+ci];
- }
- stereo_pcm[2*i+0]=l;
- stereo_pcm[2*i+1]=r;
- }
- if(!fwrite(stereo_pcm,sizeof(*stereo_pcm)*2,ret,stdout)){
+ if(!fwrite(pcm,sizeof(*pcm)*2,ret,stdout)){
fprintf(stderr,"Error writing decoded audio data: %s\n",strerror(errno));
ret=EXIT_FAILURE;
break;
--- a/include/opus/opusfile.h
+++ b/include/opus/opusfile.h
@@ -784,6 +784,22 @@
partially open.*/
const OpusTags *op_tags(OggOpusFile *_of,int _li);
+/**Retrieve the index of the current link.
+ This is the link that produced the data most recently read by
+ op_read_float() or its associated functions, or, after a seek, the link
+ that the seek target landed in.
+ Reading more data may advance the link index (even on the first read after a
+ seek).
+ \return The index of the current link on success, or a negative value on
+ failture.
+ For seekable streams, this is a number between 0 and the value
+ returned by op_link_count().
+ For unseekable streams, this value starts at 0 and increments by one
+ each time a new link is encountered (even though op_link_count()
+ always returns 1).
+ \retval #OP_EINVAL The stream was not fully open.*/
+int op_current_link(OggOpusFile *_of);
+
/**Computes the bitrate for a given link in a (possibly chained) Ogg Opus
stream.
The stream must be seekable to compute the bitrate.
@@ -955,6 +971,96 @@
a starting timestamp that failed basic validity
checks.*/
int op_read_float(OggOpusFile *_of,float *_pcm,int _buf_size,int *_li);
+
+/**Reads more samples from the stream and downmixes to stereo, if necessary.
+ This function is intended for simple players that want a uniform output
+ format, even if the channel count changes between links in a chained
+ stream.
+ \param[out] _pcm A buffer in which to store the output PCM samples, as
+ signed native-endian 16-bit values with a nominal
+ range of <code>[-32768,32767)</code>.
+ The left and right channels are interleaved in the
+ buffer.
+ This must have room for at least \a _buf_size values.
+ \param _buf_size The number of values that can be stored in \a _pcm.
+ It is reccommended that this be large enough for at
+ least 120 ms of data at 48 kHz per channel (11520
+ values total).
+ Smaller buffers will simply return less data, possibly
+ consuming more memory to buffer the data internally.
+ \return The number of samples read per channel on success, or a negative
+ value on failure.
+ The number of samples returned may be 0 if the buffer was too small
+ to store even a single sample for both channels, or if end of file
+ was reached.
+ The list of possible failure codes follows.
+ Most of them can only be returned by unseekable, chained streams
+ that encounter a new link.
+ \retval #OP_EFAULT An internal memory allocation failed.
+ \retval #OP_EIMPL An unseekable stream encountered a new link that
+ used a feature that is not implemented, such as
+ an unsupported channel family.
+ \retval #OP_EINVAL The stream was not fully open.
+ \retval #OP_ENOTFORMAT An unseekable stream encountered a new link that
+ contained a link that did not have any logical
+ Opus streams in it.
+ \retval #OP_EBADHEADER An unseekable stream encountered a new link with a
+ required header packet that was not properly
+ formatted, contained illegal values, or was
+ missing altogether.
+ \retval #OP_EVERSION An unseekable stream encountered a new link with
+ an ID header that contained an unrecognized
+ version number.
+ \retval #OP_EBADPACKET Failed to properly decode the next packet.
+ \retval #OP_EBADTIMESTAMP An unseekable stream encountered a new link with
+ a starting timestamp that failed basic validity
+ checks.*/
+int op_read_stereo(OggOpusFile *_of,opus_int16 *_pcm,int _buf_size);
+
+/**Reads more samples from the stream and downmixes to stereo, if necessary.
+ This function is intended for simple players that want a uniform output
+ format, even if the channel count changes between links in a chained
+ stream.
+ \param[out] _pcm A buffer in which to store the output PCM samples, as
+ signed floats with a nominal range of
+ <code>[-1.0,1.0]</code>.
+ The left and right channels are interleaved in the
+ buffer.
+ This must have room for at least \a _buf_size values.
+ \param _buf_size The number of values that can be stored in \a _pcm.
+ It is reccommended that this be large enough for at
+ least 120 ms of data at 48 kHz per channel (11520
+ values total).
+ Smaller buffers will simply return less data, possibly
+ consuming more memory to buffer the data internally.
+ \return The number of samples read per channel on success, or a negative
+ value on failure.
+ The number of samples returned may be 0 if the buffer was too small
+ to store even a single sample for both channels, or if end of file
+ was reached.
+ The list of possible failure codes follows.
+ Most of them can only be returned by unseekable, chained streams
+ that encounter a new link.
+ \retval #OP_EFAULT An internal memory allocation failed.
+ \retval #OP_EIMPL An unseekable stream encountered a new link that
+ used a feature that is not implemented, such as
+ an unsupported channel family.
+ \retval #OP_EINVAL The stream was not fully open.
+ \retval #OP_ENOTFORMAT An unseekable stream encountered a new link that
+ contained a link that did not have any logical
+ Opus streams in it.
+ \retval #OP_EBADHEADER An unseekable stream encountered a new link with a
+ required header packet that was not properly
+ formatted, contained illegal values, or was
+ missing altogether.
+ \retval #OP_EVERSION An unseekable stream encountered a new link with
+ an ID header that contained an unrecognized
+ version number.
+ \retval #OP_EBADPACKET Failed to properly decode the next packet.
+ \retval #OP_EBADTIMESTAMP An unseekable stream encountered a new link with
+ a starting timestamp that failed basic validity
+ checks.*/
+int op_read_float_stereo(OggOpusFile *_of,float *_pcm,int _buf_size);
# if OP_GNUC_PREREQ(4,0)
# pragma GCC visibility pop
--- a/src/opusfile.c
+++ b/src/opusfile.c
@@ -1376,6 +1376,11 @@
return _li>=_of->nlinks?NULL:&_of->links[_li].tags;
}
+int op_current_link(OggOpusFile *_of){
+ if(OP_UNLIKELY(_of->ready_state<OP_OPENED))return OP_EINVAL;
+ return _of->cur_link;
+}
+
/*Compute an average bitrate given a byte and sample count.
Return: The bitrate in bits per second.*/
static opus_int32 op_calc_bitrate(opus_int64 _bytes,ogg_int64_t _samples){
@@ -2214,31 +2219,28 @@
}
}
-#if defined(OP_FIXED_POINT)
+typedef int (*op_read_filter_func)(OggOpusFile *_of,void *_dst,int _dst_sz,
+ op_sample *_src,int _nsamples,int _nchannels);
-int op_read(OggOpusFile *_of,opus_int16 *_pcm,int _buf_size,int *_li){
- return op_read_native(_of,_pcm,_buf_size,_li);
-}
-
-# if !defined(OP_DISABLE_FLOAT_API)
-int op_read_float(OggOpusFile *_of,float *_pcm,int _buf_size,int *_li){
+/*Decode some samples and then apply a custom filter to them.
+ This is used to convert to different output formats.*/
+static int op_read_native_filter(OggOpusFile *_of,void *_dst,int _dst_sz,
+ op_read_filter_func _filter,int *_li){
int ret;
/*Ensure we have some decoded samples in our buffer.*/
ret=op_read_native(_of,NULL,0,_li);
- /*Now convert them to float.*/
+ /*Now apply the filter to them.*/
if(OP_LIKELY(ret>=0)&&OP_LIKELY(_of->ready_state>=OP_INITSET)){
- int nchannels;
int od_buffer_pos;
- nchannels=_of->links[_of->seekable?_of->cur_link:0].head.channel_count;
od_buffer_pos=_of->od_buffer_pos;
ret=_of->od_buffer_size-od_buffer_pos;
if(OP_LIKELY(ret>0)){
- op_sample *buf;
- int i;
- if(OP_UNLIKELY(ret*nchannels>_buf_size))ret=_buf_size/nchannels;
- buf=_of->od_buffer+nchannels*od_buffer_pos;
- _buf_size=ret*nchannels;
- for(i=0;i<_buf_size;i++)_pcm[i]=(1.0F/32768)*buf[i];
+ int nchannels;
+ nchannels=_of->links[_of->seekable?_of->cur_link:0].head.channel_count;
+ ret=(*_filter)(_of,_dst,_dst_sz,
+ _of->od_buffer+nchannels*od_buffer_pos,ret,nchannels);
+ OP_ASSERT(ret>=0);
+ OP_ASSERT(ret<=_of->od_buffer_size-od_buffer_pos);
od_buffer_pos+=ret;
_of->od_buffer_pos=od_buffer_pos;
}
@@ -2245,6 +2247,129 @@
}
return ret;
}
+
+#if defined(OP_FIXED_POINT)
+
+int op_read(OggOpusFile *_of,opus_int16 *_pcm,int _buf_size,int *_li){
+ return op_read_native(_of,_pcm,_buf_size,_li);
+}
+
+/*Matrices for downmixing from the supported channel counts to stereo.
+ The matrices with 5 or more channels are normalized to a total volume of 2.0,
+ since most mixes sound too quiet if normalized to 1.0 (as there is generally
+ little volume in the side/rear channels).
+ Hence we keep the coefficients in Q14, so the downmix values won't overflow a
+ 32-bit number.*/
+static const opus_int16 OP_STEREO_DOWNMIX_Q14
+ [OP_NCHANNELS_MAX-2][OP_NCHANNELS_MAX][2]={
+ /*3.0*/
+ {
+ {9598,0},{6786,6786},{0,9598}
+ },
+ /*quadrophonic*/
+ {
+ {6924,0},{0,6924},{5996,3464},{3464,5996}
+ },
+ /*5.0*/
+ {
+ {10666,0},{7537,7537},{0,10666},{9234,5331},{5331,9234}
+ },
+ /*5.1*/
+ {
+ {8668,0},{6129,6129},{0,8668},{7507,4335},{4335,7507},{6129,6129}
+ },
+ /*6.1*/
+ {
+ {7459,0},{5275,5275},{0,7459},{6460,3731},{3731,6460},{4568,4568},
+ {5275,5275}
+ },
+ /*7.1*/
+ {
+ {6368,0},{4502,4502},{0,6368},{5515,3183},{3183,5515},{5515,3183},
+ {3183,5515},{4502,4502}
+ }
+};
+
+static int op_stereo_filter(OggOpusFile *_of,void *_dst,int _dst_sz,
+ op_sample *_src,int _nsamples,int _nchannels){
+ _of=_of;
+ _nsamples=OP_MIN(_nsamples,_dst_sz>>1);
+ if(_nchannels==2)memcpy(_dst,_src,_nsamples*2*sizeof(*_src));
+ else{
+ opus_int16 *dst;
+ int i;
+ dst=(opus_int16 *)_dst;
+ if(_nchannels==1){
+ for(i=0;i<_nsamples;i++)dst[2*i+0]=dst[2*i+1]=_src[i];
+ }
+ else{
+ for(i=0;i<_nsamples;i++){
+ opus_int32 l;
+ opus_int32 r;
+ int ci;
+ l=r=0;
+ for(ci=0;ci<_nchannels;ci++){
+ opus_int32 s;
+ s=_src[_nchannels*i+ci];
+ l+=OP_STEREO_DOWNMIX_Q14[_nchannels-3][ci][0]*s;
+ r+=OP_STEREO_DOWNMIX_Q14[_nchannels-3][ci][1]*s;
+ }
+ dst[2*i+0]=(opus_int16)OP_CLAMP(-32768,l+8192>>14,32767);
+ dst[2*i+1]=(opus_int16)OP_CLAMP(-32768,r+8192>>14,32767);
+ }
+ }
+ }
+ return _nsamples;
+}
+
+int op_read_stereo(OggOpusFile *_of,opus_int16 *_pcm,int _buf_size){
+ return op_read_native_filter(_of,_pcm,_buf_size,op_stereo_filter,NULL);
+}
+
+# if !defined(OP_DISABLE_FLOAT_API)
+
+static int op_short2float_filter(OggOpusFile *_of,void *_dst,int _dst_sz,
+ op_sample *_src,int _nsamples,int _nchannels){
+ float *dst;
+ int i;
+ dst=(float *)_dst;
+ if(OP_UNLIKELY(_nsamples*_nchannels>_dst_sz))_nsamples=_dst_sz/_nchannels;
+ _dst_sz=_nsamples*_nchannels;
+ for(i=0;i<_dst_sz;i++)dst[i]=(1.0F/32768)*_src[i];
+ return _nsamples;
+}
+
+int op_read_float(OggOpusFile *_of,float *_pcm,int _buf_size,int *_li){
+ return op_read_native_filter(_of,_pcm,_buf_size,op_short2float_filter,_li);
+}
+
+static int op_short2float_stereo_filter(OggOpusFile *_of,
+ void *_dst,int _dst_sz,op_sample *_src,int _nsamples,int _nchannels){
+ float *dst;
+ dst=(float *)_dst;
+ _nsamples=OP_MIN(_nsamples,_dst_sz>>1);
+ if(_nchannels==1){
+ int i;
+ _nsamples=op_short2float_filter(_of,dst,_nsamples,_src,_nsamples,1);
+ for(i=_nsamples;i-->0;)dst[2*i+0]=dst[2*i+1]=dst[i];
+ return _nsamples;
+ }
+ /*It would be better to convert to floats and then downmix (so that we don't
+ risk clipping with more than 5 channels), but that would require a large
+ stack buffer, which is probably not a good idea if you're using the
+ fixed-point build.*/
+ if(_nchannels>2){
+ _nsamples=op_stereo_filter(_of,_src,_nsamples*2,
+ _src,_nsamples,_nchannels);
+ }
+ return op_short2float_filter(_of,dst,_dst_sz,_src,_nsamples,2);
+}
+
+int op_read_stereo_float(OggOpusFile *_of,opus_int16 *_pcm,int _buf_size){
+ return op_read_native_filter(_of,_pcm,_buf_size,
+ op_short2float_stereo_filter,NULL);
+}
+
# endif
#else
@@ -2297,8 +2422,8 @@
0.9030F,0.0116F,-0.5853F,-0.2571F
};
-static void op_shaped_dither16(OggOpusFile *_of,opus_int16 *_dst,float *_src,
- int _nsamples,int _nchannels){
+static void op_shaped_dither16(OggOpusFile *_of,opus_int16 *_dst,
+ const float *_src,int _nsamples,int _nchannels){
opus_uint32 seed;
int mute;
int i;
@@ -2355,31 +2480,115 @@
_of->dither_seed=seed;
}
+static int op_float2short_filter(OggOpusFile *_of,void *_dst,int _dst_sz,
+ op_sample *_src,int _nsamples,int _nchannels){
+ opus_int16 *dst;
+ dst=(opus_int16 *)_dst;
+ if(OP_UNLIKELY(_nsamples*_nchannels>_dst_sz))_nsamples=_dst_sz/_nchannels;
+ op_shaped_dither16(_of,dst,_src,_nsamples,_nchannels);
+ return _nsamples;
+}
+
int op_read(OggOpusFile *_of,opus_int16 *_pcm,int _buf_size,int *_li){
- int ret;
- /*Ensure we have some decoded samples in our buffer.*/
- ret=op_read_native(_of,NULL,0,_li);
- /*Now convert them to shorts.*/
- if(OP_LIKELY(ret>=0)&&OP_LIKELY(_of->ready_state>=OP_INITSET)){
- int nchannels;
- int od_buffer_pos;
- nchannels=_of->links[_of->seekable?_of->cur_link:0].head.channel_count;
- od_buffer_pos=_of->od_buffer_pos;
- ret=_of->od_buffer_size-od_buffer_pos;
- if(OP_LIKELY(ret>0)){
- op_sample *buf;
- if(OP_UNLIKELY(ret*nchannels>_buf_size))ret=_buf_size/nchannels;
- buf=_of->od_buffer+nchannels*od_buffer_pos;
- op_shaped_dither16(_of,_pcm,buf,ret,nchannels);
- od_buffer_pos+=ret;
- _of->od_buffer_pos=od_buffer_pos;
- }
- }
- return ret;
+ return op_read_native_filter(_of,_pcm,_buf_size,op_float2short_filter,_li);
}
int op_read_float(OggOpusFile *_of,float *_pcm,int _buf_size,int *_li){
return op_read_native(_of,_pcm,_buf_size,_li);
+}
+
+/*Matrices for downmixing from the supported channel counts to stereo.
+ The matrices with 5 or more channels are normalized to a total volume of 2.0,
+ since most mixes sound too quiet if normalized to 1.0 (as there is generally
+ little volume in the side/rear channels).*/
+static const float OP_STEREO_DOWNMIX[OP_NCHANNELS_MAX-2][OP_NCHANNELS_MAX][2]={
+ /*3.0*/
+ {
+ {0.5858F,0.0F},{0.4142F,0.4142F},{0.0F,0.5858F}
+ },
+ /*quadrophonic*/
+ {
+ {0.4226F,0.0F},{0.0F,0.4226F},{0.366F,0.2114F},{0.2114F,0.336F}
+ },
+ /*5.0*/
+ {
+ {0.651F,0.0F},{0.46F,0.46F},{0.0F,0.651F},{0.5636F,0.3254F},
+ {0.3254F,0.5636F}
+ },
+ /*5.1*/
+ {
+ {0.529F,0.0F},{0.3741F,0.3741F},{0.0F,0.529F},{0.4582F,0.2645F},
+ {0.2645F,0.4582F},{0.3741F,0.3741F}
+ },
+ /*6.1*/
+ {
+ {0.4553F,0.0F},{0.322F,0.322F},{0.0F,0.4553F},{0.3943F,0.2277F},
+ {0.2277F,0.3943F},{0.2788F,0.2788F},{0.322F,0.322F}
+ },
+ /*7.1*/
+ {
+ {0.3886F,0.0F},{0.2748F,0.2748F},{0.0F,0.3886F},{0.3366F,0.1943F},
+ {0.1943F,0.3366F},{0.3366F,0.1943F},{0.1943F,0.3366F},{0.2748F,0.2748F}
+ }
+};
+
+static int op_stereo_filter(OggOpusFile *_of,void *_dst,int _dst_sz,
+ op_sample *_src,int _nsamples,int _nchannels){
+ _of=_of;
+ _nsamples=OP_MIN(_nsamples,_dst_sz>>1);
+ if(_nchannels==2)memcpy(_dst,_src,_nsamples*2*sizeof(*_src));
+ else{
+ float *dst;
+ int i;
+ dst=(float *)_dst;
+ if(_nchannels==1){
+ for(i=0;i<_nsamples;i++)dst[2*i+0]=dst[2*i+1]=_src[i];
+ }
+ else{
+ for(i=0;i<_nsamples;i++){
+ float l;
+ float r;
+ int ci;
+ l=r=0;
+ for(ci=0;ci<_nchannels;ci++){
+ l+=OP_STEREO_DOWNMIX[_nchannels-3][ci][0]*_src[_nchannels*i+ci];
+ r+=OP_STEREO_DOWNMIX[_nchannels-3][ci][1]*_src[_nchannels*i+ci];
+ }
+ dst[2*i+0]=l;
+ dst[2*i+1]=r;
+ }
+ }
+ }
+ return _nsamples;
+}
+
+static int op_float2short_stereo_filter(OggOpusFile *_of,
+ void *_dst,int _dst_sz,op_sample *_src,int _nsamples,int _nchannels){
+ opus_int16 *dst;
+ dst=(opus_int16 *)_dst;
+ _nsamples=OP_MIN(_nsamples,_dst_sz>>1);
+ if(_nchannels==1){
+ int i;
+ op_shaped_dither16(_of,dst,_src,_nsamples,1);
+ for(i=_nsamples;i-->0;)dst[2*i+0]=dst[2*i+1]=dst[i];
+ }
+ else{
+ if(_nchannels>2){
+ _nsamples=op_stereo_filter(_of,_src,_nsamples*2,
+ _src,_nsamples,_nchannels);
+ }
+ op_shaped_dither16(_of,dst,_src,_nsamples,_nchannels);
+ }
+ return _nsamples;
+}
+
+int op_read_stereo(OggOpusFile *_of,opus_int16 *_pcm,int _buf_size){
+ return op_read_native_filter(_of,_pcm,_buf_size,
+ op_float2short_stereo_filter,NULL);
+}
+
+int op_read_float_stereo(OggOpusFile *_of,float *_pcm,int _buf_size){
+ return op_read_native_filter(_of,_pcm,_buf_size,op_stereo_filter,NULL);
}
#endif