diff --git a/gst-libs/gst/audio/audio-converter.c b/gst-libs/gst/audio/audio-converter.c index a5972f3bf9..8c66b4c149 100644 --- a/gst-libs/gst/audio/audio-converter.c +++ b/gst-libs/gst/audio/audio-converter.c @@ -217,19 +217,16 @@ audio_chain_free (AudioChain * chain) } static gpointer * -audio_chain_alloc_samples (AudioChain * chain, gsize num_samples, gsize * avail) +audio_chain_alloc_samples (AudioChain * chain, gsize num_samples) { - return chain->alloc_func (chain, num_samples, avail, chain->alloc_data); + return chain->alloc_func (chain, num_samples, chain->alloc_data); } static void audio_chain_set_samples (AudioChain * chain, gpointer * samples, gsize num_samples) { - if (num_samples == 0) - return; - - GST_LOG ("set samples %" G_GSIZE_FORMAT, num_samples); + GST_LOG ("set samples %p %" G_GSIZE_FORMAT, samples, num_samples); chain->samples = samples; chain->num_samples = num_samples; @@ -307,7 +304,7 @@ copy_config (GQuark field_id, const GValue * value, gpointer user_data) * * Set @in_rate, @out_rate and @config as extra configuration for @convert. * - * in_rate and @out_rate specify the new sample rates of input and output + * @in_rate and @out_rate specify the new sample rates of input and output * formats. A value of 0 leaves the sample rate unchanged. * * @config can be %NULL, in which case, the current configuration is not @@ -345,6 +342,10 @@ gst_audio_converter_update_config (GstAudioConverter * convert, gst_structure_free (config); } + if (convert->resampler) + gst_audio_resampler_update (convert->resampler, in_rate, out_rate, + convert->config); + return TRUE; } @@ -459,7 +460,6 @@ do_unpack (AudioChain * chain, gpointer user_data) } } else { tmp = convert->in_data; - num_samples = convert->in_samples; GST_LOG ("get in samples %p", tmp); } audio_chain_set_samples (chain, tmp, num_samples); @@ -470,8 +470,8 @@ do_unpack (AudioChain * chain, gpointer user_data) static gboolean do_convert_in (AudioChain * chain, gpointer user_data) { - GstAudioConverter *convert = user_data; gsize num_samples; + GstAudioConverter *convert = user_data; gpointer *in, *out; gint i; @@ -490,8 +490,8 @@ do_convert_in (AudioChain * chain, gpointer user_data) static gboolean do_mix (AudioChain * chain, gpointer user_data) { - GstAudioConverter *convert = user_data; gsize num_samples; + GstAudioConverter *convert = user_data; gpointer *in, *out; in = audio_chain_get_samples (chain->prev, &num_samples); @@ -510,23 +510,19 @@ do_resample (AudioChain * chain, gpointer user_data) { GstAudioConverter *convert = user_data; gpointer *in, *out; - gsize in_frames, out_frames, produced, consumed; + gsize in_frames, out_frames; in = audio_chain_get_samples (chain->prev, &in_frames); + out_frames = convert->out_samples; + out = (chain->allow_ip ? in : audio_chain_alloc_samples (chain, out_frames)); - out_frames = - gst_audio_resampler_get_out_frames (convert->resampler, in_frames); - out = - (chain->allow_ip ? in : audio_chain_alloc_samples (chain, out_frames, - &out_frames)); - - GST_LOG ("resample %p %p,%" G_GSIZE_FORMAT " %" G_GSIZE_FORMAT " %" - G_GSIZE_FORMAT, in, out, in_frames, out_frames, num_samples); + GST_LOG ("resample %p %p,%" G_GSIZE_FORMAT " %" G_GSIZE_FORMAT, in, + out, in_frames, out_frames); gst_audio_resampler_resample (convert->resampler, in, in_frames, out, - out_frames, &consumed, &produced); + out_frames); - audio_chain_set_samples (chain, out, produced); + audio_chain_set_samples (chain, out, out_frames); return TRUE; } @@ -541,7 +537,7 @@ do_convert_out (AudioChain * chain, gpointer user_data) in = audio_chain_get_samples (chain->prev, &num_samples); out = (chain->allow_ip ? in : audio_chain_alloc_samples (chain, num_samples)); - GST_LOG ("convert out %p, %p, %" G_GSIZE_FORMAT, in, out, num_samples); + GST_LOG ("convert out %p, %p %" G_GSIZE_FORMAT, in, out, num_samples); for (i = 0; i < chain->blocks; i++) convert->convert_out (out[i], in[i], num_samples * chain->inc); @@ -560,7 +556,7 @@ do_quantize (AudioChain * chain, gpointer user_data) in = audio_chain_get_samples (chain->prev, &num_samples); out = (chain->allow_ip ? in : audio_chain_alloc_samples (chain, num_samples)); - GST_LOG ("quantize %p, %p, %" G_GSIZE_FORMAT, in, out, num_samples); + GST_LOG ("quantize %p, %p %" G_GSIZE_FORMAT, in, out, num_samples); gst_audio_quantize_samples (convert->quant, in, out, num_samples); @@ -679,7 +675,8 @@ chain_resample (GstAudioConverter * convert, AudioChain * prev) GstAudioFormat format = convert->current_format; gint channels = convert->current_channels; - if (in->rate != out->rate) { + if (in->rate != out->rate + || convert->flags & GST_AUDIO_CONVERTER_FLAG_VARIABLE_RATE) { method = GET_OPT_RESAMPLER_METHOD (convert); flags = 0; @@ -885,7 +882,7 @@ converter_generic (GstAudioConverter * convert, /** * gst_audio_converter_new: (skip) - * @flags: #GstAudioConverterFlags + * @flags: extra #GstAudioConverterFlags * @in_info: a source #GstAudioInfo * @out_info: a destination #GstAudioInfo * @config: (transfer full): a #GstStructure with configuration options @@ -1062,52 +1059,6 @@ gst_audio_converter_get_max_latency (GstAudioConverter * convert) return 0; } -/** - * gst_audio_converter_update_rates: - * @convert: a #GstAudioConverter - * @in_rate: input rate - * @out_rate: output rate - * @options: resampler options - * - * Update the input and output rates, passing @options to the resampler. - * - * Returns: %TRUE on success. - */ -gboolean -gst_audio_converter_update_rates (GstAudioConverter * convert, - gint in_rate, gint out_rate, GstStructure * options) -{ - g_return_val_if_fail (convert != NULL, FALSE); - g_return_val_if_fail (in_rate > 0, FALSE); - g_return_val_if_fail (out_rate > 0, FALSE); - - convert->in.rate = in_rate; - convert->out.rate = out_rate; - - if (options) - gst_structure_free (options); - - return TRUE; -} - -/** - * gst_audio_converter_get_rates: - * @convert: a #GstAudioConverter - * @in_rate: input rate - * @out_rate: output rate - * - * Get the current input and output rates. - */ -void -gst_audio_converter_get_rates (GstAudioConverter * convert, - gint * in_rate, gint * out_rate) -{ - if (in_rate) - *in_rate = convert->in.rate; - if (out_rate) - *out_rate = convert->out.rate; -} - /** * gst_audio_converter_reset: * @convert: a #GstAudioConverter @@ -1158,8 +1109,6 @@ gst_audio_converter_samples (GstAudioConverter * convert, g_return_val_if_fail (convert != NULL, FALSE); g_return_val_if_fail (out != NULL, FALSE); - in_frames = MIN (in_frames, out_frames); - if (in_frames == 0) { GST_LOG ("skipping empty buffer"); return TRUE; diff --git a/gst-libs/gst/audio/audio-converter.h b/gst-libs/gst/audio/audio-converter.h index cb47889f8f..1f6ddc5d1f 100644 --- a/gst-libs/gst/audio/audio-converter.h +++ b/gst-libs/gst/audio/audio-converter.h @@ -103,12 +103,6 @@ gsize gst_audio_converter_get_in_frames (GstAudioConverter *con gsize gst_audio_converter_get_max_latency (GstAudioConverter *convert); -gboolean gst_audio_converter_update_rates (GstAudioConverter *convert, - gint in_rate, gint out_rate, - GstStructure *options); -void gst_audio_converter_get_rates (GstAudioConverter *convert, - gint *in_rate, gint *out_rate); - gboolean gst_audio_converter_samples (GstAudioConverter * convert, GstAudioConverterFlags flags, gpointer in[], gsize in_frames, diff --git a/gst-libs/gst/audio/audio-resampler.c b/gst-libs/gst/audio/audio-resampler.c index 355456e3fc..9f848a96e2 100644 --- a/gst-libs/gst/audio/audio-resampler.c +++ b/gst-libs/gst/audio/audio-resampler.c @@ -39,7 +39,7 @@ typedef struct _Tap typedef void (*MakeTapsFunc) (GstAudioResampler * resampler, Tap * t, gint j); typedef void (*ResampleFunc) (GstAudioResampler * resampler, gpointer in[], gsize in_len, gpointer out[], gsize out_len, gsize * consumed, - gsize * produced, gboolean move); + gboolean move); typedef void (*DeinterleaveFunc) (GstAudioResampler * resampler, gpointer * sbuf, gpointer in[], gsize in_frames); typedef void (*MirrorFunc) (GstAudioResampler * resampler, gpointer * sbuf); @@ -377,8 +377,7 @@ make_taps (GstAudioResampler * resampler, Tap * t, gint j) #define MAKE_RESAMPLE_FUNC(type) \ static void \ resample_ ##type (GstAudioResampler * resampler, gpointer in[], gsize in_len, \ - gpointer out[], gsize out_len, gsize * consumed, gsize * produced, \ - gboolean move) \ + gpointer out[], gsize out_len, gsize * consumed, gboolean move) \ { \ gint c, di = 0; \ gint n_taps = resampler->n_taps; \ @@ -411,7 +410,6 @@ resample_ ##type (GstAudioResampler * resampler, gpointer in[], gsize in_len, memmove (ip, &ip[samp_index], (in_len - samp_index) * sizeof(type)); \ } \ *consumed = samp_index - resampler->samp_index; \ - *produced = di; \ \ resampler->samp_index = move ? 0 : samp_index; \ resampler->samp_phase = samp_phase; \ @@ -425,8 +423,7 @@ MAKE_RESAMPLE_FUNC (gint16); #define MAKE_RESAMPLE_INTERLEAVED_FUNC(type,channels) \ static void \ resample_interleaved_ ##type##_##channels (GstAudioResampler * resampler, gpointer in[],\ - gsize in_len, gpointer out[], gsize out_len, gsize * consumed, gsize * produced, \ - gboolean move) \ + gsize in_len, gpointer out[], gsize out_len, gsize * consumed, gboolean move) \ { \ gint di = 0; \ gint n_taps = resampler->n_taps; \ @@ -459,7 +456,6 @@ resample_interleaved_ ##type##_##channels (GstAudioResampler * resampler, gpoint (in_len - samp_index) * sizeof(type) * channels); \ } \ *consumed = samp_index - resampler->samp_index; \ - *produced = di; \ \ resampler->samp_index = move ? 0 : samp_index; \ resampler->samp_phase = samp_phase; \ @@ -861,6 +857,8 @@ gst_audio_resampler_new (GstAudioResamplerMethod method, * Update the resampler parameters for @resampler. This function should * not be called concurrently with any other function on @resampler. * + * When @in_rate or @out_rate is 0, its value is unchanged. + * * Returns: %TRUE if the new parameters could be set */ gboolean @@ -870,8 +868,11 @@ gst_audio_resampler_update (GstAudioResampler * resampler, gint gcd; g_return_val_if_fail (resampler != NULL, FALSE); - g_return_val_if_fail (in_rate != 0, FALSE); - g_return_val_if_fail (out_rate != 0, FALSE); + + if (in_rate == 0) + in_rate = resampler->in_rate; + if (out_rate == 0) + out_rate = resampler->out_rate; gcd = gst_util_greatest_common_divisor (in_rate, out_rate); in_rate /= gcd; @@ -1034,12 +1035,9 @@ get_sample_bufs (GstAudioResampler * resampler, gsize need) * @in: input samples * @in_frames: number of input frames * @out: output samples - * @out_frames: maximum output frames - * @in_consumed: number of frames consumed - * @out_produced: number of frames produced + * @out_frames: number of output frames * - * Perform resampling on @in_frames frames in @in and write at most - * @out_frames of frames to @out. + * Perform resampling on @in_frames frames in @in and write @out_frames to @out. * * In case the samples are interleaved, @in and @out must point to an * array with a single element pointing to a block of interleaved samples. @@ -1047,30 +1045,26 @@ get_sample_bufs (GstAudioResampler * resampler, gsize need) * If non-interleaved samples are used, @in and @out must point to an * array with pointers to memory blocks, one for each channel. * - * @in may be %NULL, in which case @in_frames of 0 samples are pushed + * @in may be %NULL, in which case @in_frames of silence samples are pushed * into the resampler. * - * The number of frames consumed is returned in @consumed and can be - * less than @in_frames due to latency of the resampler or because - * the number of samples produced equals @out_frames. - * - * The number of frames produced is returned in @produced. + * This function always produces @out_frames of output and consumes @in_frames of + * input. Use gst_audio_resampler_get_out_frames() and + * gst_audio_resampler_get_in_frames() to make sure @in_frames and @out_frames + * are matching and @in and @out point to enough memory. */ void gst_audio_resampler_resample (GstAudioResampler * resampler, - gpointer in[], gsize in_frames, gpointer out[], gsize out_frames, - gsize * in_consumed, gsize * out_produced) + gpointer in[], gsize in_frames, gpointer out[], gsize out_frames) { gsize samples_avail; - gsize out2, need; + gsize need, consumed; gpointer *sbuf; /* do sample skipping */ if (resampler->skip >= in_frames) { /* we need tp skip all input */ resampler->skip -= in_frames; - *in_consumed = in_frames; - *out_produced = 0; return; } /* skip the last samples by advancing the sample index */ @@ -1090,8 +1084,6 @@ gst_audio_resampler_resample (GstAudioResampler * resampler, need = resampler->n_taps + resampler->samp_index; if (samples_avail < need) { /* not enough samples to start */ - *in_consumed = in_frames; - *out_produced = 0; return; } @@ -1101,21 +1093,16 @@ gst_audio_resampler_resample (GstAudioResampler * resampler, resampler->filling = FALSE; } - /* calculate maximum number of available output samples */ - out2 = calc_out (resampler, samples_avail - need); - out_frames = MIN (out2, out_frames); - /* resample all channels */ resampler->resample (resampler, sbuf, samples_avail, out, out_frames, - in_consumed, out_produced, TRUE); + &consumed, TRUE); GST_LOG ("in %" G_GSIZE_FORMAT ", used %" G_GSIZE_FORMAT ", consumed %" - G_GSIZE_FORMAT ", produced %" G_GSIZE_FORMAT, in_frames, samples_avail, - *in_consumed, *out_produced); + G_GSIZE_FORMAT, in_frames, samples_avail, consumed); /* update pointers */ - if (*in_consumed > 0) { - gssize left = samples_avail - *in_consumed; + if (consumed > 0) { + gssize left = samples_avail - consumed; if (left > 0) { /* we consumed part of our samples */ resampler->samples_avail = left; @@ -1124,7 +1111,5 @@ gst_audio_resampler_resample (GstAudioResampler * resampler, resampler->samples_avail = 0; resampler->skip = -left; } - /* we always consume everything */ - *in_consumed = in_frames; } } diff --git a/gst-libs/gst/audio/audio-resampler.h b/gst-libs/gst/audio/audio-resampler.h index 7ef220d1c5..c31789af2b 100644 --- a/gst-libs/gst/audio/audio-resampler.h +++ b/gst-libs/gst/audio/audio-resampler.h @@ -178,8 +178,7 @@ gsize gst_audio_resampler_get_max_latency (GstAudioResampler *res void gst_audio_resampler_resample (GstAudioResampler * resampler, gpointer in[], gsize in_frames, - gpointer out[], gsize out_frames, - gsize *in_consumed, gsize *out_produced); + gpointer out[], gsize out_frames); G_END_DECLS diff --git a/gst/audioresample/gstaudioresample.c b/gst/audioresample/gstaudioresample.c index 7f20b9b706..2faf6752b8 100644 --- a/gst/audioresample/gstaudioresample.c +++ b/gst/audioresample/gstaudioresample.c @@ -386,14 +386,16 @@ gst_audio_resample_update_state (GstAudioResample * resample, GstAudioInfo * in, resample->converter = NULL; } if (resample->converter == NULL) { - resample->converter = gst_audio_converter_new (0, in, out, options); + resample->converter = + gst_audio_converter_new (GST_AUDIO_CONVERTER_FLAG_VARIABLE_RATE, in, + out, options); if (resample->converter == NULL) goto resampler_failed; } else if (in && out) { gboolean ret; ret = - gst_audio_converter_update_rates (resample->converter, in->rate, + gst_audio_converter_update_config (resample->converter, in->rate, out->rate, options); if (!ret) goto update_failed; @@ -568,8 +570,7 @@ gst_audio_resample_push_drain (GstAudioResample * resample, guint history_len) GstBuffer *outbuf; GstFlowReturn res; gint outsize; - gsize in_processed; - gsize out_len, out_processed; + gsize out_len; GstMapInfo map; gpointer out[1]; @@ -591,11 +592,8 @@ gst_audio_resample_push_drain (GstAudioResample * resample, guint history_len) out[0] = map.data; gst_audio_converter_samples (resample->converter, 0, NULL, history_len, - out, out_len, &in_processed, &out_processed); + out, out_len); - /* If we wrote more than allocated something is really wrong now - * and we should better abort immediately */ - g_assert (out_len == out_processed); gst_buffer_unmap (outbuf, &map); /* time */ @@ -604,7 +602,7 @@ gst_audio_resample_push_drain (GstAudioResample * resample, guint history_len) gst_util_uint64_scale_int_round (resample->samples_out, GST_SECOND, resample->out.rate); GST_BUFFER_DURATION (outbuf) = resample->t0 + - gst_util_uint64_scale_int_round (resample->samples_out + out_processed, + gst_util_uint64_scale_int_round (resample->samples_out + out_len, GST_SECOND, resample->out.rate) - GST_BUFFER_TIMESTAMP (outbuf); } else { GST_BUFFER_TIMESTAMP (outbuf) = GST_CLOCK_TIME_NONE; @@ -613,13 +611,13 @@ gst_audio_resample_push_drain (GstAudioResample * resample, guint history_len) /* offset */ if (resample->out_offset0 != GST_BUFFER_OFFSET_NONE) { GST_BUFFER_OFFSET (outbuf) = resample->out_offset0 + resample->samples_out; - GST_BUFFER_OFFSET_END (outbuf) = GST_BUFFER_OFFSET (outbuf) + out_processed; + GST_BUFFER_OFFSET_END (outbuf) = GST_BUFFER_OFFSET (outbuf) + out_len; } else { GST_BUFFER_OFFSET (outbuf) = GST_BUFFER_OFFSET_NONE; GST_BUFFER_OFFSET_END (outbuf) = GST_BUFFER_OFFSET_NONE; } /* move along */ - resample->samples_out += out_processed; + resample->samples_out += out_len; resample->samples_in += history_len; GST_LOG_OBJECT (resample, @@ -742,20 +740,23 @@ gst_audio_resample_process (GstAudioResample * resample, GstBuffer * inbuf, { GstMapInfo in_map, out_map; gsize outsize; - guint32 in_len, in_processed; - guint32 out_len, out_processed; + guint32 in_len; + guint32 out_len; guint filt_len = gst_audio_converter_get_max_latency (resample->converter) * 2; + gboolean inbuf_writable; - gst_buffer_map (inbuf, &in_map, GST_MAP_READ); + inbuf_writable = gst_buffer_is_writable (inbuf) + && gst_buffer_n_memory (inbuf) == 1 + && gst_memory_is_writable (gst_buffer_peek_memory (inbuf, 0)); + + gst_buffer_map (inbuf, &in_map, + inbuf_writable ? GST_MAP_READWRITE : GST_MAP_READ); gst_buffer_map (outbuf, &out_map, GST_MAP_WRITE); in_len = in_map.size / resample->in.bpf; out_len = out_map.size / resample->out.bpf; - in_processed = in_len; - out_processed = out_len; - if (GST_BUFFER_FLAG_IS_SET (inbuf, GST_BUFFER_FLAG_GAP)) { resample->num_nongap_samples = 0; if (resample->num_gap_samples < filt_len) { @@ -777,16 +778,15 @@ gst_audio_resample_process (GstAudioResample * resample, GstBuffer * inbuf, den = resample->out.rate; if (resample->samples_in + in_len >= filt_len / 2) - out_processed = + out_len = gst_util_uint64_scale_int_ceil (resample->samples_in + in_len - filt_len / 2, den, num) - resample->samples_out; else - out_processed = 0; + out_len = 0; memset (out_map.data, 0, out_map.size); GST_BUFFER_FLAG_SET (outbuf, GST_BUFFER_FLAG_GAP); resample->num_gap_samples += in_len; - in_processed = in_len; } } else { /* not a gap */ if (resample->num_gap_samples > filt_len) { @@ -807,43 +807,31 @@ gst_audio_resample_process (GstAudioResample * resample, GstBuffer * inbuf, { /* process */ { - gsize in_proc, out_proc, out_test; gpointer in[1], out[1]; + GstAudioConverterFlags flags; - out_test = + out_len = gst_audio_converter_get_out_frames (resample->converter, in_len); - out_test = MIN (out_test, out_len); + + flags = 0; + if (inbuf_writable) + flags |= GST_AUDIO_CONVERTER_FLAG_IN_WRITABLE; in[0] = in_map.data; out[0] = out_map.data; - gst_audio_converter_samples (resample->converter, 0, in, in_len, - out, out_len, &in_proc, &out_proc); - - in_processed = in_proc; - out_processed = out_proc; - - //g_printerr ("in %d, test %d, %d, real %d (%d)\n", (gint) in_len, (gint) out_test, (gint) out_len, (gint) out_proc, (gint) (out_test - out_proc)); - g_assert (out_test == out_proc); + gst_audio_converter_samples (resample->converter, flags, in, in_len, + out, out_len); } } } - /* If we wrote more than allocated something is really wrong now and we - * should better abort immediately */ - g_assert (out_len >= out_processed); - - if (G_UNLIKELY (in_len != in_processed)) { - GST_WARNING_OBJECT (resample, "converted %d of %d input samples", - in_processed, in_len); - } - /* time */ if (GST_CLOCK_TIME_IS_VALID (resample->t0)) { GST_BUFFER_TIMESTAMP (outbuf) = resample->t0 + gst_util_uint64_scale_int_round (resample->samples_out, GST_SECOND, resample->out.rate); GST_BUFFER_DURATION (outbuf) = resample->t0 + - gst_util_uint64_scale_int_round (resample->samples_out + out_processed, + gst_util_uint64_scale_int_round (resample->samples_out + out_len, GST_SECOND, resample->out.rate) - GST_BUFFER_TIMESTAMP (outbuf); } else { GST_BUFFER_TIMESTAMP (outbuf) = GST_CLOCK_TIME_NONE; @@ -852,26 +840,26 @@ gst_audio_resample_process (GstAudioResample * resample, GstBuffer * inbuf, /* offset */ if (resample->out_offset0 != GST_BUFFER_OFFSET_NONE) { GST_BUFFER_OFFSET (outbuf) = resample->out_offset0 + resample->samples_out; - GST_BUFFER_OFFSET_END (outbuf) = GST_BUFFER_OFFSET (outbuf) + out_processed; + GST_BUFFER_OFFSET_END (outbuf) = GST_BUFFER_OFFSET (outbuf) + out_len; } else { GST_BUFFER_OFFSET (outbuf) = GST_BUFFER_OFFSET_NONE; GST_BUFFER_OFFSET_END (outbuf) = GST_BUFFER_OFFSET_NONE; } /* move along */ - resample->samples_out += out_processed; + resample->samples_out += out_len; resample->samples_in += in_len; gst_buffer_unmap (inbuf, &in_map); gst_buffer_unmap (outbuf, &out_map); - outsize = out_processed * resample->in.bpf; + outsize = out_len * resample->in.bpf; gst_buffer_resize (outbuf, 0, outsize); GST_LOG_OBJECT (resample, "Converted to buffer of %" G_GUINT32_FORMAT " samples (%" G_GSIZE_FORMAT " bytes) with timestamp %" GST_TIME_FORMAT ", duration %" GST_TIME_FORMAT ", offset %" G_GUINT64_FORMAT - ", offset_end %" G_GUINT64_FORMAT, out_processed, outsize, + ", offset_end %" G_GUINT64_FORMAT, out_len, outsize, GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (outbuf)), GST_TIME_ARGS (GST_BUFFER_DURATION (outbuf)), GST_BUFFER_OFFSET (outbuf), GST_BUFFER_OFFSET_END (outbuf));