diff options
author | wm4 <wm4@nowhere> | 2012-11-05 17:02:04 +0100 |
---|---|---|
committer | wm4 <wm4@nowhere> | 2012-11-12 20:06:14 +0100 |
commit | d4bdd0473d6f43132257c9fb3848d829755167a3 (patch) | |
tree | 8021c2f7da1841393c8c832105e20cd527826d6c /audio | |
parent | bd48deba77bd5582c5829d6fe73a7d2571088aba (diff) |
Rename directories, move files (step 1 of 2) (does not compile)
Tis drops the silly lib prefixes, and attempts to organize the tree in
a more logical way. Make the top-level directory less cluttered as
well.
Renames the following directories:
libaf -> audio/filter
libao2 -> audio/out
libvo -> video/out
libmpdemux -> demux
Split libmpcodecs:
vf* -> video/filter
vd*, dec_video.* -> video/decode
mp_image*, img_format*, ... -> video/
ad*, dec_audio.* -> audio/decode
libaf/format.* is moved to audio/ - this is similar to how mp_image.*
is located in video/.
Move most top-level .c/.h files to core. (talloc.c/.h is left on top-
level, because it's external.) Park some of the more annoying files
in compat/. Some of these are relicts from the time mplayer used
ffmpeg internals.
sub/ is not split, because it's too much of a mess (subtitle code is
mixed with OSD display and rendering).
Maybe the organization of core is not ideal: it mixes playback core
(like mplayer.c) and utility helpers (like bstr.c/h). Should the need
arise, the playback core will be moved somewhere else, while core
contains all helper and common code.
Diffstat (limited to 'audio')
68 files changed, 22057 insertions, 0 deletions
diff --git a/audio/decode/ad.c b/audio/decode/ad.c new file mode 100644 index 0000000000..93cebed86d --- /dev/null +++ b/audio/decode/ad.c @@ -0,0 +1,50 @@ +/* + * audio decoder interface + * + * This file is part of MPlayer. + * + * MPlayer 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. + * + * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "config.h" + +#include "stream/stream.h" +#include "libmpdemux/demuxer.h" +#include "libmpdemux/stheader.h" +#include "ad.h" + +/* Missed vorbis, mad, dshow */ + +extern const ad_functions_t mpcodecs_ad_mpg123; +extern const ad_functions_t mpcodecs_ad_ffmpeg; +extern const ad_functions_t mpcodecs_ad_pcm; +extern const ad_functions_t mpcodecs_ad_dvdpcm; +extern const ad_functions_t mpcodecs_ad_spdif; + +const ad_functions_t * const mpcodecs_ad_drivers[] = +{ +#ifdef CONFIG_MPG123 + &mpcodecs_ad_mpg123, +#endif + &mpcodecs_ad_ffmpeg, + &mpcodecs_ad_pcm, + &mpcodecs_ad_dvdpcm, + &mpcodecs_ad_spdif, + NULL +}; diff --git a/audio/decode/ad.h b/audio/decode/ad.h new file mode 100644 index 0000000000..5396085d04 --- /dev/null +++ b/audio/decode/ad.h @@ -0,0 +1,54 @@ +/* + * This file is part of MPlayer. + * + * MPlayer 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. + * + * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPLAYER_AD_H +#define MPLAYER_AD_H + +#include "mpc_info.h" +#include "libmpdemux/stheader.h" + +typedef struct mp_codec_info ad_info_t; + +/* interface of video decoder drivers */ +typedef struct ad_functions +{ + const ad_info_t *info; + int (*preinit)(sh_audio_t *sh); + int (*init)(sh_audio_t *sh); + void (*uninit)(sh_audio_t *sh); + int (*control)(sh_audio_t *sh,int cmd,void* arg, ...); + int (*decode_audio)(sh_audio_t *sh, unsigned char *buffer, int minlen, + int maxlen); +} ad_functions_t; + +// NULL terminated array of all drivers +extern const ad_functions_t * const mpcodecs_ad_drivers[]; + +// fallback if ADCTRL_RESYNC not implemented: sh_audio->a_in_buffer_len=0; +#define ADCTRL_RESYNC_STREAM 1 // resync, called after seeking + +// fallback if ADCTRL_SKIP not implemented: ds_fill_buffer(sh_audio->ds); +#define ADCTRL_SKIP_FRAME 2 // skip block/frame, called while seeking + +// fallback if ADCTRL_QUERY_FORMAT not implemented: sh_audio->sample_format +#define ADCTRL_QUERY_FORMAT 3 // test for availabilty of a format + +// fallback: use hw mixer in libao +#define ADCTRL_SET_VOLUME 4 // not used at the moment + +#endif /* MPLAYER_AD_H */ diff --git a/audio/decode/ad_dvdpcm.c b/audio/decode/ad_dvdpcm.c new file mode 100644 index 0000000000..41f6a1426d --- /dev/null +++ b/audio/decode/ad_dvdpcm.c @@ -0,0 +1,162 @@ +/* + * This file is part of MPlayer. + * + * MPlayer 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. + * + * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +#include "config.h" +#include "mp_msg.h" +#include "ad_internal.h" + +static const ad_info_t info = +{ + "Uncompressed DVD/VOB LPCM audio decoder", + "dvdpcm", + "Nick Kurshev", + "A'rpi", + "" +}; + +LIBAD_EXTERN(dvdpcm) + +static int init(sh_audio_t *sh) +{ +/* DVD PCM Audio:*/ + sh->i_bps = 0; + if(sh->codecdata_len==3){ + // we have LPCM header: + unsigned char h=sh->codecdata[1]; + sh->channels=1+(h&7); + switch((h>>4)&3){ + case 0: sh->samplerate=48000;break; + case 1: sh->samplerate=96000;break; + case 2: sh->samplerate=44100;break; + case 3: sh->samplerate=32000;break; + } + switch ((h >> 6) & 3) { + case 0: + sh->sample_format = AF_FORMAT_S16_BE; + sh->samplesize = 2; + break; + case 1: + mp_tmsg(MSGT_DECAUDIO, MSGL_INFO, "Samples of this format are needed to improve support. Please contact the developers.\n"); + sh->i_bps = sh->channels * sh->samplerate * 5 / 2; + case 2: + sh->sample_format = AF_FORMAT_S24_BE; + sh->samplesize = 3; + break; + default: + sh->sample_format = AF_FORMAT_S16_BE; + sh->samplesize = 2; + } + } else { + // use defaults: + sh->channels=2; + sh->samplerate=48000; + sh->sample_format = AF_FORMAT_S16_BE; + sh->samplesize = 2; + } + if (!sh->i_bps) + sh->i_bps = sh->samplesize * sh->channels * sh->samplerate; + return 1; +} + +static int preinit(sh_audio_t *sh) +{ + sh->audio_out_minsize=2048; + return 1; +} + +static void uninit(sh_audio_t *sh) +{ +} + +static int control(sh_audio_t *sh,int cmd,void* arg, ...) +{ + int skip; + switch(cmd) + { + case ADCTRL_SKIP_FRAME: + skip=sh->i_bps/16; + skip=skip&(~3); + demux_read_data(sh->ds,NULL,skip); + return CONTROL_TRUE; + } + return CONTROL_UNKNOWN; +} + +static int decode_audio(sh_audio_t *sh_audio,unsigned char *buf,int minlen,int maxlen) +{ + int j,len; + if (sh_audio->samplesize == 3) { + if (((sh_audio->codecdata[1] >> 6) & 3) == 1) { + // 20 bit + // not sure if the "& 0xf0" and "<< 4" are the right way around + // can somebody clarify? + for (j = 0; j < minlen; j += 12) { + char tmp[10]; + len = demux_read_data(sh_audio->ds, tmp, 10); + if (len < 10) break; + // first sample + buf[j + 0] = tmp[0]; + buf[j + 1] = tmp[1]; + buf[j + 2] = tmp[8] & 0xf0; + // second sample + buf[j + 3] = tmp[2]; + buf[j + 4] = tmp[3]; + buf[j + 5] = tmp[8] << 4; + // third sample + buf[j + 6] = tmp[4]; + buf[j + 7] = tmp[5]; + buf[j + 8] = tmp[9] & 0xf0; + // fourth sample + buf[j + 9] = tmp[6]; + buf[j + 10] = tmp[7]; + buf[j + 11] = tmp[9] << 4; + } + len = j; + } else { + // 24 bit + for (j = 0; j < minlen; j += 12) { + char tmp[12]; + len = demux_read_data(sh_audio->ds, tmp, 12); + if (len < 12) break; + // first sample + buf[j + 0] = tmp[0]; + buf[j + 1] = tmp[1]; + buf[j + 2] = tmp[8]; + // second sample + buf[j + 3] = tmp[2]; + buf[j + 4] = tmp[3]; + buf[j + 5] = tmp[9]; + // third sample + buf[j + 6] = tmp[4]; + buf[j + 7] = tmp[5]; + buf[j + 8] = tmp[10]; + // fourth sample + buf[j + 9] = tmp[6]; + buf[j + 10] = tmp[7]; + buf[j + 11] = tmp[11]; + } + len = j; + } + } else + len=demux_read_data(sh_audio->ds,buf,(minlen+3)&(~3)); + return len; +} diff --git a/audio/decode/ad_internal.h b/audio/decode/ad_internal.h new file mode 100644 index 0000000000..4cffc95126 --- /dev/null +++ b/audio/decode/ad_internal.h @@ -0,0 +1,46 @@ +/* + * This file is part of MPlayer. + * + * MPlayer 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. + * + * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPLAYER_AD_INTERNAL_H +#define MPLAYER_AD_INTERNAL_H + +#include "codec-cfg.h" +#include "libaf/format.h" + +#include "stream/stream.h" +#include "libmpdemux/demuxer.h" +#include "libmpdemux/stheader.h" + +#include "ad.h" + +static int init(sh_audio_t *sh); +static int preinit(sh_audio_t *sh); +static void uninit(sh_audio_t *sh); +static int control(sh_audio_t *sh,int cmd,void* arg, ...); +static int decode_audio(sh_audio_t *sh,unsigned char *buffer,int minlen,int maxlen); + +#define LIBAD_EXTERN(x) const ad_functions_t mpcodecs_ad_##x = {\ + &info,\ + preinit,\ + init,\ + uninit,\ + control,\ + decode_audio\ +}; + +#endif /* MPLAYER_AD_INTERNAL_H */ diff --git a/audio/decode/ad_lavc.c b/audio/decode/ad_lavc.c new file mode 100644 index 0000000000..2eacfadb8f --- /dev/null +++ b/audio/decode/ad_lavc.c @@ -0,0 +1,413 @@ +/* + * This file is part of MPlayer. + * + * MPlayer 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. + * + * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <stdbool.h> +#include <assert.h> + +#include <libavcodec/avcodec.h> +#include <libavutil/opt.h> + +#include "talloc.h" + +#include "config.h" +#include "mp_msg.h" +#include "options.h" + +#include "ad_internal.h" +#include "libaf/reorder_ch.h" + +#include "mpbswap.h" + +static const ad_info_t info = +{ + "libavcodec audio decoders", + "ffmpeg", + "", + "", + "", + .print_name = "libavcodec", +}; + +LIBAD_EXTERN(ffmpeg) + +struct priv { + AVCodecContext *avctx; + AVFrame *avframe; + char *output; + char *output_packed; // used by deplanarize to store packed audio samples + int output_left; + int unitsize; + int previous_data_left; // input demuxer packet data +}; + +static int preinit(sh_audio_t *sh) +{ + return 1; +} + +/* Prefer playing audio with the samplerate given in container data + * if available, but take number the number of channels and sample format + * from the codec, since if the codec isn't using the correct values for + * those everything breaks anyway. + */ +static int setup_format(sh_audio_t *sh_audio, + const AVCodecContext *lavc_context) +{ + int sample_format = sh_audio->sample_format; + switch (av_get_packed_sample_fmt(lavc_context->sample_fmt)) { + case AV_SAMPLE_FMT_U8: sample_format = AF_FORMAT_U8; break; + case AV_SAMPLE_FMT_S16: sample_format = AF_FORMAT_S16_NE; break; + case AV_SAMPLE_FMT_S32: sample_format = AF_FORMAT_S32_NE; break; + case AV_SAMPLE_FMT_FLT: sample_format = AF_FORMAT_FLOAT_NE; break; + default: + mp_msg(MSGT_DECAUDIO, MSGL_FATAL, "Unsupported sample format\n"); + sample_format = AF_FORMAT_UNKNOWN; + } + + bool broken_srate = false; + int samplerate = lavc_context->sample_rate; + int container_samplerate = sh_audio->container_out_samplerate; + if (!container_samplerate && sh_audio->wf) + container_samplerate = sh_audio->wf->nSamplesPerSec; + if (lavc_context->codec_id == CODEC_ID_AAC + && samplerate == 2 * container_samplerate) + broken_srate = true; + else if (container_samplerate) + samplerate = container_samplerate; + + if (lavc_context->channels != sh_audio->channels || + samplerate != sh_audio->samplerate || + sample_format != sh_audio->sample_format) { + sh_audio->channels = lavc_context->channels; + sh_audio->samplerate = samplerate; + sh_audio->sample_format = sample_format; + sh_audio->samplesize = af_fmt2bits(sh_audio->sample_format) / 8; + if (broken_srate) + mp_msg(MSGT_DECAUDIO, MSGL_WARN, + "Ignoring broken container sample rate for AAC with SBR\n"); + return 1; + } + return 0; +} + +static int init(sh_audio_t *sh_audio) +{ + struct MPOpts *opts = sh_audio->opts; + AVCodecContext *lavc_context; + AVCodec *lavc_codec; + + if (sh_audio->codec->dll) { + lavc_codec = avcodec_find_decoder_by_name(sh_audio->codec->dll); + if (!lavc_codec) { + mp_tmsg(MSGT_DECAUDIO, MSGL_ERR, + "Cannot find codec '%s' in libavcodec...\n", + sh_audio->codec->dll); + return 0; + } + } else if (!sh_audio->libav_codec_id) { + mp_tmsg(MSGT_DECAUDIO, MSGL_INFO, "No Libav codec ID known. " + "Generic lavc decoder is not applicable.\n"); + return 0; + } else { + lavc_codec = avcodec_find_decoder(sh_audio->libav_codec_id); + if (!lavc_codec) { + mp_tmsg(MSGT_DECAUDIO, MSGL_INFO, "Libavcodec has no decoder " + "for this codec\n"); + return 0; + } + } + + sh_audio->codecname = lavc_codec->long_name; + if (!sh_audio->codecname) + sh_audio->codecname = lavc_codec->name; + + struct priv *ctx = talloc_zero(NULL, struct priv); + sh_audio->context = ctx; + lavc_context = avcodec_alloc_context3(lavc_codec); + ctx->avctx = lavc_context; + ctx->avframe = avcodec_alloc_frame(); + + // Always try to set - option only exists for AC3 at the moment + av_opt_set_double(lavc_context, "drc_scale", opts->drc_level, + AV_OPT_SEARCH_CHILDREN); + lavc_context->sample_rate = sh_audio->samplerate; + lavc_context->bit_rate = sh_audio->i_bps * 8; + if (sh_audio->wf) { + lavc_context->channels = sh_audio->wf->nChannels; + lavc_context->sample_rate = sh_audio->wf->nSamplesPerSec; + lavc_context->bit_rate = sh_audio->wf->nAvgBytesPerSec * 8; + lavc_context->block_align = sh_audio->wf->nBlockAlign; + lavc_context->bits_per_coded_sample = sh_audio->wf->wBitsPerSample; + } + lavc_context->request_channels = opts->audio_output_channels; + lavc_context->codec_tag = sh_audio->format; //FOURCC + if (sh_audio->gsh->lavf_codec_tag) + lavc_context->codec_tag = sh_audio->gsh->lavf_codec_tag; + lavc_context->codec_type = AVMEDIA_TYPE_AUDIO; + lavc_context->codec_id = lavc_codec->id; // not sure if required, imho not --A'rpi + + /* alloc extra data */ + if (sh_audio->wf && sh_audio->wf->cbSize > 0) { + lavc_context->extradata = av_mallocz(sh_audio->wf->cbSize + FF_INPUT_BUFFER_PADDING_SIZE); + lavc_context->extradata_size = sh_audio->wf->cbSize; + memcpy(lavc_context->extradata, sh_audio->wf + 1, + lavc_context->extradata_size); + } + + // for QDM2 + if (sh_audio->codecdata_len && sh_audio->codecdata && + !lavc_context->extradata) { + lavc_context->extradata = av_malloc(sh_audio->codecdata_len + + FF_INPUT_BUFFER_PADDING_SIZE); + lavc_context->extradata_size = sh_audio->codecdata_len; + memcpy(lavc_context->extradata, (char *)sh_audio->codecdata, + lavc_context->extradata_size); + } + + /* open it */ + if (avcodec_open2(lavc_context, lavc_codec, NULL) < 0) { + mp_tmsg(MSGT_DECAUDIO, MSGL_ERR, "Could not open codec.\n"); + uninit(sh_audio); + return 0; + } + mp_msg(MSGT_DECAUDIO, MSGL_V, "INFO: libavcodec \"%s\" init OK!\n", + lavc_codec->name); + + if (sh_audio->format == 0x3343414D) { + // MACE 3:1 + sh_audio->ds->ss_div = 2 * 3; // 1 samples/packet + sh_audio->ds->ss_mul = 2 * sh_audio->wf->nChannels; // 1 byte*ch/packet + } else if (sh_audio->format == 0x3643414D) { + // MACE 6:1 + sh_audio->ds->ss_div = 2 * 6; // 1 samples/packet + sh_audio->ds->ss_mul = 2 * sh_audio->wf->nChannels; // 1 byte*ch/packet + } + + // Decode at least 1 byte: (to get header filled) + for (int tries = 0;;) { + int x = decode_audio(sh_audio, sh_audio->a_buffer, 1, + sh_audio->a_buffer_size); + if (x > 0) { + sh_audio->a_buffer_len = x; + break; + } + if (++tries >= 5) { + mp_msg(MSGT_DECAUDIO, MSGL_ERR, + "ad_ffmpeg: initial decode failed\n"); + uninit(sh_audio); + return 0; + } + } + + sh_audio->i_bps = lavc_context->bit_rate / 8; + if (sh_audio->wf && sh_audio->wf->nAvgBytesPerSec) + sh_audio->i_bps = sh_audio->wf->nAvgBytesPerSec; + + switch (av_get_packed_sample_fmt(lavc_context->sample_fmt)) { + case AV_SAMPLE_FMT_U8: + case AV_SAMPLE_FMT_S16: + case AV_SAMPLE_FMT_S32: + case AV_SAMPLE_FMT_FLT: + break; + default: + uninit(sh_audio); + return 0; + } + return 1; +} + +static void uninit(sh_audio_t *sh) +{ + sh->codecname = NULL; + struct priv *ctx = sh->context; + if (!ctx) + return; + AVCodecContext *lavc_context = ctx->avctx; + + if (lavc_context) { + if (avcodec_close(lavc_context) < 0) + mp_tmsg(MSGT_DECVIDEO, MSGL_ERR, "Could not close codec.\n"); + av_freep(&lavc_context->extradata); + av_freep(&lavc_context); + } + avcodec_free_frame(&ctx->avframe); + talloc_free(ctx); + sh->context = NULL; +} + +static int control(sh_audio_t *sh, int cmd, void *arg, ...) +{ + struct priv *ctx = sh->context; + switch (cmd) { + case ADCTRL_RESYNC_STREAM: + avcodec_flush_buffers(ctx->avctx); + ds_clear_parser(sh->ds); + ctx->previous_data_left = 0; + ctx->output_left = 0; + return CONTROL_TRUE; + } + return CONTROL_UNKNOWN; +} + +static av_always_inline void deplanarize(struct sh_audio *sh) +{ + struct priv *priv = sh->context; + + size_t bps = av_get_bytes_per_sample(priv->avctx->sample_fmt); + size_t nb_samples = priv->avframe->nb_samples; + size_t channels = priv->avctx->channels; + size_t size = bps * nb_samples * channels; + + if (talloc_get_size(priv->output_packed) != size) + priv->output_packed = + talloc_realloc_size(priv, priv->output_packed, size); + + size_t offset = 0; + unsigned char *output_ptr = priv->output_packed; + unsigned char **src = priv->avframe->data; + + for (size_t s = 0; s < nb_samples; s++) { + for (size_t c = 0; c < channels; c++) { + memcpy(output_ptr, src[c] + offset, bps); + output_ptr += bps; + } + offset += bps; + } + + priv->output = priv->output_packed; +} + +static int decode_new_packet(struct sh_audio *sh) +{ + struct priv *priv = sh->context; + AVCodecContext *avctx = priv->avctx; + double pts = MP_NOPTS_VALUE; + int insize; + bool packet_already_used = priv->previous_data_left; + struct demux_packet *mpkt = ds_get_packet2(sh->ds, + priv->previous_data_left); + unsigned char *start; + if (!mpkt) { + assert(!priv->previous_data_left); + start = NULL; + insize = 0; + ds_parse(sh->ds, &start, &insize, pts, 0); + if (insize <= 0) + return -1; // error or EOF + } else { + assert(mpkt->len >= priv->previous_data_left); + if (!priv->previous_data_left) { + priv->previous_data_left = mpkt->len; + pts = mpkt->pts; + } + insize = priv->previous_data_left; + start = mpkt->buffer + mpkt->len - priv->previous_data_left; + int consumed = ds_parse(sh->ds, &start, &insize, pts, 0); + priv->previous_data_left -= consumed; + priv->previous_data_left = FFMAX(priv->previous_data_left, 0); + } + + AVPacket pkt; + av_init_packet(&pkt); + pkt.data = start; + pkt.size = insize; + if (mpkt && mpkt->avpacket) { + pkt.side_data = mpkt->avpacket->side_data; + pkt.side_data_elems = mpkt->avpacket->side_data_elems; + } + if (pts != MP_NOPTS_VALUE && !packet_already_used) { + sh->pts = pts; + sh->pts_bytes = 0; + } + int got_frame = 0; + int ret = avcodec_decode_audio4(avctx, priv->avframe, &got_frame, &pkt); + // LATM may need many packets to find mux info + if (ret == AVERROR(EAGAIN)) + return 0; + if (ret < 0) { + mp_msg(MSGT_DECAUDIO, MSGL_V, "lavc_audio: error\n"); + return -1; + } + // The "insize >= ret" test is sanity check against decoder overreads + if (!sh->parser && insize >= ret) + priv->previous_data_left = insize - ret; + if (!got_frame) + return 0; + uint64_t unitsize = (uint64_t)av_get_bytes_per_sample(avctx->sample_fmt) * + avctx->channels; + if (unitsize > 100000) + abort(); + priv->unitsize = unitsize; + uint64_t output_left = unitsize * priv->avframe->nb_samples; + if (output_left > 500000000) + abort(); + priv->output_left = output_left; + if (av_sample_fmt_is_planar(avctx->sample_fmt) && avctx->channels > 1) { + deplanarize(sh); + } else { + priv->output = priv->avframe->data[0]; + } + mp_dbg(MSGT_DECAUDIO, MSGL_DBG2, "Decoded %d -> %d \n", insize, + priv->output_left); + return 0; +} + + +static int decode_audio(sh_audio_t *sh_audio, unsigned char *buf, int minlen, + int maxlen) +{ + struct priv *priv = sh_audio->context; + AVCodecContext *avctx = priv->avctx; + + int len = -1; + while (len < minlen) { + if (!priv->output_left) { + if (decode_new_packet(sh_audio) < 0) + break; + continue; + } + if (setup_format(sh_audio, avctx)) + return len; + int size = (minlen - len + priv->unitsize - 1); + size -= size % priv->unitsize; + size = FFMIN(size, priv->output_left); + if (size > maxlen) + abort(); + memcpy(buf, priv->output, size); + priv->output += size; + priv->output_left -= size; + if (avctx->channels >= 5) { + int samplesize = av_get_bytes_per_sample(avctx->sample_fmt); + reorder_channel_nch(buf, AF_CHANNEL_LAYOUT_LAVC_DEFAULT, + AF_CHANNEL_LAYOUT_MPLAYER_DEFAULT, + avctx->channels, + size / samplesize, samplesize); + } + if (len < 0) + len = size; + else + len += size; + buf += size; + maxlen -= size; + sh_audio->pts_bytes += size; + } + return len; +} diff --git a/audio/decode/ad_mpg123.c b/audio/decode/ad_mpg123.c new file mode 100644 index 0000000000..a3ce2cdcf6 --- /dev/null +++ b/audio/decode/ad_mpg123.c @@ -0,0 +1,489 @@ +/* + * MPEG 1.0/2.0/2.5 audio layer I, II, III decoding with libmpg123 + * + * Copyright (C) 2010-2012 Thomas Orgis <thomas@orgis.org> + * + * MPlayer 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. + * + * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +#include "config.h" + +#include "ad_internal.h" + +static const ad_info_t info = { + "MPEG 1.0/2.0/2.5 layers I, II, III", + "mpg123", + "Thomas Orgis", + "mpg123.org", + "High-performance decoder using libmpg123." +}; + +LIBAD_EXTERN(mpg123) + +/* Reducing the ifdeffery to two main variants: + * 1. most compatible to any libmpg123 version + * 2. fastest variant with recent libmpg123 (>=1.14) + * Running variant 2 on older libmpg123 versions may work in + * principle, but is not supported. + * So, please leave the check for MPG123_API_VERSION there, m-kay? + */ +#include <mpg123.h> + +/* Enable faster mode of operation with newer libmpg123, avoiding + * unnecessary memcpy() calls. */ +#if (defined MPG123_API_VERSION) && (MPG123_API_VERSION >= 33) +#define AD_MPG123_FRAMEWISE +#endif + +/* Switch for updating bitrate info of VBR files. Not essential. */ +#define AD_MPG123_MEAN_BITRATE + +/* Funny thing, that. I assume I shall use it for selecting mpg123 channels. + * Please correct me if I guessed wrong. */ +extern int fakemono; + +struct ad_mpg123_context { + mpg123_handle *handle; +#ifdef AD_MPG123_MEAN_BITRATE + /* Running mean for bit rate, stream length estimation. */ + float mean_rate; + unsigned int mean_count; + /* Time delay for updates. */ + short delay; +#endif + /* If the stream is actually VBR. */ + char vbr; +}; + +/* This initializes libmpg123 and prepares the handle, including funky + * parameters. */ +static int preinit(sh_audio_t *sh) +{ + int err, flag; + struct ad_mpg123_context *con; + /* Assumption: You always call preinit + init + uninit, on every file. + * But you stop at preinit in case it fails. + * If that is not true, one must ensure not to call mpg123_init / exit + * twice in a row. */ + if (mpg123_init() != MPG123_OK) + return 0; + + sh->context = malloc(sizeof(struct ad_mpg123_context)); + con = sh->context; + /* Auto-choice of optimized decoder (first argument NULL). */ + con->handle = mpg123_new(NULL, &err); + if (!con->handle) + goto bad_end; + + /* Guessing here: Default value triggers forced upmix of mono to stereo. */ + flag = fakemono == 0 ? MPG123_FORCE_STEREO : + fakemono == 1 ? MPG123_MONO_LEFT : + fakemono == 2 ? MPG123_MONO_RIGHT : 0; + if (mpg123_param(con->handle, MPG123_ADD_FLAGS, flag, 0.0) != MPG123_OK) + goto bad_end; + + /* Basic settings. + * Don't spill messages, enable better resync with non-seekable streams. + * Give both flags individually without error checking to keep going with + * old libmpg123. Generally, it is not fatal if the flags are not + * honored */ + mpg123_param(con->handle, MPG123_ADD_FLAGS, MPG123_QUIET, 0.0); + /* Do not bail out on malformed streams at all. + * MPlayer does not handle a decoder throwing the towel on crappy input. */ + mpg123_param(con->handle, MPG123_RESYNC_LIMIT, -1, 0.0); + + /* Open decisions: Configure libmpg123 to force encoding (or stay open about + * library builds that support only float or int32 output), (de)configure + * gapless decoding (won't work with seeking in MPlayer, though). + * Don't forget to eventually enable ReplayGain/RVA support, too. + * Let's try to run with the default for now. */ + + /* That would produce floating point output. + * You can get 32 and 24 bit ints, even 8 bit via format matrix. */ + /* mpg123_param(con->handle, MPG123_ADD_FLAGS, MPG123_FORCE_FLOAT, 0.); */ + + /* Example for RVA choice (available since libmpg123 1.0.0): + mpg123_param(con->handle, MPG123_RVA, MPG123_RVA_MIX, 0.0) */ + +#ifdef AD_MPG123_FRAMEWISE + /* Prevent funky automatic resampling. + * This way, we can be sure that one frame will never produce + * more than 1152 stereo samples. */ + mpg123_param(con->handle, MPG123_REMOVE_FLAGS, MPG123_AUTO_RESAMPLE, 0.); +#else + /* Older mpg123 is vulnerable to concatenated streams when gapless cutting + * is enabled (will only play the jingle of a badly constructed radio + * stream). The versions using framewise decoding are fine with that. */ + mpg123_param(con->handle, MPG123_REMOVE_FLAGS, MPG123_GAPLESS, 0.); +#endif + + return 1; + + bad_end: + if (!con->handle) + mp_msg(MSGT_DECAUDIO, MSGL_ERR, "mpg123 preinit error: %s\n", + mpg123_plain_strerror(err)); + else + mp_msg(MSGT_DECAUDIO, MSGL_ERR, "mpg123 preinit error: %s\n", + mpg123_strerror(con->handle)); + + if (con->handle) + mpg123_delete(con->handle); + mpg123_exit(); + free(sh->context); + sh->context = NULL; + return 0; +} + +/* Compute bitrate from frame size. */ +static int compute_bitrate(struct mpg123_frameinfo *i) +{ + static const int samples_per_frame[4][4] = { + {-1, 384, 1152, 1152}, /* MPEG 1 */ + {-1, 384, 1152, 576}, /* MPEG 2 */ + {-1, 384, 1152, 576}, /* MPEG 2.5 */ + {-1, -1, -1, -1}, /* Unknown */ + }; + return (int) ((i->framesize + 4) * 8 * i->rate * 0.001 / + samples_per_frame[i->version][i->layer] + 0.5); +} + +/* Opted against the header printout from old mp3lib, too much + * irrelevant info. This is modelled after the mpg123 app's + * standard output line. + * If more verbosity is demanded, one can add more detail and + * also throw in ID3v2 info which libmpg123 collects anyway. */ +static void print_header_compact(struct mpg123_frameinfo *i) +{ + static const char *smodes[5] = { + "stereo", "joint-stereo", "dual-channel", "mono", "invalid" + }; + static const char *layers[4] = { + "Unknown", "I", "II", "III" + }; + static const char *versions[4] = { + "1.0", "2.0", "2.5", "x.x" + }; + + mp_msg(MSGT_DECAUDIO, MSGL_V, "MPEG %s layer %s, ", + versions[i->version], layers[i->layer]); + switch (i->vbr) { + case MPG123_CBR: + if (i->bitrate) + mp_msg(MSGT_DECAUDIO, MSGL_V, "%d kbit/s", i->bitrate); + else + mp_msg(MSGT_DECAUDIO, MSGL_V, "%d kbit/s (free format)", + compute_bitrate(i)); + break; + case MPG123_VBR: + mp_msg(MSGT_DECAUDIO, MSGL_V, "VBR"); + break; + case MPG123_ABR: + mp_msg(MSGT_DECAUDIO, MSGL_V, "%d kbit/s ABR", i->abr_rate); + break; + default: + mp_msg(MSGT_DECAUDIO, MSGL_V, "???"); + } + mp_msg(MSGT_DECAUDIO, MSGL_V, ", %ld Hz %s\n", i->rate, + smodes[i->mode]); +} + +/* This tries to extract a requested amount of decoded data. + * Even when you request 0 bytes, it will feed enough input so that + * the decoder _could_ have delivered something. + * Returns byte count >= 0, -1 on error. + * + * Thoughts on exact pts keeping: + * We have to assume that MPEG frames are cut in pieces by packet boundaries. + * Also, it might be possible that the first packet does not contain enough + * data to ensure initial stream sync... or re-sync on erroneous streams. + * So we need something robust to relate the decoded byte count to the correct + * time stamp. This is tricky, though. From the outside, you cannot tell if, + * after having fed two packets until the first output arrives, one should + * start counting from the first packet's pts or the second packet's. + * So, let's just count from the last fed package's pts. If the packets are + * exactly cut to MPEG frames, this will cause one frame mismatch in the + * beginning (when mpg123 peeks ahead for the following header), but will + * be corrected with the third frame already. One might add special code to + * not increment the base pts past the first packet's after a resync before + * the first decoded bytes arrived. */ +static int decode_a_bit(sh_audio_t *sh, unsigned char *buf, int count) +{ + int ret = MPG123_OK; + int got = 0; + struct ad_mpg123_context *con = sh->context; + + /* There will be one MPG123_NEW_FORMAT message on first open. + * This will be handled in init(). */ + do { + size_t got_now = 0; + + /* Feed the decoder. This will only fire from the second round on. */ + if (ret == MPG123_NEED_MORE) { + int incount; + double pts; + unsigned char *inbuf; + /* Feed more input data. */ + incount = ds_get_packet_pts(sh->ds, &inbuf, &pts); + if (incount <= 0) + break; /* Apparently that's it. EOF. */ + + /* Next bytes from that presentation time. */ + if (pts != MP_NOPTS_VALUE) { + sh->pts = pts; + sh->pts_bytes = 0; + } + +#ifdef AD_MPG123_FRAMEWISE + /* Have to use mpg123_feed() to avoid decoding here. */ + ret = mpg123_feed(con->handle, inbuf, incount); +#else + /* Do not use mpg123_feed(), added in later libmpg123 versions. */ + ret = mpg123_decode(con->handle, inbuf, incount, NULL, 0, NULL); +#endif + if (ret == MPG123_ERR) + break; + } + /* Theoretically, mpg123 could return MPG123_DONE, so be prepared. + * Should not happen in our usage, but it is a valid return code. */ + else if (ret == MPG123_ERR || ret == MPG123_DONE) + break; + + /* Try to decode a bit. This is the return value that counts + * for the loop condition. */ +#ifdef AD_MPG123_FRAMEWISE + if (!buf) { /* fake call just for feeding to get format */ + ret = mpg123_getformat(con->handle, NULL, NULL, NULL); + } else { /* This is the decoding. One frame at a time. */ + ret = mpg123_replace_buffer(con->handle, buf, count); + if (ret == MPG123_OK) + ret = mpg123_decode_frame(con->handle, NULL, NULL, &got_now); + } +#else + ret = mpg123_decode(con->handle, NULL, 0, buf + got, count - got, + &got_now); +#endif + + got += got_now; + sh->pts_bytes += got_now; + +#ifdef AD_MPG123_FRAMEWISE + } while (ret == MPG123_NEED_MORE || (got == 0 && count != 0)); +#else + } while (ret == MPG123_NEED_MORE || got < count); +#endif + + if (ret == MPG123_ERR) { + mp_msg(MSGT_DECAUDIO, MSGL_ERR, "mpg123 decoding failed: %s\n", + mpg123_strerror(con->handle)); + mpg123_close(con->handle); + return -1; + } + + return got; +} + +/* Close, reopen stream. Feed data until we know the format of the stream. + * 1 on success, 0 on error */ +static int reopen_stream(sh_audio_t *sh) +{ + struct ad_mpg123_context *con = (struct ad_mpg123_context*) sh->context; + + mpg123_close(con->handle); + /* No resetting of the context: + * We do not want to loose the mean bitrate data. */ + + /* Open and make sure we have fed enough data to get stream properties. */ + if (MPG123_OK == mpg123_open_feed(con->handle) && + /* Feed data until mpg123 is ready (has found stream beginning). */ + !decode_a_bit(sh, NULL, 0)) { + return 1; + } else { + mp_msg(MSGT_DECAUDIO, MSGL_ERR, + "mpg123 failed to reopen stream: %s\n", + mpg123_strerror(con->handle)); + mpg123_close(con->handle); + return 0; + } +} + +/* Now we really start accessing some data and determining file format. + * Paranoia note: The mpg123_close() on errors is not really necessary, + * But it ensures that we don't accidentally continue decoding with a + * bad state (possibly interpreting the format badly or whatnot). */ +static int init(sh_audio_t *sh) +{ + long rate = 0; + int channels = 0; + int encoding = 0; + mpg123_id3v2 *v2; + struct mpg123_frameinfo finfo; + struct ad_mpg123_context *con = sh->context; + + /* We're open about any output format that libmpg123 will suggest. + * Note that a standard build will always default to 16 bit signed and + * the native sample rate of the file. */ + if (MPG123_OK == mpg123_format_all(con->handle) && + reopen_stream(sh) && + MPG123_OK == mpg123_getformat(con->handle, &rate, &channels, &encoding) && + /* Forbid the format to change later on. */ + MPG123_OK == mpg123_format_none(con->handle) && + MPG123_OK == mpg123_format(con->handle, rate, channels, encoding) && + /* Get MPEG header info. */ + MPG123_OK == mpg123_info(con->handle, &finfo) && + /* Since we queried format, mpg123 should have read past ID3v2 tags. + * We need to decide if printing of UTF-8 encoded text info is wanted. */ + MPG123_OK == mpg123_id3(con->handle, NULL, &v2)) { + /* If we are here, we passed all hurdles. Yay! Extract the info. */ + print_header_compact(&finfo); + /* Do we want to print out the UTF-8 Id3v2 info? + if (v2) + print_id3v2(v2); */ + + /* Have kb/s, want B/s + * For VBR, the first frame will be a bad estimate. */ + sh->i_bps = (finfo.bitrate ? finfo.bitrate : compute_bitrate(&finfo)) + * 1000 / 8; +#ifdef AD_MPG123_MEAN_BITRATE + con->delay = 1; + con->mean_rate = 0.; + con->mean_count = 0; +#endif + con->vbr = (finfo.vbr != MPG123_CBR); + sh->channels = channels; + sh->samplerate = rate; + /* Without external force, mpg123 will always choose signed encoding, + * and non-16-bit only on builds that don't support it. + * Be reminded that it doesn't matter to the MPEG file what encoding + * is produced from it. */ + switch (encoding) { + case MPG123_ENC_SIGNED_8: + sh->sample_format = AF_FORMAT_S8; + sh->samplesize = 1; + break; + case MPG123_ENC_SIGNED_16: + sh->sample_format = AF_FORMAT_S16_NE; + sh->samplesize = 2; + break; + /* To stay compatible with the oldest libmpg123 headers, do not rely + * on float and 32 bit encoding symbols being defined. + * Those formats came later */ + case 0x1180: /* MPG123_ENC_SIGNED_32 */ + sh->sample_format = AF_FORMAT_S32_NE; + sh->samplesize = 4; + break; + case 0x200: /* MPG123_ENC_FLOAT_32 */ + sh->sample_format = AF_FORMAT_FLOAT_NE; + sh->samplesize = 4; + break; + default: + mp_msg(MSGT_DECAUDIO, MSGL_ERR, + "Bad encoding from mpg123: %i.\n", encoding); + mpg123_close(con->handle); + return 0; + } +#ifdef AD_MPG123_FRAMEWISE + /* Going to decode directly to MPlayer's memory. It is important + * to have MPG123_AUTO_RESAMPLE disabled for the buffer size + * being an all-time limit. */ + sh->audio_out_minsize = 1152 * 2 * sh->samplesize; +#endif + + return 1; + } else { + mp_msg(MSGT_DECAUDIO, MSGL_ERR, "mpg123 init error: %s\n", + mpg123_strerror(con->handle)); + mpg123_close(con->handle); + return 0; + } +} + +static void uninit(sh_audio_t *sh) +{ + struct ad_mpg123_context *con = (struct ad_mpg123_context*) sh->context; + + mpg123_close(con->handle); + mpg123_delete(con->handle); + free(sh->context); + sh->context = NULL; + mpg123_exit(); +} + +#ifdef AD_MPG123_MEAN_BITRATE +/* Update mean bitrate. This could be dropped if accurate time display + * on audio file playback is not desired. */ +static void update_info(sh_audio_t *sh) +{ + struct ad_mpg123_context *con = sh->context; + if (con->vbr && --con->delay < 1) { + struct mpg123_frameinfo finfo; + if (MPG123_OK == mpg123_info(con->handle, &finfo)) { + if (++con->mean_count > ((unsigned int) -1) / 2) + con->mean_count = ((unsigned int) -1) / 4; + + /* Might not be numerically optimal, but works fine enough. */ + con->mean_rate = ((con->mean_count - 1) * con->mean_rate + + finfo.bitrate) / con->mean_count; + sh->i_bps = (int) (con->mean_rate * 1000 / 8); + + con->delay = 10; + } + } +} +#endif + +static int decode_audio(sh_audio_t *sh, unsigned char *buf, int minlen, + int maxlen) +{ + int bytes; + + bytes = decode_a_bit(sh, buf, maxlen); + if (bytes == 0) + return -1; /* EOF */ + +#ifdef AD_MPG123_MEAN_BITRATE + update_info(sh); +#endif + return bytes; +} + +static int control(sh_audio_t *sh, int cmd, void *arg, ...) +{ + switch (cmd) { + case ADCTRL_RESYNC_STREAM: + /* Close/reopen the stream for mpg123 to make sure it doesn't + * think that it still knows the exact stream position. + * Otherwise, we would have funny effects from the gapless code. + * Oh, and it helps to minimize artifacts from jumping in the stream. */ + if (reopen_stream(sh)) { +#ifdef AD_MPG123_MEAN_BITRATE + update_info(sh); +#endif + return CONTROL_TRUE; + } else { + /* MPlayer ignores this case! It just keeps on decoding. + * So we have to make sure resync never fails ... */ + mp_msg(MSGT_DECAUDIO, MSGL_ERR, + "mpg123 cannot reopen stream for resync.\n"); + return CONTROL_FALSE; + } + break; + } + return CONTROL_UNKNOWN; +} diff --git a/audio/decode/ad_pcm.c b/audio/decode/ad_pcm.c new file mode 100644 index 0000000000..c265dfcd56 --- /dev/null +++ b/audio/decode/ad_pcm.c @@ -0,0 +1,220 @@ +/* + * This file is part of MPlayer. + * + * MPlayer 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. + * + * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <stdbool.h> + +#include <libavutil/common.h> + +#include "talloc.h" +#include "config.h" +#include "ad_internal.h" +#include "libaf/format.h" +#include "libaf/reorder_ch.h" + +static const ad_info_t info = { + "Uncompressed PCM audio decoder", + "pcm", + "Nick Kurshev", + "A'rpi", + "" +}; + +struct ad_pcm_context { + unsigned char *buffer; + int buffer_pos; + int buffer_len; + int buffer_size; +}; + +LIBAD_EXTERN(pcm) + +static int init(sh_audio_t * sh_audio) +{ + WAVEFORMATEX *h = sh_audio->wf; + if (!h) + return 0; + sh_audio->i_bps = h->nAvgBytesPerSec; + sh_audio->channels = h->nChannels; + sh_audio->samplerate = h->nSamplesPerSec; + sh_audio->samplesize = (h->wBitsPerSample + 7) / 8; + sh_audio->sample_format = AF_FORMAT_S16_LE; // default + switch (sh_audio->format) { /* hardware formats: */ + case 0x0: + case 0x1: // Microsoft PCM + case 0xfffe: // Extended + switch (sh_audio->samplesize) { + case 1: sh_audio->sample_format = AF_FORMAT_U8; break; + case 2: sh_audio->sample_format = AF_FORMAT_S16_LE; break; + case 3: sh_audio->sample_format = AF_FORMAT_S24_LE; break; + case 4: sh_audio->sample_format = AF_FORMAT_S32_LE; break; + } + break; + case 0x3: // IEEE float + sh_audio->sample_format = AF_FORMAT_FLOAT_LE; + break; + case 0x6: sh_audio->sample_format = AF_FORMAT_A_LAW; break; + case 0x7: sh_audio->sample_format = AF_FORMAT_MU_LAW; break; + case 0x11: sh_audio->sample_format = AF_FORMAT_IMA_ADPCM; break; + case 0x50: sh_audio->sample_format = AF_FORMAT_MPEG2; break; +/* case 0x2000: sh_audio->sample_format=AFMT_AC3; */ + case 0x20776172: // 'raw ' + sh_audio->sample_format = AF_FORMAT_S16_BE; + if (sh_audio->samplesize == 1) + sh_audio->sample_format = AF_FORMAT_U8; + break; + case 0x736F7774: // 'twos' + sh_audio->sample_format = AF_FORMAT_S16_BE; + // intended fall-through + case 0x74776F73: // 'sowt' + if (sh_audio->samplesize == 1) + sh_audio->sample_format = AF_FORMAT_S8; + break; + case 0x32336c66: // 'fl32', bigendian float32 + case 0x32334C46: // 'FL32', bigendian float32 in aiff + sh_audio->sample_format = AF_FORMAT_FLOAT_BE; + sh_audio->samplesize = 4; + break; + case 0x666c3332: // '23lf', little endian float32, MPlayer internal fourCC + case 0x6D63706C: // 'lpcm' + sh_audio->sample_format = AF_FORMAT_FLOAT_LE; + sh_audio->samplesize = 4; + break; +/* case 0x34366c66: // 'fl64', bigendian float64 + sh_audio->sample_format=AF_FORMAT_FLOAT_BE; + sh_audio->samplesize=8; + break; + case 0x666c3634: // '46lf', little endian float64, MPlayer internal fourCC + sh_audio->sample_format=AF_FORMAT_FLOAT_LE; + sh_audio->samplesize=8; + break;*/ + case 0x34326e69: // 'in24', bigendian int24 + sh_audio->sample_format = AF_FORMAT_S24_BE; + sh_audio->samplesize = 3; + break; + case 0x696e3234: // '42ni', little endian int24, MPlayer internal fourCC + sh_audio->sample_format = AF_FORMAT_S24_LE; + sh_audio->samplesize = 3; + break; + case 0x32336e69: // 'in32', bigendian int32 + sh_audio->sample_format = AF_FORMAT_S32_BE; + sh_audio->samplesize = 4; + break; + case 0x696e3332: // '23ni', little endian int32, MPlayer internal fourCC + sh_audio->sample_format = AF_FORMAT_S32_LE; + sh_audio->samplesize = 4; + break; + case MKTAG('M', 'P', 'a', 'f'): + sh_audio->sample_format = h->wFormatTag; + sh_audio->samplesize = (af_fmt2bits(sh_audio->sample_format) + 7) / 8; + break; + default: + if (sh_audio->samplesize != 2) + sh_audio->sample_format = AF_FORMAT_U8; + } + if (!sh_audio->samplesize) // this would cause MPlayer to hang later + sh_audio->samplesize = 2; + sh_audio->context = talloc_zero(NULL, struct ad_pcm_context); + return 1; +} + +static int preinit(sh_audio_t *sh) +{ + sh->audio_out_minsize = 2048; + return 1; +} + +static void uninit(sh_audio_t *sh) +{ + talloc_free(sh->context); +} + +static int control(sh_audio_t *sh, int cmd, void *arg, ...) +{ + struct ad_pcm_context *ctx = sh->context; + int skip; + switch (cmd) { + case ADCTRL_RESYNC_STREAM: + ctx->buffer_len = 0; + return true; + case ADCTRL_SKIP_FRAME: + skip = sh->i_bps / 16; + skip = skip & (~3); + demux_read_data(sh->ds, NULL, skip); + return CONTROL_TRUE; + } + return CONTROL_UNKNOWN; +} + +static int decode_audio(sh_audio_t *sh_audio, unsigned char *buf, int minlen, + int maxlen) +{ + int unitsize = sh_audio->channels * sh_audio->samplesize; + minlen = (minlen + unitsize - 1) / unitsize * unitsize; + if (minlen > maxlen) + // if someone needs hundreds of channels adjust audio_out_minsize + // based on channels in preinit() + return -1; + + int len = 0; + struct ad_pcm_context *ctx = sh_audio->context; + while (len < minlen) { + if (ctx->buffer_len - ctx->buffer_pos <= 0) { + double pts; + unsigned char *ptr; + int plen = ds_get_packet_pts(sh_audio->ds, &ptr, &pts); + if (plen < 0) + break; + if (ctx->buffer_size < plen) { + talloc_free(ctx->buffer); + ctx->buffer = talloc_size(ctx, plen); + ctx->buffer_size = plen; + } + memcpy(ctx->buffer, ptr, plen); + ctx->buffer_len = plen; + ctx->buffer_pos = 0; + if (pts != MP_NOPTS_VALUE) { + sh_audio->pts = pts; + sh_audio->pts_bytes = 0; + } + } + int from_stored = ctx->buffer_len - ctx->buffer_pos; + if (from_stored > minlen - len) + from_stored = minlen - len; + memcpy(buf + len, ctx->buffer + ctx->buffer_pos, from_stored); + ctx->buffer_pos += from_stored; + sh_audio->pts_bytes += from_stored; + len += from_stored; + } + if (len % unitsize) { + mp_msg(MSGT_DECAUDIO, MSGL_WARN, "[ad_pcm] discarding partial sample " + "at end\n"); + len -= len % unitsize; + } + if (len == 0) + len = -1; // The loop above only exits at error/EOF + if (len > 0 && sh_audio->channels >= 5) { + reorder_channel_nch(buf, AF_CHANNEL_LAYOUT_WAVEEX_DEFAULT, + AF_CHANNEL_LAYOUT_MPLAYER_DEFAULT, + sh_audio->channels, len / sh_audio->samplesize, + sh_audio->samplesize); + } + return len; +} diff --git a/audio/decode/ad_spdif.c b/audio/decode/ad_spdif.c new file mode 100644 index 0000000000..877bc99317 --- /dev/null +++ b/audio/decode/ad_spdif.c @@ -0,0 +1,310 @@ +/* + * This file is part of MPlayer. + * + * MPlayer 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. + * + * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <string.h> + +#include <libavformat/avformat.h> +#include <libavcodec/avcodec.h> +#include <libavutil/opt.h> + +#include "config.h" +#include "mp_msg.h" +#include "ad_internal.h" + +static const ad_info_t info = { + "libavformat/spdifenc audio pass-through decoder.", + "spdif", + "Naoya OYAMA", + "Naoya OYAMA", + "For ALL hardware decoders" +}; + +LIBAD_EXTERN(spdif) + +#define FILENAME_SPDIFENC "spdif" +#define OUTBUF_SIZE 65536 +struct spdifContext { + AVFormatContext *lavf_ctx; + int iec61937_packet_size; + int out_buffer_len; + int out_buffer_size; + uint8_t *out_buffer; + uint8_t pb_buffer[OUTBUF_SIZE]; +}; + +static int read_packet(void *p, uint8_t *buf, int buf_size) +{ + // spdifenc does not use read callback. + return 0; +} + +static int write_packet(void *p, uint8_t *buf, int buf_size) +{ + int len; + struct spdifContext *ctx = p; + + len = FFMIN(buf_size, ctx->out_buffer_size -ctx->out_buffer_len); + memcpy(&ctx->out_buffer[ctx->out_buffer_len], buf, len); + ctx->out_buffer_len += len; + return len; +} + +static int64_t seek(void *p, int64_t offset, int whence) +{ + // spdifenc does not use seek callback. + return 0; +} + +static int preinit(sh_audio_t *sh) +{ + sh->samplesize = 2; + return 1; +} + +static int init(sh_audio_t *sh) +{ + int i, x, in_size, srate, bps, *dtshd_rate; + unsigned char *start; + double pts; + static const struct { + const char *name; enum CodecID id; + } fmt_id_type[] = { + { "aac" , CODEC_ID_AAC }, + { "ac3" , CODEC_ID_AC3 }, + { "dca" , CODEC_ID_DTS }, + { "eac3", CODEC_ID_EAC3 }, + { "mpa" , CODEC_ID_MP3 }, + { "thd" , CODEC_ID_TRUEHD }, + { NULL , 0 } + }; + AVFormatContext *lavf_ctx = NULL; + AVStream *stream = NULL; + const AVOption *opt = NULL; + struct spdifContext *spdif_ctx = NULL; + + spdif_ctx = av_mallocz(sizeof(*spdif_ctx)); + if (!spdif_ctx) + goto fail; + spdif_ctx->lavf_ctx = avformat_alloc_context(); + if (!spdif_ctx->lavf_ctx) + goto fail; + + sh->context = spdif_ctx; + lavf_ctx = spdif_ctx->lavf_ctx; + + lavf_ctx->oformat = av_guess_format(FILENAME_SPDIFENC, NULL, NULL); + if (!lavf_ctx->oformat) + goto fail; + lavf_ctx->priv_data = av_mallocz(lavf_ctx->oformat->priv_data_size); + if (!lavf_ctx->priv_data) + goto fail; + lavf_ctx->pb = avio_alloc_context(spdif_ctx->pb_buffer, OUTBUF_SIZE, 1, spdif_ctx, + read_packet, write_packet, seek); + if (!lavf_ctx->pb) + goto fail; + stream = avformat_new_stream(lavf_ctx, 0); + if (!stream) + goto fail; + lavf_ctx->duration = AV_NOPTS_VALUE; + lavf_ctx->start_time = AV_NOPTS_VALUE; + for (i = 0; fmt_id_type[i].name; i++) { + if (!strcmp(sh->codec->dll, fmt_id_type[i].name)) { + lavf_ctx->streams[0]->codec->codec_id = fmt_id_type[i].id; + break; + } + } + lavf_ctx->raw_packet_buffer_remaining_size = RAW_PACKET_BUFFER_SIZE; + if (AVERROR_PATCHWELCOME == lavf_ctx->oformat->write_header(lavf_ctx)) { + mp_msg(MSGT_DECAUDIO,MSGL_INFO, + "This codec is not supported by spdifenc.\n"); + goto fail; + } + + // get sample_rate & bitrate from parser + bps = srate = 0; + x = ds_get_packet_pts(sh->ds, &start, &pts); + in_size = x; + if (x <= 0) { + pts = MP_NOPTS_VALUE; + x = 0; + } + ds_parse(sh->ds, &start, &x, pts, 0); + if (x == 0) { // not enough buffer + srate = 48000; //fake value + bps = 768000/8; //fake value + } else if (sh->avctx) { + if (sh->avctx->sample_rate < 44100) { + mp_msg(MSGT_DECAUDIO,MSGL_INFO, + "This stream sample_rate[%d Hz] may be broken. " + "Force reset 48000Hz.\n", + sh->avctx->sample_rate); + srate = 48000; //fake value + } else + srate = sh->avctx->sample_rate; + bps = sh->avctx->bit_rate/8; + } + sh->ds->buffer_pos -= in_size; + + switch (lavf_ctx->streams[0]->codec->codec_id) { + case CODEC_ID_AAC: + spdif_ctx->iec61937_packet_size = 16384; + sh->sample_format = AF_FORMAT_IEC61937_LE; + sh->samplerate = srate; + sh->channels = 2; + sh->i_bps = bps; + break; + case CODEC_ID_AC3: + spdif_ctx->iec61937_packet_size = 6144; + sh->sample_format = AF_FORMAT_IEC61937_LE; + sh->samplerate = srate; + sh->channels = 2; + sh->i_bps = bps; + break; + case CODEC_ID_DTS: // FORCE USE DTS-HD + opt = av_opt_find(&lavf_ctx->oformat->priv_class, + "dtshd_rate", NULL, 0, 0); + if (!opt) + goto fail; + dtshd_rate = (int*)(((uint8_t*)lavf_ctx->priv_data) + + opt->offset); + *dtshd_rate = 192000*4; + spdif_ctx->iec61937_packet_size = 32768; + sh->sample_format = AF_FORMAT_IEC61937_LE; + sh->samplerate = 192000; // DTS core require 48000 + sh->channels = 2*4; + sh->i_bps = bps; + break; + case CODEC_ID_EAC3: + spdif_ctx->iec61937_packet_size = 24576; + sh->sample_format = AF_FORMAT_IEC61937_LE; + sh->samplerate = 192000; + sh->channels = 2; + sh->i_bps = bps; + break; + case CODEC_ID_MP3: + spdif_ctx->iec61937_packet_size = 4608; + sh->sample_format = AF_FORMAT_MPEG2; + sh->samplerate = srate; + sh->channels = 2; + sh->i_bps = bps; + break; + case CODEC_ID_TRUEHD: + spdif_ctx->iec61937_packet_size = 61440; + sh->sample_format = AF_FORMAT_IEC61937_LE; + sh->samplerate = 192000; + sh->channels = 8; + sh->i_bps = bps; + break; + default: + break; + } + + return 1; + +fail: + uninit(sh); + return 0; +} + +static int decode_audio(sh_audio_t *sh, unsigned char *buf, + int minlen, int maxlen) +{ + struct spdifContext *spdif_ctx = sh->context; + AVFormatContext *lavf_ctx = spdif_ctx->lavf_ctx; + AVPacket pkt; + double pts; + int ret, in_size, consumed, x; + unsigned char *start = NULL; + + consumed = spdif_ctx->out_buffer_len = 0; + spdif_ctx->out_buffer_size = maxlen; + spdif_ctx->out_buffer = buf; + while (spdif_ctx->out_buffer_len + spdif_ctx->iec61937_packet_size < maxlen + && spdif_ctx->out_buffer_len < minlen) { + if (sh->ds->eof) + break; + x = ds_get_packet_pts(sh->ds, &start, &pts); + if (x <= 0) { + x = 0; + ds_parse(sh->ds, &start, &x, MP_NOPTS_VALUE, 0); + if (x == 0) + continue; // END_NOT_FOUND + in_size = x; + } else { + in_size = x; + consumed = ds_parse(sh->ds, &start, &x, pts, 0); + if (x == 0) { + mp_msg(MSGT_DECAUDIO,MSGL_V, + "start[%p] in_size[%d] consumed[%d] x[%d].\n", + start, in_size, consumed, x); + continue; // END_NOT_FOUND + } + sh->ds->buffer_pos -= in_size - consumed; + } + av_init_packet(&pkt); + pkt.data = start; + pkt.size = x; + mp_msg(MSGT_DECAUDIO,MSGL_V, + "start[%p] pkt.size[%d] in_size[%d] consumed[%d] x[%d].\n", + start, pkt.size, in_size, consumed, x); + if (pts != MP_NOPTS_VALUE) { + sh->pts = pts; + sh->pts_bytes = 0; + } + ret = lavf_ctx->oformat->write_packet(lavf_ctx, &pkt); + if (ret < 0) + break; + } + sh->pts_bytes += spdif_ctx->out_buffer_len; + return spdif_ctx->out_buffer_len; +} + +static int control(sh_audio_t *sh, int cmd, void* arg, ...) +{ + unsigned char *start; + double pts; + + switch (cmd) { + case ADCTRL_RESYNC_STREAM: + case ADCTRL_SKIP_FRAME: + ds_get_packet_pts(sh->ds, &start, &pts); + return CONTROL_TRUE; + } + return CONTROL_UNKNOWN; +} + +static void uninit(sh_audio_t *sh) +{ + struct spdifContext *spdif_ctx = sh->context; + AVFormatContext *lavf_ctx = spdif_ctx->lavf_ctx; + + if (lavf_ctx) { + if (lavf_ctx->oformat) + lavf_ctx->oformat->write_trailer(lavf_ctx); + av_freep(&lavf_ctx->pb); + if (lavf_ctx->streams) { + av_freep(&lavf_ctx->streams[0]->codec); + av_freep(&lavf_ctx->streams[0]->info); + av_freep(&lavf_ctx->streams[0]); + } + av_freep(&lavf_ctx->streams); + av_freep(&lavf_ctx->priv_data); + } + av_freep(&lavf_ctx); + av_freep(&spdif_ctx); +} diff --git a/audio/decode/dec_audio.c b/audio/decode/dec_audio.c new file mode 100644 index 0000000000..2602352e52 --- /dev/null +++ b/audio/decode/dec_audio.c @@ -0,0 +1,462 @@ +/* + * This file is part of MPlayer. + * + * MPlayer 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. + * + * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <assert.h> + +#include "config.h" +#include "mp_msg.h" +#include "bstr.h" + +#include "stream/stream.h" +#include "libmpdemux/demuxer.h" + +#include "codec-cfg.h" +#include "libmpdemux/stheader.h" + +#include "dec_audio.h" +#include "ad.h" +#include "libaf/format.h" + +#include "libaf/af.h" + +int fakemono = 0; + +struct af_cfg af_cfg = { 1, NULL }; // Configuration for audio filters + +void afm_help(void) +{ + int i; + mp_tmsg(MSGT_DECAUDIO, MSGL_INFO, "Available (compiled-in) audio codec families/drivers:\n"); + mp_msg(MSGT_IDENTIFY, MSGL_INFO, "ID_AUDIO_DRIVERS\n"); + mp_msg(MSGT_DECAUDIO, MSGL_INFO, " afm: info: (comment)\n"); + for (i = 0; mpcodecs_ad_drivers[i] != NULL; i++) + if (mpcodecs_ad_drivers[i]->info->comment + && mpcodecs_ad_drivers[i]->info->comment[0]) + mp_msg(MSGT_DECAUDIO, MSGL_INFO, "%9s %s (%s)\n", + mpcodecs_ad_drivers[i]->info->short_name, + mpcodecs_ad_drivers[i]->info->name, + mpcodecs_ad_drivers[i]->info->comment); + else + mp_msg(MSGT_DECAUDIO, MSGL_INFO, "%9s %s\n", + mpcodecs_ad_drivers[i]->info->short_name, + mpcodecs_ad_drivers[i]->info->name); +} + +static int init_audio_codec(sh_audio_t *sh_audio) +{ + assert(!sh_audio->initialized); + resync_audio_stream(sh_audio); + if ((af_cfg.force & AF_INIT_FORMAT_MASK) == AF_INIT_FLOAT) { + int fmt = AF_FORMAT_FLOAT_NE; + if (sh_audio->ad_driver->control(sh_audio, ADCTRL_QUERY_FORMAT, + &fmt) == CONTROL_TRUE) { + sh_audio->sample_format = fmt; + sh_audio->samplesize = 4; + } + } + sh_audio->audio_out_minsize = 8192; // default, preinit() may change it + if (!sh_audio->ad_driver->preinit(sh_audio)) { + mp_tmsg(MSGT_DECAUDIO, MSGL_ERR, "ADecoder preinit failed :(\n"); + return 0; + } + + /* allocate audio in buffer: */ + if (sh_audio->audio_in_minsize > 0) { + sh_audio->a_in_buffer_size = sh_audio->audio_in_minsize; + mp_tmsg(MSGT_DECAUDIO, MSGL_V, "dec_audio: Allocating %d bytes for input buffer.\n", + sh_audio->a_in_buffer_size); + sh_audio->a_in_buffer = av_mallocz(sh_audio->a_in_buffer_size); + } + + const int base_size = 65536; + // At least 64 KiB plus rounding up to next decodable unit size + sh_audio->a_buffer_size = base_size + sh_audio->audio_out_minsize; + + mp_tmsg(MSGT_DECAUDIO, MSGL_V, "dec_audio: Allocating %d + %d = %d bytes for output buffer.\n", + sh_audio->audio_out_minsize, base_size, sh_audio->a_buffer_size); + + sh_audio->a_buffer = av_mallocz(sh_audio->a_buffer_size); + if (!sh_audio->a_buffer) + abort(); + sh_audio->a_buffer_len = 0; + + if (!sh_audio->ad_driver->init(sh_audio)) { + mp_tmsg(MSGT_DECAUDIO, MSGL_V, "ADecoder init failed :(\n"); + uninit_audio(sh_audio); // free buffers + return 0; + } + + sh_audio->initialized = 1; + + if (!sh_audio->channels || !sh_audio->samplerate) { + mp_tmsg(MSGT_DECAUDIO, MSGL_ERR, "Audio decoder did not specify " + "audio format!\n"); + uninit_audio(sh_audio); // free buffers + return 0; + } + + if (!sh_audio->o_bps) + sh_audio->o_bps = sh_audio->channels * sh_audio->samplerate + * sh_audio->samplesize; + return 1; +} + +static int init_audio(sh_audio_t *sh_audio, char *codecname, char *afm, + int status, stringset_t *selected) +{ + int force = 0; + if (codecname && codecname[0] == '+') { + codecname = &codecname[1]; + force = 1; + } + sh_audio->codec = NULL; + while (1) { + const ad_functions_t *mpadec; + sh_audio->ad_driver = 0; + if (!(sh_audio->codec = find_audio_codec(sh_audio->format, + NULL, + sh_audio->codec, force))) + break; + // ok we found one codec + if (stringset_test(selected, sh_audio->codec->name)) + continue; // already tried & failed + if (codecname && strcmp(sh_audio->codec->name, codecname)) + continue; // -ac + if (afm && strcmp(sh_audio->codec->drv, afm)) + continue; // afm doesn't match + if (!force && sh_audio->codec->status < status) + continue; // too unstable + stringset_add(selected, sh_audio->codec->name); // tagging it + // ok, it matches all rules, let's find the driver! + int i; + for (i = 0; mpcodecs_ad_drivers[i] != NULL; i++) + if (!strcmp(mpcodecs_ad_drivers[i]->info->short_name, + sh_audio->codec->drv)) + break; + mpadec = mpcodecs_ad_drivers[i]; + if (!mpadec) { // driver not available (==compiled in) + mp_tmsg(MSGT_DECAUDIO, MSGL_ERR, + "Requested audio codec family [%s] (afm=%s) not available.\nEnable it at compilation.\n", + sh_audio->codec->name, sh_audio->codec->drv); + continue; + } + // it's available, let's try to init! + // init() + mp_tmsg(MSGT_DECAUDIO, MSGL_V, "Opening audio decoder: [%s] %s\n", + mpadec->info->short_name, mpadec->info->name); + sh_audio->ad_driver = mpadec; + if (!init_audio_codec(sh_audio)) { + mp_tmsg(MSGT_DECAUDIO, MSGL_WARN, "Audio decoder init failed for " + "codecs.conf entry \"%s\".\n", sh_audio->codec->name); + continue; // try next... + } + // Yeah! We got it! + return 1; + } + return 0; +} + +int init_best_audio_codec(sh_audio_t *sh_audio, char **audio_codec_list, + char **audio_fm_list) +{ + stringset_t selected; + char *ac_l_default[2] = { "", (char *) NULL }; + // hack: + if (!audio_codec_list) + audio_codec_list = ac_l_default; + // Go through the codec.conf and find the best codec... + sh_audio->initialized = 0; + stringset_init(&selected); + while (!sh_audio->initialized && *audio_codec_list) { + char *audio_codec = *(audio_codec_list++); + if (audio_codec[0]) { + if (audio_codec[0] == '-') { + // disable this codec: + stringset_add(&selected, audio_codec + 1); + } else { + // forced codec by name: + mp_tmsg(MSGT_DECAUDIO, MSGL_INFO, "Forced audio codec: %s\n", + audio_codec); + init_audio(sh_audio, audio_codec, NULL, -1, &selected); + } + } else { + int status; + // try in stability order: UNTESTED, WORKING, BUGGY. + // never try CRASHING. + if (audio_fm_list) { + char **fmlist = audio_fm_list; + // try first the preferred codec families: + while (!sh_audio->initialized && *fmlist) { + char *audio_fm = *(fmlist++); + mp_tmsg(MSGT_DECAUDIO, MSGL_INFO, "Trying to force audio codec driver family %s...\n", + audio_fm); + for (status = CODECS_STATUS__MAX; + status >= CODECS_STATUS__MIN; --status) + if (init_audio(sh_audio, NULL, audio_fm, status, &selected)) + break; + } + } + if (!sh_audio->initialized) + for (status = CODECS_STATUS__MAX; status >= CODECS_STATUS__MIN; + --status) + if (init_audio(sh_audio, NULL, NULL, status, &selected)) + break; + } + } + stringset_free(&selected); + + if (!sh_audio->initialized) { + mp_tmsg(MSGT_DECAUDIO, MSGL_ERR, "Cannot find codec for audio format 0x%X.\n", + sh_audio->format); + return 0; // failed + } + + mp_tmsg(MSGT_DECAUDIO, MSGL_INFO, "Selected audio codec: %s [%s]\n", + sh_audio->codecname ? sh_audio->codecname : sh_audio->codec->info, + sh_audio->ad_driver->info->print_name ? + sh_audio->ad_driver->info->print_name : + sh_audio->ad_driver->info->short_name); + mp_tmsg(MSGT_DECAUDIO, MSGL_V, + "Audio codecs.conf entry: %s (%s) afm: %s\n", + sh_audio->codec->name, sh_audio->codec->info, sh_audio->codec->drv); + mp_msg(MSGT_DECAUDIO, MSGL_V, + "AUDIO: %d Hz, %d ch, %s, %3.1f kbit/%3.2f%% (ratio: %d->%d)\n", + sh_audio->samplerate, sh_audio->channels, + af_fmt2str_short(sh_audio->sample_format), + sh_audio->i_bps * 8 * 0.001, + ((float) sh_audio->i_bps / sh_audio->o_bps) * 100.0, + sh_audio->i_bps, sh_audio->o_bps); + mp_msg(MSGT_IDENTIFY, MSGL_INFO, + "ID_AUDIO_BITRATE=%d\nID_AUDIO_RATE=%d\n" "ID_AUDIO_NCH=%d\n", + sh_audio->i_bps * 8, sh_audio->samplerate, sh_audio->channels); + + return 1; // success +} + +void uninit_audio(sh_audio_t *sh_audio) +{ + if (sh_audio->afilter) { + mp_msg(MSGT_DECAUDIO, MSGL_V, "Uninit audio filters...\n"); + af_uninit(sh_audio->afilter); + free(sh_audio->afilter); + sh_audio->afilter = NULL; + } + if (sh_audio->initialized) { + mp_tmsg(MSGT_DECAUDIO, MSGL_V, "Uninit audio: %s\n", + sh_audio->codec->drv); + sh_audio->ad_driver->uninit(sh_audio); + sh_audio->initialized = 0; + } + av_freep(&sh_audio->a_buffer); + av_freep(&sh_audio->a_in_buffer); +} + + +int init_audio_filters(sh_audio_t *sh_audio, int in_samplerate, + int *out_samplerate, int *out_channels, int *out_format) +{ + struct af_stream *afs = sh_audio->afilter; + if (!afs) { + afs = calloc(1, sizeof(struct af_stream)); + afs->opts = sh_audio->opts; + } + // input format: same as codec's output format: + afs->input.rate = in_samplerate; + afs->input.nch = sh_audio->channels; + afs->input.format = sh_audio->sample_format; + af_fix_parameters(&(afs->input)); + + // output format: same as ao driver's input format (if missing, fallback to input) + afs->output.rate = *out_samplerate; + afs->output.nch = *out_channels; + afs->output.format = *out_format; + af_fix_parameters(&(afs->output)); + + // filter config: + memcpy(&afs->cfg, &af_cfg, sizeof(struct af_cfg)); + + mp_tmsg(MSGT_DECAUDIO, MSGL_V, "Building audio filter chain for %dHz/%dch/%s -> %dHz/%dch/%s...\n", + afs->input.rate, afs->input.nch, + af_fmt2str_short(afs->input.format), afs->output.rate, + afs->output.nch, af_fmt2str_short(afs->output.format)); + + // let's autoprobe it! + if (0 != af_init(afs)) { + sh_audio->afilter = NULL; + free(afs); + return 0; // failed :( + } + + *out_samplerate = afs->output.rate; + *out_channels = afs->output.nch; + *out_format = afs->output.format; + + // ok! + sh_audio->afilter = (void *) afs; + return 1; +} + +static void set_min_out_buffer_size(struct bstr *outbuf, int len) +{ + size_t oldlen = talloc_get_size(outbuf->start); + if (oldlen < len) { + assert(outbuf->start); // talloc context should be already set + mp_msg(MSGT_DECAUDIO, MSGL_V, "Increasing filtered audio buffer size " + "from %zd to %d\n", oldlen, len); + outbuf->start = talloc_realloc_size(NULL, outbuf->start, len); + } +} + +static int filter_n_bytes(sh_audio_t *sh, struct bstr *outbuf, int len) +{ + assert(len-1 + sh->audio_out_minsize <= sh->a_buffer_size); + + int error = 0; + + // Decode more bytes if needed + int old_samplerate = sh->samplerate; + int old_channels = sh->channels; + int old_sample_format = sh->sample_format; + while (sh->a_buffer_len < len) { + unsigned char *buf = sh->a_buffer + sh->a_buffer_len; + int minlen = len - sh->a_buffer_len; + int maxlen = sh->a_buffer_size - sh->a_buffer_len; + int ret = sh->ad_driver->decode_audio(sh, buf, minlen, maxlen); + int format_change = sh->samplerate != old_samplerate + || sh->channels != old_channels + || sh->sample_format != old_sample_format; + if (ret <= 0 || format_change) { + error = format_change ? -2 : -1; + // samples from format-changing call get discarded too + len = sh->a_buffer_len; + break; + } + sh->a_buffer_len += ret; + } + + // Filter + struct mp_audio filter_input = { + .audio = sh->a_buffer, + .len = len, + .rate = sh->samplerate, + .nch = sh->channels, + .format = sh->sample_format + }; + af_fix_parameters(&filter_input); + struct mp_audio *filter_output = af_play(sh->afilter, &filter_input); + if (!filter_output) + return -1; + set_min_out_buffer_size(outbuf, outbuf->len + filter_output->len); + memcpy(outbuf->start + outbuf->len, filter_output->audio, + filter_output->len); + outbuf->len += filter_output->len; + + // remove processed data from decoder buffer: + sh->a_buffer_len -= len; + memmove(sh->a_buffer, sh->a_buffer + len, sh->a_buffer_len); + + return error; +} + +/* Try to get at least minlen decoded+filtered bytes in outbuf + * (total length including possible existing data). + * Return 0 on success, -1 on error/EOF (not distinguished). + * In the former case outbuf->len is always >= minlen on return. + * In case of EOF/error it might or might not be. + * Outbuf.start must be talloc-allocated, and will be reallocated + * if needed to fit all filter output. */ +int decode_audio(sh_audio_t *sh_audio, struct bstr *outbuf, int minlen) +{ + // Indicates that a filter seems to be buffering large amounts of data + int huge_filter_buffer = 0; + // Decoded audio must be cut at boundaries of this many bytes + int unitsize = sh_audio->channels * sh_audio->samplesize * 16; + + /* Filter output size will be about filter_multiplier times input size. + * If some filter buffers audio in big blocks this might only hold + * as average over time. */ + double filter_multiplier = af_calc_filter_multiplier(sh_audio->afilter); + + /* If the decoder set audio_out_minsize then it can do the equivalent of + * "while (output_len < target_len) output_len += audio_out_minsize;", + * so we must guarantee there is at least audio_out_minsize-1 bytes + * more space in the output buffer than the minimum length we try to + * decode. */ + int max_decode_len = sh_audio->a_buffer_size - sh_audio->audio_out_minsize; + if (!unitsize) + return -1; + max_decode_len -= max_decode_len % unitsize; + + while (outbuf->len < minlen) { + int declen = (minlen - outbuf->len) / filter_multiplier + + (unitsize << 5); // some extra for possible filter buffering + if (huge_filter_buffer) + /* Some filter must be doing significant buffering if the estimated + * input length didn't produce enough output from filters. + * Feed the filters 2k bytes at a time until we have enough output. + * Very small amounts could make filtering inefficient while large + * amounts can make MPlayer demux the file unnecessarily far ahead + * to get audio data and buffer video frames in memory while doing + * so. However the performance impact of either is probably not too + * significant as long as the value is not completely insane. */ + declen = 2000; + declen -= declen % unitsize; + if (declen > max_decode_len) + declen = max_decode_len; + else + /* if this iteration does not fill buffer, we must have lots + * of buffering in filters */ + huge_filter_buffer = 1; + int res = filter_n_bytes(sh_audio, outbuf, declen); + if (res < 0) + return res; + } + return 0; +} + +void decode_audio_prepend_bytes(struct bstr *outbuf, int count, int byte) +{ + set_min_out_buffer_size(outbuf, outbuf->len + count); + memmove(outbuf->start + count, outbuf->start, outbuf->len); + memset(outbuf->start, byte, count); + outbuf->len += count; +} + + +void resync_audio_stream(sh_audio_t *sh_audio) +{ + sh_audio->a_in_buffer_len = 0; // clear audio input buffer + sh_audio->pts = MP_NOPTS_VALUE; + if (!sh_audio->initialized) + return; + sh_audio->ad_driver->control(sh_audio, ADCTRL_RESYNC_STREAM, NULL); +} + +void skip_audio_frame(sh_audio_t *sh_audio) +{ + if (!sh_audio->initialized) + return; + if (sh_audio->ad_driver->control(sh_audio, ADCTRL_SKIP_FRAME, NULL) == + CONTROL_TRUE) + return; + // default skip code: + ds_fill_buffer(sh_audio->ds); // skip block +} diff --git a/audio/decode/dec_audio.h b/audio/decode/dec_audio.h new file mode 100644 index 0000000000..0d4baf0666 --- /dev/null +++ b/audio/decode/dec_audio.h @@ -0,0 +1,38 @@ +/* + * This file is part of MPlayer. + * + * MPlayer 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. + * + * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPLAYER_DEC_AUDIO_H +#define MPLAYER_DEC_AUDIO_H + +#include "libmpdemux/stheader.h" + +struct bstr; + +// dec_audio.c: +void afm_help(void); +int init_best_audio_codec(sh_audio_t *sh_audio, char** audio_codec_list, char** audio_fm_list); +int decode_audio(sh_audio_t *sh_audio, struct bstr *outbuf, int minlen); +void decode_audio_prepend_bytes(struct bstr *outbuf, int count, int byte); +void resync_audio_stream(sh_audio_t *sh_audio); +void skip_audio_frame(sh_audio_t *sh_audio); +void uninit_audio(sh_audio_t *sh_audio); + +int init_audio_filters(sh_audio_t *sh_audio, int in_samplerate, + int *out_samplerate, int *out_channels, int *out_format); + +#endif /* MPLAYER_DEC_AUDIO_H */ diff --git a/audio/filter/af.c b/audio/filter/af.c new file mode 100644 index 0000000000..1f3e446821 --- /dev/null +++ b/audio/filter/af.c @@ -0,0 +1,700 @@ +/* + * This file is part of MPlayer. + * + * MPlayer 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. + * + * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include "osdep/strsep.h" + +#include "af.h" + +// Static list of filters +extern struct af_info af_info_dummy; +extern struct af_info af_info_delay; +extern struct af_info af_info_channels; +extern struct af_info af_info_format; +extern struct af_info af_info_resample; +extern struct af_info af_info_volume; +extern struct af_info af_info_equalizer; +extern struct af_info af_info_pan; +extern struct af_info af_info_surround; +extern struct af_info af_info_sub; +extern struct af_info af_info_export; +extern struct af_info af_info_volnorm; +extern struct af_info af_info_extrastereo; +extern struct af_info af_info_lavcac3enc; +extern struct af_info af_info_lavcresample; +extern struct af_info af_info_sweep; +extern struct af_info af_info_hrtf; +extern struct af_info af_info_ladspa; +extern struct af_info af_info_center; +extern struct af_info af_info_sinesuppress; +extern struct af_info af_info_karaoke; +extern struct af_info af_info_scaletempo; +extern struct af_info af_info_bs2b; + +static struct af_info* filter_list[]={ + &af_info_dummy, + &af_info_delay, + &af_info_channels, + &af_info_format, + &af_info_resample, + &af_info_volume, + &af_info_equalizer, + &af_info_pan, + &af_info_surround, + &af_info_sub, +#ifdef HAVE_SYS_MMAN_H + &af_info_export, +#endif + &af_info_volnorm, + &af_info_extrastereo, + &af_info_lavcac3enc, + &af_info_lavcresample, + &af_info_sweep, + &af_info_hrtf, +#ifdef CONFIG_LADSPA + &af_info_ladspa, +#endif + &af_info_center, + &af_info_sinesuppress, + &af_info_karaoke, + &af_info_scaletempo, +#ifdef CONFIG_LIBBS2B + &af_info_bs2b, +#endif + NULL +}; + +// CPU speed +int* af_cpu_speed = NULL; + +/* Find a filter in the static list of filters using it's name. This + function is used internally */ +static struct af_info* af_find(char*name) +{ + int i=0; + while(filter_list[i]){ + if(!strcmp(filter_list[i]->name,name)) + return filter_list[i]; + i++; + } + mp_msg(MSGT_AFILTER, MSGL_ERR, "Couldn't find audio filter '%s'\n",name); + return NULL; +} + +/* Find filter in the dynamic filter list using it's name This + function is used for finding already initialized filters */ +struct af_instance* af_get(struct af_stream* s, char* name) +{ + struct af_instance* af=s->first; + // Find the filter + while(af != NULL){ + if(!strcmp(af->info->name,name)) + return af; + af=af->next; + } + return NULL; +} + +/*/ Function for creating a new filter of type name. The name may + contain the commandline parameters for the filter */ +static struct af_instance* af_create(struct af_stream* s, const char* name_with_cmd) +{ + char* name = strdup(name_with_cmd); + char* cmdline = name; + + // Allocate space for the new filter and reset all pointers + struct af_instance* new=malloc(sizeof(struct af_instance)); + if (!name || !new) { + mp_msg(MSGT_AFILTER, MSGL_ERR, "[libaf] Could not allocate memory\n"); + goto err_out; + } + memset(new,0,sizeof(struct af_instance)); + + // Check for commandline parameters + strsep(&cmdline, "="); + + // Find filter from name + if(NULL == (new->info=af_find(name))) + goto err_out; + + /* Make sure that the filter is not already in the list if it is + non-reentrant */ + if(new->info->flags & AF_FLAGS_NOT_REENTRANT){ + if(af_get(s,name)){ + mp_msg(MSGT_AFILTER, MSGL_ERR, "[libaf] There can only be one instance of" + " the filter '%s' in each stream\n",name); + goto err_out; + } + } + + mp_msg(MSGT_AFILTER, MSGL_V, "[libaf] Adding filter %s \n",name); + + // Initialize the new filter + if(AF_OK == new->info->open(new) && + AF_ERROR < new->control(new,AF_CONTROL_POST_CREATE,&s->cfg)){ + if(cmdline){ + if(AF_ERROR>=new->control(new,AF_CONTROL_COMMAND_LINE,cmdline)) + goto err_out; + } + free(name); + return new; + } + +err_out: + free(new); + mp_msg(MSGT_AFILTER, MSGL_ERR, "[libaf] Couldn't create or open audio filter '%s'\n", + name); + free(name); + return NULL; +} + +/* Create and insert a new filter of type name before the filter in the + argument. This function can be called during runtime, the return + value is the new filter */ +static struct af_instance* af_prepend(struct af_stream* s, struct af_instance* af, const char* name) +{ + // Create the new filter and make sure it is OK + struct af_instance* new=af_create(s,name); + if(!new) + return NULL; + // Update pointers + new->next=af; + if(af){ + new->prev=af->prev; + af->prev=new; + } + else + s->last=new; + if(new->prev) + new->prev->next=new; + else + s->first=new; + return new; +} + +/* Create and insert a new filter of type name after the filter in the + argument. This function can be called during runtime, the return + value is the new filter */ +static struct af_instance* af_append(struct af_stream* s, struct af_instance* af, const char* name) +{ + // Create the new filter and make sure it is OK + struct af_instance* new=af_create(s,name); + if(!new) + return NULL; + // Update pointers + new->prev=af; + if(af){ + new->next=af->next; + af->next=new; + } + else + s->first=new; + if(new->next) + new->next->prev=new; + else + s->last=new; + return new; +} + +// Uninit and remove the filter "af" +void af_remove(struct af_stream* s, struct af_instance* af) +{ + if(!af) return; + + // Print friendly message + mp_msg(MSGT_AFILTER, MSGL_V, "[libaf] Removing filter %s \n",af->info->name); + + // Notify filter before changing anything + af->control(af,AF_CONTROL_PRE_DESTROY,0); + + // Detach pointers + if(af->prev) + af->prev->next=af->next; + else + s->first=af->next; + if(af->next) + af->next->prev=af->prev; + else + s->last=af->prev; + + // Uninitialize af and free memory + af->uninit(af); + free(af); +} + +static void print_fmt(struct mp_audio *d) +{ + if (d) { + mp_msg(MSGT_AFILTER, MSGL_V, "%dHz/%dch/%s", d->rate, d->nch, + af_fmt2str_short(d->format)); + } else { + mp_msg(MSGT_AFILTER, MSGL_V, "(?)"); + } +} + +static void af_print_filter_chain(struct af_stream* s) +{ + mp_msg(MSGT_AFILTER, MSGL_V, "Audio filter chain:\n"); + + mp_msg(MSGT_AFILTER, MSGL_V, " [in] "); + print_fmt(&s->input); + mp_msg(MSGT_AFILTER, MSGL_V, "\n"); + + struct af_instance *af = s->first; + while (af) { + mp_msg(MSGT_AFILTER, MSGL_V, " [%s] ", af->info->name); + print_fmt(af->data); + mp_msg(MSGT_AFILTER, MSGL_V, "\n"); + + af = af->next; + } + + mp_msg(MSGT_AFILTER, MSGL_V, " [out] "); + print_fmt(&s->output); + mp_msg(MSGT_AFILTER, MSGL_V, "\n"); +} + +// Warning: +// A failed af_reinit() leaves the audio chain behind in a useless, broken +// state (for example, format filters that were tentatively inserted stay +// inserted). +// In that case, you should always rebuild the filter chain, or abort. +int af_reinit(struct af_stream* s, struct af_instance* af) +{ + do{ + struct mp_audio in; // Format of the input to current filter + int rv=0; // Return value + + // Check if there are any filters left in the list + if(NULL == af){ + if(!(af=af_append(s,s->first,"dummy"))) + return AF_UNKNOWN; + else + return AF_ERROR; + } + + // Check if this is the first filter + if(!af->prev) + memcpy(&in,&(s->input),sizeof(struct mp_audio)); + else + memcpy(&in,af->prev->data,sizeof(struct mp_audio)); + // Reset just in case... + in.audio=NULL; + in.len=0; + + rv = af->control(af,AF_CONTROL_REINIT,&in); + switch(rv){ + case AF_OK: + af = af->next; + break; + case AF_FALSE:{ // Configuration filter is needed + // Do auto insertion only if force is not specified + if((AF_INIT_TYPE_MASK & s->cfg.force) != AF_INIT_FORCE){ + struct af_instance* new = NULL; + // Insert channels filter + if((af->prev?af->prev->data->nch:s->input.nch) != in.nch){ + // Create channels filter + if(NULL == (new = af_prepend(s,af,"channels"))) + return AF_ERROR; + // Set number of output channels + if(AF_OK != (rv = new->control(new,AF_CONTROL_CHANNELS,&in.nch))) + return rv; + // Initialize channels filter + if(!new->prev) + memcpy(&in,&(s->input),sizeof(struct mp_audio)); + else + memcpy(&in,new->prev->data,sizeof(struct mp_audio)); + if(AF_OK != (rv = new->control(new,AF_CONTROL_REINIT,&in))) + return rv; + } + // Insert format filter + if((af->prev?af->prev->data->format:s->input.format) != in.format){ + // Create format filter + if(NULL == (new = af_prepend(s,af,"format"))) + return AF_ERROR; + // Set output bits per sample + in.format |= af_bits2fmt(in.bps*8); + if(AF_OK != (rv = new->control(new,AF_CONTROL_FORMAT_FMT,&in.format))) + return rv; + // Initialize format filter + if(!new->prev) + memcpy(&in,&(s->input),sizeof(struct mp_audio)); + else + memcpy(&in,new->prev->data,sizeof(struct mp_audio)); + if(AF_OK != (rv = new->control(new,AF_CONTROL_REINIT,&in))) + return rv; + } + if(!new){ // Should _never_ happen + mp_msg(MSGT_AFILTER, MSGL_ERR, "[libaf] Unable to correct audio format. " + "This error should never occur, please send a bug report.\n"); + return AF_ERROR; + } + af=new->next; + } + else { + mp_msg(MSGT_AFILTER, MSGL_ERR, "[libaf] Automatic filter insertion disabled " + "but formats do not match. Giving up.\n"); + return AF_ERROR; + } + break; + } + case AF_DETACH:{ // Filter is redundant and wants to be unloaded + // Do auto remove only if force is not specified + if((AF_INIT_TYPE_MASK & s->cfg.force) != AF_INIT_FORCE){ + struct af_instance* aft=af->prev; + af_remove(s,af); + if(aft) + af=aft->next; + else + af=s->first; // Restart configuration + } + break; + } + default: + mp_msg(MSGT_AFILTER, MSGL_ERR, "[libaf] Reinitialization did not work, audio" + " filter '%s' returned error code %i\n",af->info->name,rv); + return AF_ERROR; + } + }while(af); + + af_print_filter_chain(s); + + return AF_OK; +} + +// Uninit and remove all filters +void af_uninit(struct af_stream* s) +{ + while(s->first) + af_remove(s,s->first); +} + +/** + * Extend the filter chain so we get the required output format at the end. + * \return AF_ERROR on error, AF_OK if successful. + */ +static int fixup_output_format(struct af_stream* s) +{ + struct af_instance* af = NULL; + // Check number of output channels fix if not OK + // If needed always inserted last -> easy to screw up other filters + if(s->output.nch && s->last->data->nch!=s->output.nch){ + if(!strcmp(s->last->info->name,"format")) + af = af_prepend(s,s->last,"channels"); + else + af = af_append(s,s->last,"channels"); + // Init the new filter + if(!af || (AF_OK != af->control(af,AF_CONTROL_CHANNELS,&(s->output.nch)))) + return AF_ERROR; + if(AF_OK != af_reinit(s,af)) + return AF_ERROR; + } + + // Check output format fix if not OK + if(s->output.format != AF_FORMAT_UNKNOWN && + s->last->data->format != s->output.format){ + if(strcmp(s->last->info->name,"format")) + af = af_append(s,s->last,"format"); + else + af = s->last; + // Init the new filter + s->output.format |= af_bits2fmt(s->output.bps*8); + if(!af || (AF_OK != af->control(af,AF_CONTROL_FORMAT_FMT,&(s->output.format)))) + return AF_ERROR; + if(AF_OK != af_reinit(s,af)) + return AF_ERROR; + } + + // Re init again just in case + if(AF_OK != af_reinit(s,s->first)) + return AF_ERROR; + + if (s->output.format == AF_FORMAT_UNKNOWN) + s->output.format = s->last->data->format; + if (!s->output.nch) s->output.nch = s->last->data->nch; + if (!s->output.rate) s->output.rate = s->last->data->rate; + if((s->last->data->format != s->output.format) || + (s->last->data->nch != s->output.nch) || + (s->last->data->rate != s->output.rate)) { + return AF_ERROR; + } + return AF_OK; +} + +/** + * Automatic downmix to stereo in case the codec does not implement it. + */ +static void af_downmix(struct af_stream* s) +{ + static const char * const downmix_strs[AF_NCH + 1] = { + /* FL FR RL RR FC LF AL AR */ + [3] = "pan=2:" "0.6:0:" "0:0.6:" "0.4:0.4", + [4] = "pan=2:" "0.6:0:" "0:0.6:" "0.4:0:" "0:0.4", + [5] = "pan=2:" "0.5:0:" "0:0.5:" "0.2:0:" "0:0.2:" "0.3:0.3", + [6] = "pan=2:" "0.4:0:" "0:0.4:" "0.2:0:" "0:0.2:" "0.3:0.3:" "0.1:0.1", + [7] = "pan=2:" "0.4:0:" "0:0.4:" "0.2:0:" "0:0.2:" "0.3:0.3:" "0.1:0:" "0:0.1", + [8] = "pan=2:" "0.4:0:" "0:0.4:" "0.15:0:" "0:0.15:" "0.25:0.25:" "0.1:0.1:" "0.1:0:" "0:0.1", + }; + const char *af_pan_str = downmix_strs[s->input.nch]; + + if (af_pan_str) + af_append(s, s->first, af_pan_str); +} + +/* Initialize the stream "s". This function creates a new filter list + if necessary according to the values set in input and output. Input + and output should contain the format of the current movie and the + formate of the preferred output respectively. The function is + reentrant i.e. if called with an already initialized stream the + stream will be reinitialized. + If one of the prefered output parameters is 0 the one that needs + no conversion is used (i.e. the output format in the last filter). + The return value is 0 if success and -1 if failure */ +int af_init(struct af_stream* s) +{ + struct MPOpts *opts = s->opts; + int i=0; + + // Sanity check + if(!s) return -1; + + // Precaution in case caller is misbehaving + s->input.audio = s->output.audio = NULL; + s->input.len = s->output.len = 0; + + // Figure out how fast the machine is + if(AF_INIT_AUTO == (AF_INIT_TYPE_MASK & s->cfg.force)) + s->cfg.force = (s->cfg.force & ~AF_INIT_TYPE_MASK) | AF_INIT_TYPE; + + // Check if this is the first call + if(!s->first){ + // Append a downmix pan filter at the beginning of the chain if needed + if (s->input.nch != opts->audio_output_channels + && opts->audio_output_channels == 2) + af_downmix(s); + // Add all filters in the list (if there are any) + if (s->cfg.list) { + while(s->cfg.list[i]){ + if(!af_append(s,s->last,s->cfg.list[i++])) + return -1; + } + } + } + + // If we do not have any filters otherwise + // add dummy to make automatic format conversion work + if (!s->first && !af_append(s, s->first, "dummy")) + return -1; + + // Init filters + if(AF_OK != af_reinit(s,s->first)) + return -1; + + // make sure the chain is not empty and valid (e.g. because of AF_DETACH) + if (!s->first) + if (!af_append(s,s->first,"dummy") || AF_OK != af_reinit(s,s->first)) + return -1; + + // Check output format + if((AF_INIT_TYPE_MASK & s->cfg.force) != AF_INIT_FORCE){ + struct af_instance* af = NULL; // New filter + // Check output frequency if not OK fix with resample + if(s->output.rate && s->last->data->rate!=s->output.rate){ + // try to find a filter that can change samplrate + af = af_control_any_rev(s, AF_CONTROL_RESAMPLE_RATE | AF_CONTROL_SET, + &(s->output.rate)); + if (!af) { + char *resampler = "resample"; + if ((AF_INIT_TYPE_MASK & s->cfg.force) == AF_INIT_SLOW) + resampler = "lavcresample"; + if((AF_INIT_TYPE_MASK & s->cfg.force) == AF_INIT_SLOW){ + if(!strcmp(s->first->info->name,"format")) + af = af_append(s,s->first,resampler); + else + af = af_prepend(s,s->first,resampler); + } + else{ + if(!strcmp(s->last->info->name,"format")) + af = af_prepend(s,s->last,resampler); + else + af = af_append(s,s->last,resampler); + } + // Init the new filter + if(!af || (AF_OK != af->control(af,AF_CONTROL_RESAMPLE_RATE | AF_CONTROL_SET, + &(s->output.rate)))) + return -1; + // Use lin int if the user wants fast + if ((AF_INIT_TYPE_MASK & s->cfg.force) == AF_INIT_FAST) { + char args[32]; + sprintf(args, "%d", s->output.rate); + if (strcmp(resampler, "lavcresample") == 0) + strcat(args, ":1"); + else + strcat(args, ":0:0"); + af->control(af, AF_CONTROL_COMMAND_LINE, args); + } + } + if(AF_OK != af_reinit(s,af)) + return -1; + } + if (AF_OK != fixup_output_format(s)) { + // Something is stuffed audio out will not work + mp_msg(MSGT_AFILTER, MSGL_ERR, "[libaf] Unable to setup filter system can not" + " meet sound-card demands, please send a bug report. \n"); + af_uninit(s); + return -1; + } + } + return 0; +} + +/* Add filter during execution. This function adds the filter "name" + to the stream s. The filter will be inserted somewhere nice in the + list of filters. The return value is a pointer to the new filter, + If the filter couldn't be added the return value is NULL. */ +struct af_instance* af_add(struct af_stream* s, char* name){ + struct af_instance* new; + // Sanity check + if(!s || !s->first || !name) + return NULL; + // Insert the filter somewhere nice + if(!strcmp(s->first->info->name,"format")) + new = af_append(s, s->first, name); + else + new = af_prepend(s, s->first, name); + if(!new) + return NULL; + + // Reinitalize the filter list + if(AF_OK != af_reinit(s, s->first) || + AF_OK != fixup_output_format(s)){ + while (s->first) + af_remove(s, s->first); + af_init(s); + return NULL; + } + return new; +} + +// Filter data chunk through the filters in the list +struct mp_audio* af_play(struct af_stream* s, struct mp_audio* data) +{ + struct af_instance* af=s->first; + // Iterate through all filters + do{ + if (data->len <= 0) break; + data=af->play(af,data); + af=af->next; + }while(af && data); + return data; +} + +/* Calculate the minimum output buffer size for given input data d + * when using the RESIZE_LOCAL_BUFFER macro. The +t+1 part ensures the + * value is >= len*mul rounded upwards to whole samples even if the + * double 'mul' is inexact. */ +int af_lencalc(double mul, struct mp_audio* d) +{ + int t = d->bps * d->nch; + return d->len * mul + t + 1; +} + +// Calculate average ratio of filter output size to input size +double af_calc_filter_multiplier(struct af_stream* s) +{ + struct af_instance* af=s->first; + double mul = 1; + // Iterate through all filters and calculate total multiplication factor + do{ + mul *= af->mul; + af=af->next; + }while(af); + + return mul; +} + +/* Calculate the total delay [bytes output] caused by the filters */ +double af_calc_delay(struct af_stream* s) +{ + struct af_instance* af=s->first; + register double delay = 0.0; + // Iterate through all filters + while(af){ + delay += af->delay; + delay *= af->mul; + af=af->next; + } + return delay; +} + +/* Helper function called by the macro with the same name this + function should not be called directly */ +int af_resize_local_buffer(struct af_instance* af, struct mp_audio* data) +{ + // Calculate new length + register int len = af_lencalc(af->mul,data); + mp_msg(MSGT_AFILTER, MSGL_V, "[libaf] Reallocating memory in module %s, " + "old len = %i, new len = %i\n",af->info->name,af->data->len,len); + // If there is a buffer free it + free(af->data->audio); + // Create new buffer and check that it is OK + af->data->audio = malloc(len); + if(!af->data->audio){ + mp_msg(MSGT_AFILTER, MSGL_FATAL, "[libaf] Could not allocate memory \n"); + return AF_ERROR; + } + af->data->len=len; + return AF_OK; +} + +// documentation in af.h +struct af_instance *af_control_any_rev (struct af_stream* s, int cmd, void* arg) { + int res = AF_UNKNOWN; + struct af_instance* filt = s->last; + while (filt) { + res = filt->control(filt, cmd, arg); + if (res == AF_OK) + return filt; + filt = filt->prev; + } + return NULL; +} + +void af_help (void) { + int i = 0; + mp_msg(MSGT_AFILTER, MSGL_INFO, "Available audio filters:\n"); + while (filter_list[i]) { + if (filter_list[i]->comment && filter_list[i]->comment[0]) + mp_msg(MSGT_AFILTER, MSGL_INFO, " %-15s: %s (%s)\n", filter_list[i]->name, filter_list[i]->info, filter_list[i]->comment); + else + mp_msg(MSGT_AFILTER, MSGL_INFO, " %-15s: %s\n", filter_list[i]->name, filter_list[i]->info); + i++; + } +} + +void af_fix_parameters(struct mp_audio *data) +{ + if (data->nch < 0 || data->nch > AF_NCH) { + mp_msg(MSGT_AFILTER, MSGL_ERR, "Invalid number of channels %i, assuming 2.\n", data->nch); + data->nch = 2; + } + data->bps = af_fmt2bits(data->format)/8; +} diff --git a/audio/filter/af.h b/audio/filter/af.h new file mode 100644 index 0000000000..edce49a978 --- /dev/null +++ b/audio/filter/af.h @@ -0,0 +1,349 @@ +/* + * This file is part of MPlayer. + * + * MPlayer 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. + * + * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPLAYER_AF_H +#define MPLAYER_AF_H + +#include <stdio.h> + +#include "config.h" + +#include "options.h" +#include "libaf/format.h" +#include "control.h" +#include "cpudetect.h" +#include "mp_msg.h" + +struct af_instance; + +// Number of channels +#ifndef AF_NCH +#define AF_NCH 8 +#endif + +// Audio data chunk +struct mp_audio { + void *audio; // data buffer + int len; // buffer length + int rate; // sample rate + int nch; // number of channels + int format; // format + int bps; // bytes per sample +}; + + +// Flags used for defining the behavior of an audio filter +#define AF_FLAGS_REENTRANT 0x00000000 +#define AF_FLAGS_NOT_REENTRANT 0x00000001 + +/* Audio filter information not specific for current instance, but for + a specific filter */ +struct af_info { + const char *info; + const char *name; + const char *author; + const char *comment; + const int flags; + int (*open)(struct af_instance *vf); +}; + +// Linked list of audio filters +struct af_instance { + struct af_info *info; + int (*control)(struct af_instance *af, int cmd, void *arg); + void (*uninit)(struct af_instance *af); + struct mp_audio * (*play)(struct af_instance *af, struct mp_audio *data); + void *setup; // setup data for this specific instance and filter + struct mp_audio *data; // configuration for outgoing data stream + struct af_instance *next; + struct af_instance *prev; + double delay; /* Delay caused by the filter, in units of bytes read without + * corresponding output */ + double mul; /* length multiplier: how much does this instance change + the length of the buffer. */ +}; + +// Initialization flags +extern int *af_cpu_speed; + +#define AF_INIT_AUTO 0x00000000 +#define AF_INIT_SLOW 0x00000001 +#define AF_INIT_FAST 0x00000002 +#define AF_INIT_FORCE 0x00000003 +#define AF_INIT_TYPE_MASK 0x00000003 + +#define AF_INIT_INT 0x00000000 +#define AF_INIT_FLOAT 0x00000004 +#define AF_INIT_FORMAT_MASK 0x00000004 + +// Default init type +#ifndef AF_INIT_TYPE +#define AF_INIT_TYPE (af_cpu_speed ? *af_cpu_speed : AF_INIT_SLOW) +#endif + +// Configuration switches +struct af_cfg { + int force; // Initialization type + char **list; /* list of names of filters that are added to filter + list during first initialization of stream */ +}; + +// Current audio stream +struct af_stream { + // The first and last filter in the list + struct af_instance *first; + struct af_instance *last; + // Storage for input and output data formats + struct mp_audio input; + struct mp_audio output; + // Configuration for this stream + struct af_cfg cfg; + struct MPOpts *opts; +}; + +/********************************************* + // Return values + */ + +#define AF_DETACH 2 +#define AF_OK 1 +#define AF_TRUE 1 +#define AF_FALSE 0 +#define AF_UNKNOWN -1 +#define AF_ERROR -2 +#define AF_FATAL -3 + + + +/********************************************* + // Export functions + */ + +/** + * \defgroup af_chain Audio filter chain functions + * \{ + * \param s filter chain + */ + +/** + * \brief Initialize the stream "s". + * \return 0 on success, -1 on failure + * + * This function creates a new filter list if necessary, according + * to the values set in input and output. Input and output should contain + * the format of the current movie and the format of the preferred output + * respectively. + * Filters to convert to the preferred output format are inserted + * automatically, except when they are set to 0. + * The function is reentrant i.e. if called with an already initialized + * stream the stream will be reinitialized. + */ +int af_init(struct af_stream *s); + +/** + * \brief Uninit and remove all filters from audio filter chain + */ +void af_uninit(struct af_stream *s); + +/** + * \brief Reinit the filter list from the given filter on downwards + * \param Filter instance to begin the reinit from + * \return AF_OK on success or AF_ERROR on failure + */ +int af_reinit(struct af_stream *s, struct af_instance *af); + +/** + * \brief This function adds the filter "name" to the stream s. + * \param name name of filter to add + * \return pointer to the new filter, NULL if insert failed + * + * The filter will be inserted somewhere nice in the + * list of filters (i.e. at the beginning unless the + * first filter is the format filter (why??). + */ +struct af_instance *af_add(struct af_stream *s, char *name); + +/** + * \brief Uninit and remove the filter "af" + * \param af filter to remove + */ +void af_remove(struct af_stream *s, struct af_instance *af); + +/** + * \brief find filter in chain by name + * \param name name of the filter to find + * \return first filter with right name or NULL if not found + * + * This function is used for finding already initialized filters + */ +struct af_instance *af_get(struct af_stream *s, char *name); + +/** + * \brief filter data chunk through the filters in the list + * \param data data to play + * \return resulting data + * \ingroup af_chain + */ +struct mp_audio *af_play(struct af_stream *s, struct mp_audio *data); + +/** + * \brief send control to all filters, starting with the last until + * one accepts the command with AF_OK. + * \param cmd filter control command + * \param arg argument for filter command + * \return the accepting filter or NULL if none was found + */ +struct af_instance *af_control_any_rev(struct af_stream *s, int cmd, void *arg); + +/** + * \brief calculate average ratio of filter output lenth to input length + * \return the ratio + */ +double af_calc_filter_multiplier(struct af_stream *s); + +/** + * \brief Calculate the total delay caused by the filters + * \return delay in bytes of "missing" output + */ +double af_calc_delay(struct af_stream *s); + +/** \} */ // end of af_chain group + +// Helper functions and macros used inside the audio filters + +/** + * \defgroup af_filter Audio filter helper functions + * \{ + */ + +/* Helper function called by the macro with the same name only to be + called from inside filters */ +int af_resize_local_buffer(struct af_instance *af, struct mp_audio *data); + +/* Helper function used to calculate the exact buffer length needed + when buffers are resized. The returned length is >= than what is + needed */ +int af_lencalc(double mul, struct mp_audio *data); + +/** + * \brief convert dB to gain value + * \param n number of values to convert + * \param in [in] values in dB, <= -200 will become 0 gain + * \param out [out] gain values + * \param k input values are divided by this + * \param mi minimum dB value, input will be clamped to this + * \param ma maximum dB value, input will be clamped to this + * \return AF_ERROR on error, AF_OK otherwise + */ +int af_from_dB(int n, float *in, float *out, float k, float mi, float ma); + +/** + * \brief convert gain value to dB + * \param n number of values to convert + * \param in [in] gain values, 0 wil become -200 dB + * \param out [out] values in dB + * \param k output values will be multiplied by this + * \return AF_ERROR on error, AF_OK otherwise + */ +int af_to_dB(int n, float *in, float *out, float k); + +/** + * \brief convert milliseconds to sample time + * \param n number of values to convert + * \param in [in] values in milliseconds + * \param out [out] sample time values + * \param rate sample rate + * \param mi minimum ms value, input will be clamped to this + * \param ma maximum ms value, input will be clamped to this + * \return AF_ERROR on error, AF_OK otherwise + */ +int af_from_ms(int n, float *in, int *out, int rate, float mi, float ma); + +/** + * \brief convert sample time to milliseconds + * \param n number of values to convert + * \param in [in] sample time values + * \param out [out] values in milliseconds + * \param rate sample rate + * \return AF_ERROR on error, AF_OK otherwise + */ +int af_to_ms(int n, int *in, float *out, int rate); + +/** + * \brief test if output format matches + * \param af audio filter + * \param out needed format, will be overwritten by available + * format if they do not match + * \return AF_FALSE if formats do not match, AF_OK if they match + * + * compares the format, bps, rate and nch values of af->data with out + */ +int af_test_output(struct af_instance *af, struct mp_audio *out); + +/** + * \brief soft clipping function using sin() + * \param a input value + * \return clipped value + */ +float af_softclip(float a); + +/** \} */ // end of af_filter group, but more functions of this group below + +/** Print a list of all available audio filters */ +void af_help(void); + +/** + * \brief fill the missing parameters in the struct mp_audio structure + * \param data structure to fill + * \ingroup af_filter + * + * Currently only sets bps based on format + */ +void af_fix_parameters(struct mp_audio *data); + +/** Memory reallocation macro: if a local buffer is used (i.e. if the + filter doesn't operate on the incoming buffer this macro must be + called to ensure the buffer is big enough. + * \ingroup af_filter + */ +#define RESIZE_LOCAL_BUFFER(a, d) \ + ((a->data->len < \ + af_lencalc(a->mul, d)) ? af_resize_local_buffer(a, d) : AF_OK) + +/* Some other useful macro definitions*/ +#ifndef min +#define min(a, b)(((a) > (b)) ? (b) : (a)) +#endif + +#ifndef max +#define max(a, b)(((a) > (b)) ? (a) : (b)) +#endif + +#ifndef clamp +#define clamp(a, min, max) (((a) > (max)) ? (max) : (((a) < (min)) ? (min) : (a))) +#endif + +#ifndef sign +#define sign(a) (((a) > 0) ? (1) : (-1)) +#endif + +#ifndef lrnd +#define lrnd(a, b) ((b)((a) >= 0.0 ? (a) + 0.5 : (a) - 0.5)) +#endif + +#endif /* MPLAYER_AF_H */ diff --git a/audio/filter/af_bs2b.c b/audio/filter/af_bs2b.c new file mode 100644 index 0000000000..ccbf3794c5 --- /dev/null +++ b/audio/filter/af_bs2b.c @@ -0,0 +1,274 @@ +/* + * The Bauer stereophonic-to-binaural DSP using bs2b library: + * http://bs2b.sourceforge.net/ + * + * Copyright (c) 2009 Andrew Savchenko + * + * This file is part of MPlayer. + * + * MPlayer 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. + * + * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <bs2b.h> +#include <inttypes.h> +#include <stdlib.h> +#include <string.h> + +#include "af.h" +#include "subopt-helper.h" + +/// Internal specific data of the filter +struct af_bs2b { + int fcut; ///< cut frequency in Hz + int feed; ///< feed level for low frequencies in 0.1*dB + char *profile; ///< profile (available crossfeed presets) + t_bs2bdp filter; ///< instance of a library filter +}; + +#define PLAY(name, type) \ +static struct mp_audio *play_##name(struct af_instance *af, struct mp_audio *data) \ +{ \ + /* filter is called for all pairs of samples available in the buffer */ \ + bs2b_cross_feed_##name(((struct af_bs2b*)(af->setup))->filter, \ + (type*)(data->audio), data->len/data->bps/2); \ +\ + return data; \ +} + +PLAY(f, float) +PLAY(fbe, float) +PLAY(fle, float) +PLAY(s32be, int32_t) +PLAY(u32be, uint32_t) +PLAY(s32le, int32_t) +PLAY(u32le, uint32_t) +PLAY(s24be, bs2b_int24_t) +PLAY(u24be, bs2b_uint24_t) +PLAY(s24le, bs2b_int24_t) +PLAY(u24le, bs2b_uint24_t) +PLAY(s16be, int16_t) +PLAY(u16be, uint16_t) +PLAY(s16le, int16_t) +PLAY(u16le, uint16_t) +PLAY(s8, int8_t) +PLAY(u8, uint8_t) + +/// Sanity check for fcut value +static int test_fcut(void *par) +{ + const int val = *(int*)par; + if (val >= BS2B_MINFCUT && val <= BS2B_MAXFCUT) + return 1; + + mp_msg(MSGT_AFILTER, MSGL_ERR, + "[bs2b] Cut frequency must be in range [%d..%d], but current value is %d.\n", + BS2B_MINFCUT, BS2B_MAXFCUT, val); + return 0; +} + +/// Sanity check for feed value +static int test_feed(void *par) +{ + const int val = *(int*)par; + if (val >= BS2B_MINFEED && val <= BS2B_MAXFEED) + return 1; + + mp_msg(MSGT_AFILTER, MSGL_ERR, + "[bs2b] Feed level must be in range [%d..%d], but current value is %d.\n", + BS2B_MINFEED, BS2B_MAXFEED, val); + return 0; +} + +/// Initialization and runtime control +static int control(struct af_instance *af, int cmd, void *arg) +{ + struct af_bs2b *s = af->setup; + + switch (cmd) { + case AF_CONTROL_REINIT: { + int format; + char buf[256]; + // Sanity check + if (!arg) return AF_ERROR; + + format = ((struct mp_audio*)arg)->format; + af->data->rate = ((struct mp_audio*)arg)->rate; + af->data->nch = 2; // bs2b is useful only for 2ch audio + af->data->bps = ((struct mp_audio*)arg)->bps; + af->data->format = format; + + /* check for formats supported by libbs2b + and assign corresponding handlers */ + switch (format) { + case AF_FORMAT_FLOAT_BE: + af->play = play_fbe; + break; + case AF_FORMAT_FLOAT_LE: + af->play = play_fle; + break; + case AF_FORMAT_S32_BE: + af->play = play_s32be; + break; + case AF_FORMAT_U32_BE: + af->play = play_u32be; + break; + case AF_FORMAT_S32_LE: + af->play = play_s32le; + break; + case AF_FORMAT_U32_LE: + af->play = play_u32le; + break; + case AF_FORMAT_S24_BE: + af->play = play_s24be; + break; + case AF_FORMAT_U24_BE: + af->play = play_u24be; + break; + case AF_FORMAT_S24_LE: + af->play = play_s24le; + break; + case AF_FORMAT_U24_LE: + af->play = play_u24le; + break; + case AF_FORMAT_S16_BE: + af->play = play_s16be; + break; + case AF_FORMAT_U16_BE: + af->play = play_u16be; + break; + case AF_FORMAT_S16_LE: + af->play = play_s16le; + break; + case AF_FORMAT_U16_LE: + af->play = play_u16le; + break; + case AF_FORMAT_S8: + af->play = play_s8; + break; + case AF_FORMAT_U8: + af->play = play_u8; + break; + default: + af->play = play_f; + af->data->format = AF_FORMAT_FLOAT_NE; + af->data->bps = 4; + break; + } + + // bs2b have srate limits, try to resample if needed + if (af->data->rate > BS2B_MAXSRATE || af->data->rate < BS2B_MINSRATE) { + af->data->rate = BS2B_DEFAULT_SRATE; + mp_msg(MSGT_AFILTER, MSGL_WARN, + "[bs2b] Requested sample rate %d Hz is out of bounds [%d..%d] Hz.\n" + "[bs2b] Trying to resample to %d Hz.\n", + af->data->rate, BS2B_MINSRATE, BS2B_MAXSRATE, BS2B_DEFAULT_SRATE); + } + bs2b_set_srate(s->filter, (long)af->data->rate); + mp_msg(MSGT_AFILTER, MSGL_V, "[bs2b] using format %s\n", + af_fmt2str(af->data->format,buf,256)); + + return af_test_output(af,(struct mp_audio*)arg); + } + case AF_CONTROL_COMMAND_LINE: { + const opt_t subopts[] = { + {"fcut", OPT_ARG_INT, &s->fcut, test_fcut}, + {"feed", OPT_ARG_INT, &s->feed, test_feed}, + {"profile", OPT_ARG_MSTRZ, &s->profile, NULL}, + {NULL} + }; + if (subopt_parse(arg, subopts) != 0) { + mp_msg(MSGT_AFILTER, MSGL_ERR, "[bs2b] Invalid option specified.\n"); + free(s->profile); + return AF_ERROR; + } + // parse profile if specified + if (s->profile) { + if (!strcmp(s->profile, "default")) + bs2b_set_level(s->filter, BS2B_DEFAULT_CLEVEL); + else if (!strcmp(s->profile, "cmoy")) + bs2b_set_level(s->filter, BS2B_CMOY_CLEVEL); + else if (!strcmp(s->profile, "jmeier")) + bs2b_set_level(s->filter, BS2B_JMEIER_CLEVEL); + else { + mp_msg(MSGT_AFILTER, MSGL_ERR, + "[bs2b] Invalid profile specified: %s.\n" + "[bs2b] Available profiles are: default, cmoy, jmeier.\n", + s->profile); + free(s->profile); + return AF_ERROR; + } + } + // set fcut and feed only if specified, otherwise defaults will be used + if (s->fcut) + bs2b_set_level_fcut(s->filter, s->fcut); + if (s->feed) + bs2b_set_level_feed(s->filter, s->feed); + + mp_msg(MSGT_AFILTER, MSGL_V, + "[bs2b] using cut frequency %d, LF feed level %d\n", + bs2b_get_level_fcut(s->filter), bs2b_get_level_feed(s->filter)); + free(s->profile); + return AF_OK; + } + } + return AF_UNKNOWN; +} + +/// Deallocate memory and close library +static void uninit(struct af_instance *af) +{ + struct af_bs2b *s = af->setup; + free(af->data); + if (s && s->filter) + bs2b_close(s->filter); + free(s); +} + +/// Allocate memory, set function pointers and init library +static int af_open(struct af_instance *af) +{ + struct af_bs2b *s; + af->control = control; + af->uninit = uninit; + af->mul = 1; + if (!(af->data = calloc(1, sizeof(struct mp_audio)))) + return AF_ERROR; + if (!(af->setup = s = calloc(1, sizeof(struct af_bs2b)))) { + free(af->data); + return AF_ERROR; + } + + // NULL means failed initialization + if (!(s->filter = bs2b_open())) { + free(af->data); + free(af->setup); + return AF_ERROR; + } + // Set zero defaults indicating no option was specified. + s->profile = NULL; + s->fcut = 0; + s->feed = 0; + return AF_OK; +} + +/// Description of this filter +struct af_info af_info_bs2b = { + "Bauer stereophonic-to-binaural audio filter", + "bs2b", + "Andrew Savchenko", + "", + AF_FLAGS_REENTRANT, + af_open +}; diff --git a/audio/filter/af_center.c b/audio/filter/af_center.c new file mode 100644 index 0000000000..aa9aae8514 --- /dev/null +++ b/audio/filter/af_center.c @@ -0,0 +1,129 @@ +/* + * This filter adds a center channel to the audio stream by + * averaging the left and right channel. + * There are two runtime controls one for setting which channel + * to insert the center-audio into called AF_CONTROL_SUB_CH. + * + * FIXME: implement a high-pass filter for better results. + * + * copyright (c) 2005 Alex Beregszaszi + * + * This file is part of MPlayer. + * + * MPlayer 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. + * + * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "af.h" + +// Data for specific instances of this filter +typedef struct af_center_s +{ + int ch; // Channel number which to insert the filtered data +}af_center_t; + +// Initialization and runtime control +static int control(struct af_instance* af, int cmd, void* arg) +{ + af_center_t* s = af->setup; + + switch(cmd){ + case AF_CONTROL_REINIT:{ + // Sanity check + if(!arg) return AF_ERROR; + + af->data->rate = ((struct mp_audio*)arg)->rate; + af->data->nch = max(s->ch+1,((struct mp_audio*)arg)->nch); + af->data->format = AF_FORMAT_FLOAT_NE; + af->data->bps = 4; + + return af_test_output(af,(struct mp_audio*)arg); + } + case AF_CONTROL_COMMAND_LINE:{ + int ch=1; + sscanf(arg,"%i", &ch); + return control(af,AF_CONTROL_CENTER_CH | AF_CONTROL_SET, &ch); + } + case AF_CONTROL_CENTER_CH | AF_CONTROL_SET: // Requires reinit + // Sanity check + if((*(int*)arg >= AF_NCH) || (*(int*)arg < 0)){ + mp_msg(MSGT_AFILTER, MSGL_ERR, "[sub] Center channel number must be between " + " 0 and %i current value is %i\n", AF_NCH-1, *(int*)arg); + return AF_ERROR; + } + s->ch = *(int*)arg; + return AF_OK; + case AF_CONTROL_CENTER_CH | AF_CONTROL_GET: + *(int*)arg = s->ch; + return AF_OK; + } + return AF_UNKNOWN; +} + +// Deallocate memory +static void uninit(struct af_instance* af) +{ + free(af->data); + free(af->setup); +} + +// Filter data through filter +static struct mp_audio* play(struct af_instance* af, struct mp_audio* data) +{ + struct mp_audio* c = data; // Current working data + af_center_t* s = af->setup; // Setup for this instance + float* a = c->audio; // Audio data + int len = c->len/4; // Number of samples in current audio block + int nch = c->nch; // Number of channels + int ch = s->ch; // Channel in which to insert the center audio + register int i; + + // Run filter + for(i=0;i<len;i+=nch){ + // Average left and right + a[i+ch] = (a[i]/2) + (a[i+1]/2); + } + + return c; +} + +// Allocate memory and set function pointers +static int af_open(struct af_instance* af){ + af_center_t* s; + af->control=control; + af->uninit=uninit; + af->play=play; + af->mul=1; + af->data=calloc(1,sizeof(struct mp_audio)); + af->setup=s=calloc(1,sizeof(af_center_t)); + if(af->data == NULL || af->setup == NULL) + return AF_ERROR; + // Set default values + s->ch = 1; // Channel nr 2 + return AF_OK; +} + +// Description of this filter +struct af_info af_info_center = { + "Audio filter for adding a center channel", + "center", + "Alex Beregszaszi", + "", + AF_FLAGS_NOT_REENTRANT, + af_open +}; diff --git a/audio/filter/af_channels.c b/audio/filter/af_channels.c new file mode 100644 index 0000000000..8f676d8cfd --- /dev/null +++ b/audio/filter/af_channels.c @@ -0,0 +1,306 @@ +/* + * Audio filter that adds and removes channels, according to the + * command line parameter channels. It is stupid and can only add + * silence or copy channels, not mix or filter. + * + * This file is part of MPlayer. + * + * MPlayer 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. + * + * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <inttypes.h> + +#include "af.h" + +#define FR 0 +#define TO 1 + +typedef struct af_channels_s{ + int route[AF_NCH][2]; + int nr; + int router; +}af_channels_t; + +// Local function for copying data +static void copy(void* in, void* out, int ins, int inos,int outs, int outos, int len, int bps) +{ + switch(bps){ + case 1:{ + int8_t* tin = (int8_t*)in; + int8_t* tout = (int8_t*)out; + tin += inos; + tout += outos; + len = len/ins; + while(len--){ + *tout=*tin; + tin +=ins; + tout+=outs; + } + break; + } + case 2:{ + int16_t* tin = (int16_t*)in; + int16_t* tout = (int16_t*)out; + tin += inos; + tout += outos; + len = len/(2*ins); + while(len--){ + *tout=*tin; + tin +=ins; + tout+=outs; + } + break; + } + case 3:{ + int8_t* tin = (int8_t*)in; + int8_t* tout = (int8_t*)out; + tin += 3 * inos; + tout += 3 * outos; + len = len / ( 3 * ins); + while (len--) { + tout[0] = tin[0]; + tout[1] = tin[1]; + tout[2] = tin[2]; + tin += 3 * ins; + tout += 3 * outs; + } + break; + } + case 4:{ + int32_t* tin = (int32_t*)in; + int32_t* tout = (int32_t*)out; + tin += inos; + tout += outos; + len = len/(4*ins); + while(len--){ + *tout=*tin; + tin +=ins; + tout+=outs; + } + break; + } + case 8:{ + int64_t* tin = (int64_t*)in; + int64_t* tout = (int64_t*)out; + tin += inos; + tout += outos; + len = len/(8*ins); + while(len--){ + *tout=*tin; + tin +=ins; + tout+=outs; + } + break; + } + default: + mp_msg(MSGT_AFILTER, MSGL_ERR, "[channels] Unsupported number of bytes/sample: %i" + " please report this error on the MPlayer mailing list. \n",bps); + } +} + +// Make sure the routes are sane +static int check_routes(af_channels_t* s, int nin, int nout) +{ + int i; + if((s->nr < 1) || (s->nr > AF_NCH)){ + mp_msg(MSGT_AFILTER, MSGL_ERR, "[channels] The number of routing pairs must be" + " between 1 and %i. Current value is %i\n",AF_NCH,s->nr); + return AF_ERROR; + } + + for(i=0;i<s->nr;i++){ + if((s->route[i][FR] >= nin) || (s->route[i][TO] >= nout)){ + mp_msg(MSGT_AFILTER, MSGL_ERR, "[channels] Invalid routing in pair nr. %i.\n", i); + return AF_ERROR; + } + } + return AF_OK; +} + +// Initialization and runtime control +static int control(struct af_instance* af, int cmd, void* arg) +{ + af_channels_t* s = af->setup; + switch(cmd){ + case AF_CONTROL_REINIT: + + // Set default channel assignment + if(!s->router){ + int i; + // Make sure this filter isn't redundant + if(af->data->nch == ((struct mp_audio*)arg)->nch) + return AF_DETACH; + + // If mono: fake stereo + if(((struct mp_audio*)arg)->nch == 1){ + s->nr = min(af->data->nch,2); + for(i=0;i<s->nr;i++){ + s->route[i][FR] = 0; + s->route[i][TO] = i; + } + } + else{ + s->nr = min(af->data->nch, ((struct mp_audio*)arg)->nch); + for(i=0;i<s->nr;i++){ + s->route[i][FR] = i; + s->route[i][TO] = i; + } + } + } + + af->data->rate = ((struct mp_audio*)arg)->rate; + af->data->format = ((struct mp_audio*)arg)->format; + af->data->bps = ((struct mp_audio*)arg)->bps; + af->mul = (double)af->data->nch / ((struct mp_audio*)arg)->nch; + return check_routes(s,((struct mp_audio*)arg)->nch,af->data->nch); + case AF_CONTROL_COMMAND_LINE:{ + int nch = 0; + int n = 0; + // Check number of channels and number of routing pairs + sscanf(arg, "%i:%i%n", &nch, &s->nr, &n); + + // If router scan commandline for routing pairs + if(s->nr){ + char* cp = &((char*)arg)[n]; + int ch = 0; + // Sanity check + if((s->nr < 1) || (s->nr > AF_NCH)){ + mp_msg(MSGT_AFILTER, MSGL_ERR, "[channels] The number of routing pairs must be" + " between 1 and %i. Current value is %i\n",AF_NCH,s->nr); + } + s->router = 1; + // Scan for pairs on commandline + while((*cp == ':') && (ch < s->nr)){ + sscanf(cp, ":%i:%i%n" ,&s->route[ch][FR], &s->route[ch][TO], &n); + mp_msg(MSGT_AFILTER, MSGL_V, "[channels] Routing from channel %i to" + " channel %i\n",s->route[ch][FR],s->route[ch][TO]); + cp = &cp[n]; + ch++; + } + } + + if(AF_OK != af->control(af,AF_CONTROL_CHANNELS | AF_CONTROL_SET ,&nch)) + return AF_ERROR; + return AF_OK; + } + case AF_CONTROL_CHANNELS | AF_CONTROL_SET: + // Reinit must be called after this function has been called + + // Sanity check + if(((int*)arg)[0] <= 0 || ((int*)arg)[0] > AF_NCH){ + mp_msg(MSGT_AFILTER, MSGL_ERR, "[channels] The number of output channels must be" + " between 1 and %i. Current value is %i\n",AF_NCH,((int*)arg)[0]); + return AF_ERROR; + } + + af->data->nch=((int*)arg)[0]; + if(!s->router) + mp_msg(MSGT_AFILTER, MSGL_V, "[channels] Changing number of channels" + " to %i\n",af->data->nch); + return AF_OK; + case AF_CONTROL_CHANNELS | AF_CONTROL_GET: + *(int*)arg = af->data->nch; + return AF_OK; + case AF_CONTROL_CHANNELS_ROUTING | AF_CONTROL_SET:{ + int ch = ((af_control_ext_t*)arg)->ch; + int* route = ((af_control_ext_t*)arg)->arg; + s->route[ch][FR] = route[FR]; + s->route[ch][TO] = route[TO]; + return AF_OK; + } + case AF_CONTROL_CHANNELS_ROUTING | AF_CONTROL_GET:{ + int ch = ((af_control_ext_t*)arg)->ch; + int* route = ((af_control_ext_t*)arg)->arg; + route[FR] = s->route[ch][FR]; + route[TO] = s->route[ch][TO]; + return AF_OK; + } + case AF_CONTROL_CHANNELS_NR | AF_CONTROL_SET: + s->nr = *(int*)arg; + return AF_OK; + case AF_CONTROL_CHANNELS_NR | AF_CONTROL_GET: + *(int*)arg = s->nr; + return AF_OK; + case AF_CONTROL_CHANNELS_ROUTER | AF_CONTROL_SET: + s->router = *(int*)arg; + return AF_OK; + case AF_CONTROL_CHANNELS_ROUTER | AF_CONTROL_GET: + *(int*)arg = s->router; + return AF_OK; + } + return AF_UNKNOWN; +} + +// Deallocate memory +static void uninit(struct af_instance* af) +{ + free(af->setup); + if (af->data) + free(af->data->audio); + free(af->data); +} + +// Filter data through filter +static struct mp_audio* play(struct af_instance* af, struct mp_audio* data) +{ + struct mp_audio* c = data; // Current working data + struct mp_audio* l = af->data; // Local data + af_channels_t* s = af->setup; + int i; + + if(AF_OK != RESIZE_LOCAL_BUFFER(af,data)) + return NULL; + + // Reset unused channels + memset(l->audio,0,c->len / c->nch * l->nch); + + if(AF_OK == check_routes(s,c->nch,l->nch)) + for(i=0;i<s->nr;i++) + copy(c->audio,l->audio,c->nch,s->route[i][FR], + l->nch,s->route[i][TO],c->len,c->bps); + + // Set output data + c->audio = l->audio; + c->len = c->len / c->nch * l->nch; + c->nch = l->nch; + + return c; +} + +// Allocate memory and set function pointers +static int af_open(struct af_instance* af){ + af->control=control; + af->uninit=uninit; + af->play=play; + af->mul=1; + af->data=calloc(1,sizeof(struct mp_audio)); + af->setup=calloc(1,sizeof(af_channels_t)); + if((af->data == NULL) || (af->setup == NULL)) + return AF_ERROR; + return AF_OK; +} + +// Description of this filter +struct af_info af_info_channels = { + "Insert or remove channels", + "channels", + "Anders", + "", + AF_FLAGS_REENTRANT, + af_open +}; diff --git a/audio/filter/af_delay.c b/audio/filter/af_delay.c new file mode 100644 index 0000000000..ce8d71980b --- /dev/null +++ b/audio/filter/af_delay.c @@ -0,0 +1,200 @@ +/* + * This audio filter delays the output signal for the different + * channels and can be used for simple position panning. + * An extension for this filter would be a reverb. + * + * This file is part of MPlayer. + * + * MPlayer 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. + * + * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <inttypes.h> + +#include "af.h" + +#define L 65536 + +#define UPDATEQI(qi) qi=(qi+1)&(L-1) + +// Data for specific instances of this filter +typedef struct af_delay_s +{ + void* q[AF_NCH]; // Circular queues used for delaying audio signal + int wi[AF_NCH]; // Write index + int ri; // Read index + float d[AF_NCH]; // Delay [ms] +}af_delay_t; + +// Initialization and runtime control +static int control(struct af_instance* af, int cmd, void* arg) +{ + af_delay_t* s = af->setup; + switch(cmd){ + case AF_CONTROL_REINIT:{ + int i; + + // Free prevous delay queues + for(i=0;i<af->data->nch;i++) + free(s->q[i]); + + af->data->rate = ((struct mp_audio*)arg)->rate; + af->data->nch = ((struct mp_audio*)arg)->nch; + af->data->format = ((struct mp_audio*)arg)->format; + af->data->bps = ((struct mp_audio*)arg)->bps; + + // Allocate new delay queues + for(i=0;i<af->data->nch;i++){ + s->q[i] = calloc(L,af->data->bps); + if(NULL == s->q[i]) + mp_msg(MSGT_AFILTER, MSGL_FATAL, "[delay] Out of memory\n"); + } + + return control(af,AF_CONTROL_DELAY_LEN | AF_CONTROL_SET,s->d); + } + case AF_CONTROL_COMMAND_LINE:{ + int n = 1; + int i = 0; + char* cl = arg; + while(n && i < AF_NCH ){ + sscanf(cl,"%f:%n",&s->d[i],&n); + if(n==0 || cl[n-1] == '\0') + break; + cl=&cl[n]; + i++; + } + return AF_OK; + } + case AF_CONTROL_DELAY_LEN | AF_CONTROL_SET:{ + int i; + if(AF_OK != af_from_ms(AF_NCH, arg, s->wi, af->data->rate, 0.0, 1000.0)) + return AF_ERROR; + s->ri = 0; + for(i=0;i<AF_NCH;i++){ + mp_msg(MSGT_AFILTER, MSGL_DBG2, "[delay] Channel %i delayed by %0.3fms\n", + i,clamp(s->d[i],0.0,1000.0)); + mp_msg(MSGT_AFILTER, MSGL_DBG3, "[delay] Channel %i delayed by %i samples\n", + i,s->wi[i]); + } + return AF_OK; + } + case AF_CONTROL_DELAY_LEN | AF_CONTROL_GET:{ + int i; + for(i=0;i<AF_NCH;i++){ + if(s->ri > s->wi[i]) + s->wi[i] = L - (s->ri - s->wi[i]); + else + s->wi[i] = s->wi[i] - s->ri; + } + return af_to_ms(AF_NCH, s->wi, arg, af->data->rate); + } + } + return AF_UNKNOWN; +} + +// Deallocate memory +static void uninit(struct af_instance* af) +{ + int i; + + free(af->data); + for(i=0;i<AF_NCH;i++) + free(((af_delay_t*)(af->setup))->q[i]); + free(af->setup); +} + +// Filter data through filter +static struct mp_audio* play(struct af_instance* af, struct mp_audio* data) +{ + struct mp_audio* c = data; // Current working data + af_delay_t* s = af->setup; // Setup for this instance + int nch = c->nch; // Number of channels + int len = c->len/c->bps; // Number of sample in data chunk + int ri = 0; + int ch,i; + for(ch=0;ch<nch;ch++){ + switch(c->bps){ + case 1:{ + int8_t* a = c->audio; + int8_t* q = s->q[ch]; + int wi = s->wi[ch]; + ri = s->ri; + for(i=ch;i<len;i+=nch){ + q[wi] = a[i]; + a[i] = q[ri]; + UPDATEQI(wi); + UPDATEQI(ri); + } + s->wi[ch] = wi; + break; + } + case 2:{ + int16_t* a = c->audio; + int16_t* q = s->q[ch]; + int wi = s->wi[ch]; + ri = s->ri; + for(i=ch;i<len;i+=nch){ + q[wi] = a[i]; + a[i] = q[ri]; + UPDATEQI(wi); + UPDATEQI(ri); + } + s->wi[ch] = wi; + break; + } + case 4:{ + int32_t* a = c->audio; + int32_t* q = s->q[ch]; + int wi = s->wi[ch]; + ri = s->ri; + for(i=ch;i<len;i+=nch){ + q[wi] = a[i]; + a[i] = q[ri]; + UPDATEQI(wi); + UPDATEQI(ri); + } + s->wi[ch] = wi; + break; + } + } + } + s->ri = ri; + return c; +} + +// Allocate memory and set function pointers +static int af_open(struct af_instance* af){ + af->control=control; + af->uninit=uninit; + af->play=play; + af->mul=1; + af->data=calloc(1,sizeof(struct mp_audio)); + af->setup=calloc(1,sizeof(af_delay_t)); + if(af->data == NULL || af->setup == NULL) + return AF_ERROR; + return AF_OK; +} + +// Description of this filter +struct af_info af_info_delay = { + "Delay audio filter", + "delay", + "Anders", + "", + AF_FLAGS_REENTRANT, + af_open +}; diff --git a/audio/filter/af_dummy.c b/audio/filter/af_dummy.c new file mode 100644 index 0000000000..29a5b3d4b8 --- /dev/null +++ b/audio/filter/af_dummy.c @@ -0,0 +1,76 @@ +/* + * The name speaks for itself. This filter is a dummy and will + * not blow up regardless of what you do with it. + * + * This file is part of MPlayer. + * + * MPlayer 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. + * + * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "af.h" + +// Initialization and runtime control +static int control(struct af_instance* af, int cmd, void* arg) +{ + switch(cmd){ + case AF_CONTROL_REINIT: + memcpy(af->data,(struct mp_audio*)arg,sizeof(struct mp_audio)); + mp_msg(MSGT_AFILTER, MSGL_V, "[dummy] Was reinitialized: %iHz/%ich/%s\n", + af->data->rate,af->data->nch,af_fmt2str_short(af->data->format)); + return AF_OK; + } + return AF_UNKNOWN; +} + +// Deallocate memory +static void uninit(struct af_instance* af) +{ + free(af->data); +} + +// Filter data through filter +static struct mp_audio* play(struct af_instance* af, struct mp_audio* data) +{ + // Do something necessary to get rid of annoying warning during compile + if(!af) + mp_msg(MSGT_AFILTER, MSGL_ERR, "EEEK: Argument af == NULL in af_dummy.c play()."); + return data; +} + +// Allocate memory and set function pointers +static int af_open(struct af_instance* af){ + af->control=control; + af->uninit=uninit; + af->play=play; + af->mul=1; + af->data=malloc(sizeof(struct mp_audio)); + if(af->data == NULL) + return AF_ERROR; + return AF_OK; +} + +// Description of this filter +struct af_info af_info_dummy = { + "dummy", + "dummy", + "Anders", + "", + AF_FLAGS_REENTRANT, + af_open +}; diff --git a/audio/filter/af_equalizer.c b/audio/filter/af_equalizer.c new file mode 100644 index 0000000000..c488ffaeaf --- /dev/null +++ b/audio/filter/af_equalizer.c @@ -0,0 +1,248 @@ +/* + * Equalizer filter, implementation of a 10 band time domain graphic + * equalizer using IIR filters. The IIR filters are implemented using a + * Direct Form II approach, but has been modified (b1 == 0 always) to + * save computation. + * + * Copyright (C) 2001 Anders Johansson ajh@atri.curtin.edu.au + * + * This file is part of MPlayer. + * + * MPlayer 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. + * + * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <stdio.h> +#include <stdlib.h> + +#include <inttypes.h> +#include <math.h> + +#include "af.h" + +#define L 2 // Storage for filter taps +#define KM 10 // Max number of bands + +#define Q 1.2247449 /* Q value for band-pass filters 1.2247=(3/2)^(1/2) + gives 4dB suppression @ Fc*2 and Fc/2 */ + +/* Center frequencies for band-pass filters + The different frequency bands are: + nr. center frequency + 0 31.25 Hz + 1 62.50 Hz + 2 125.0 Hz + 3 250.0 Hz + 4 500.0 Hz + 5 1.000 kHz + 6 2.000 kHz + 7 4.000 kHz + 8 8.000 kHz + 9 16.00 kHz +*/ +#define CF {31.25,62.5,125,250,500,1000,2000,4000,8000,16000} + +// Maximum and minimum gain for the bands +#define G_MAX +12.0 +#define G_MIN -12.0 + +// Data for specific instances of this filter +typedef struct af_equalizer_s +{ + float a[KM][L]; // A weights + float b[KM][L]; // B weights + float wq[AF_NCH][KM][L]; // Circular buffer for W data + float g[AF_NCH][KM]; // Gain factor for each channel and band + int K; // Number of used eq bands + int channels; // Number of channels + float gain_factor; // applied at output to avoid clipping +} af_equalizer_t; + +// 2nd order Band-pass Filter design +static void bp2(float* a, float* b, float fc, float q){ + double th= 2.0 * M_PI * fc; + double C = (1.0 - tan(th*q/2.0))/(1.0 + tan(th*q/2.0)); + + a[0] = (1.0 + C) * cos(th); + a[1] = -1 * C; + + b[0] = (1.0 - C)/2.0; + b[1] = -1.0050; +} + +// Initialization and runtime control +static int control(struct af_instance* af, int cmd, void* arg) +{ + af_equalizer_t* s = (af_equalizer_t*)af->setup; + + switch(cmd){ + case AF_CONTROL_REINIT:{ + int k =0, i =0; + float F[KM] = CF; + + s->gain_factor=0.0; + + // Sanity check + if(!arg) return AF_ERROR; + + af->data->rate = ((struct mp_audio*)arg)->rate; + af->data->nch = ((struct mp_audio*)arg)->nch; + af->data->format = AF_FORMAT_FLOAT_NE; + af->data->bps = 4; + + // Calculate number of active filters + s->K=KM; + while(F[s->K-1] > (float)af->data->rate/2.2) + s->K--; + + if(s->K != KM) + mp_msg(MSGT_AFILTER, MSGL_INFO, "[equalizer] Limiting the number of filters to" + " %i due to low sample rate.\n",s->K); + + // Generate filter taps + for(k=0;k<s->K;k++) + bp2(s->a[k],s->b[k],F[k]/((float)af->data->rate),Q); + + // Calculate how much this plugin adds to the overall time delay + af->delay = 2 * af->data->nch * af->data->bps; + + // Calculate gain factor to prevent clipping at output + for(k=0;k<AF_NCH;k++) + { + for(i=0;i<KM;i++) + { + if(s->gain_factor < s->g[k][i]) s->gain_factor=s->g[k][i]; + } + } + + s->gain_factor=log10(s->gain_factor + 1.0) * 20.0; + + if(s->gain_factor > 0.0) + { + s->gain_factor=0.1+(s->gain_factor/12.0); + }else{ + s->gain_factor=1; + } + + return af_test_output(af,arg); + } + case AF_CONTROL_COMMAND_LINE:{ + float g[10]={0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0}; + int i,j; + sscanf((char*)arg,"%f:%f:%f:%f:%f:%f:%f:%f:%f:%f", &g[0], &g[1], + &g[2], &g[3], &g[4], &g[5], &g[6], &g[7], &g[8] ,&g[9]); + for(i=0;i<AF_NCH;i++){ + for(j=0;j<KM;j++){ + ((af_equalizer_t*)af->setup)->g[i][j] = + pow(10.0,clamp(g[j],G_MIN,G_MAX)/20.0)-1.0; + } + } + return AF_OK; + } + case AF_CONTROL_EQUALIZER_GAIN | AF_CONTROL_SET:{ + float* gain = ((af_control_ext_t*)arg)->arg; + int ch = ((af_control_ext_t*)arg)->ch; + int k; + if(ch >= AF_NCH || ch < 0) + return AF_ERROR; + + for(k = 0 ; k<KM ; k++) + s->g[ch][k] = pow(10.0,clamp(gain[k],G_MIN,G_MAX)/20.0)-1.0; + + return AF_OK; + } + case AF_CONTROL_EQUALIZER_GAIN | AF_CONTROL_GET:{ + float* gain = ((af_control_ext_t*)arg)->arg; + int ch = ((af_control_ext_t*)arg)->ch; + int k; + if(ch >= AF_NCH || ch < 0) + return AF_ERROR; + + for(k = 0 ; k<KM ; k++) + gain[k] = log10(s->g[ch][k]+1.0) * 20.0; + + return AF_OK; + } + } + return AF_UNKNOWN; +} + +// Deallocate memory +static void uninit(struct af_instance* af) +{ + free(af->data); + free(af->setup); +} + +// Filter data through filter +static struct mp_audio* play(struct af_instance* af, struct mp_audio* data) +{ + struct mp_audio* c = data; // Current working data + af_equalizer_t* s = (af_equalizer_t*)af->setup; // Setup + uint32_t ci = af->data->nch; // Index for channels + uint32_t nch = af->data->nch; // Number of channels + + while(ci--){ + float* g = s->g[ci]; // Gain factor + float* in = ((float*)c->audio)+ci; + float* out = ((float*)c->audio)+ci; + float* end = in + c->len/4; // Block loop end + + while(in < end){ + register int k = 0; // Frequency band index + register float yt = *in; // Current input sample + in+=nch; + + // Run the filters + for(;k<s->K;k++){ + // Pointer to circular buffer wq + register float* wq = s->wq[ci][k]; + // Calculate output from AR part of current filter + register float w=yt*s->b[k][0] + wq[0]*s->a[k][0] + wq[1]*s->a[k][1]; + // Calculate output form MA part of current filter + yt+=(w + wq[1]*s->b[k][1])*g[k]; + // Update circular buffer + wq[1] = wq[0]; + wq[0] = w; + } + // Calculate output + *out=yt*s->gain_factor; + out+=nch; + } + } + return c; +} + +// Allocate memory and set function pointers +static int af_open(struct af_instance* af){ + af->control=control; + af->uninit=uninit; + af->play=play; + af->mul=1; + af->data=calloc(1,sizeof(struct mp_audio)); + af->setup=calloc(1,sizeof(af_equalizer_t)); + if(af->data == NULL || af->setup == NULL) + return AF_ERROR; + return AF_OK; +} + +// Description of this filter +struct af_info af_info_equalizer = { + "Equalizer audio filter", + "equalizer", + "Anders", + "", + AF_FLAGS_NOT_REENTRANT, + af_open +}; diff --git a/audio/filter/af_export.c b/audio/filter/af_export.c new file mode 100644 index 0000000000..441ec31ac3 --- /dev/null +++ b/audio/filter/af_export.c @@ -0,0 +1,273 @@ +/* + * This audio filter exports the incoming signal to other processes + * using memory mapping. The memory mapped area contains a header: + * int nch, + * int size, + * unsigned long long counter (updated every time the contents of + * the area changes), + * the rest is payload (non-interleaved). + * + * This file is part of MPlayer. + * + * MPlayer 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. + * + * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <inttypes.h> +#include <unistd.h> +#include "config.h" + +#include <sys/types.h> +#include <sys/mman.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + +#include "af.h" +#include "path.h" + +#define DEF_SZ 512 // default buffer size (in samples) +#define SHARED_FILE "mpv-af_export" /* default file name + (relative to ~/.mpv/ */ + +#define SIZE_HEADER (2 * sizeof(int) + sizeof(unsigned long long)) + +// Data for specific instances of this filter +typedef struct af_export_s +{ + unsigned long long count; // Used for sync + void* buf[AF_NCH]; // Buffers for storing the data before it is exported + int sz; // Size of buffer in samples + int wi; // Write index + int fd; // File descriptor to shared memory area + char* filename; // File to export data + uint8_t *mmap_area; // MMap shared area +} af_export_t; + + +/* Initialization and runtime control + af audio filter instance + cmd control command + arg argument +*/ +static int control(struct af_instance* af, int cmd, void* arg) +{ + af_export_t* s = af->setup; + switch (cmd){ + case AF_CONTROL_REINIT:{ + int i=0; + int mapsize; + + // Free previous buffers + if (s->buf) + free(s->buf[0]); + + // unmap previous area + if(s->mmap_area) + munmap(s->mmap_area, SIZE_HEADER + (af->data->bps*s->sz*af->data->nch)); + // close previous file descriptor + if(s->fd) + close(s->fd); + + // Accept only int16_t as input format (which sucks) + af->data->rate = ((struct mp_audio*)arg)->rate; + af->data->nch = ((struct mp_audio*)arg)->nch; + af->data->format = AF_FORMAT_S16_NE; + af->data->bps = 2; + + // If buffer length isn't set, set it to the default value + if(s->sz == 0) + s->sz = DEF_SZ; + + // Allocate new buffers (as one continuous block) + s->buf[0] = calloc(s->sz*af->data->nch, af->data->bps); + if(NULL == s->buf[0]) + mp_msg(MSGT_AFILTER, MSGL_FATAL, "[export] Out of memory\n"); + for(i = 1; i < af->data->nch; i++) + s->buf[i] = (uint8_t *)s->buf[0] + i*s->sz*af->data->bps; + + // Init memory mapping + s->fd = open(s->filename, O_RDWR | O_CREAT | O_TRUNC, 0640); + mp_msg(MSGT_AFILTER, MSGL_INFO, "[export] Exporting to file: %s\n", s->filename); + if(s->fd < 0) + mp_msg(MSGT_AFILTER, MSGL_FATAL, "[export] Could not open/create file: %s\n", + s->filename); + + // header + buffer + mapsize = (SIZE_HEADER + (af->data->bps * s->sz * af->data->nch)); + + // grow file to needed size + for(i = 0; i < mapsize; i++){ + char null = 0; + write(s->fd, (void*) &null, 1); + } + + // mmap size + s->mmap_area = mmap(0, mapsize, PROT_READ|PROT_WRITE,MAP_SHARED, s->fd, 0); + if(s->mmap_area == NULL) + mp_msg(MSGT_AFILTER, MSGL_FATAL, "[export] Could not mmap file %s\n", s->filename); + mp_msg(MSGT_AFILTER, MSGL_INFO, "[export] Memory mapped to file: %s (%p)\n", + s->filename, s->mmap_area); + + // Initialize header + *((int*)s->mmap_area) = af->data->nch; + *((int*)s->mmap_area + 1) = s->sz * af->data->bps * af->data->nch; + msync(s->mmap_area, mapsize, MS_ASYNC); + + // Use test_output to return FALSE if necessary + return af_test_output(af, (struct mp_audio*)arg); + } + case AF_CONTROL_COMMAND_LINE:{ + int i=0; + char *str = arg; + + if (!str){ + free(s->filename); + + s->filename = get_path(SHARED_FILE); + return AF_OK; + } + + while((str[i]) && (str[i] != ':')) + i++; + + free(s->filename); + + s->filename = calloc(i + 1, 1); + memcpy(s->filename, str, i); + s->filename[i] = 0; + + sscanf(str + i + 1, "%d", &(s->sz)); + + return af->control(af, AF_CONTROL_EXPORT_SZ | AF_CONTROL_SET, &s->sz); + } + case AF_CONTROL_EXPORT_SZ | AF_CONTROL_SET: + s->sz = * (int *) arg; + if((s->sz <= 0) || (s->sz > 2048)) + mp_msg(MSGT_AFILTER, MSGL_ERR, "[export] Buffer size must be between" + " 1 and 2048\n" ); + + return AF_OK; + case AF_CONTROL_EXPORT_SZ | AF_CONTROL_GET: + *(int*) arg = s->sz; + return AF_OK; + + } + return AF_UNKNOWN; +} + +/* Free allocated memory and clean up other stuff too. + af audio filter instance +*/ +static void uninit( struct af_instance* af ) +{ + free(af->data); + af->data = NULL; + + if(af->setup){ + af_export_t* s = af->setup; + if (s->buf) + free(s->buf[0]); + + // Free mmaped area + if(s->mmap_area) + munmap(s->mmap_area, sizeof(af_export_t)); + + if(s->fd > -1) + close(s->fd); + + free(s->filename); + + free(af->setup); + af->setup = NULL; + } +} + +/* Filter data through filter + af audio filter instance + data audio data +*/ +static struct mp_audio* play( struct af_instance* af, struct mp_audio* data ) +{ + struct mp_audio* c = data; // Current working data + af_export_t* s = af->setup; // Setup for this instance + int16_t* a = c->audio; // Incomming sound + int nch = c->nch; // Number of channels + int len = c->len/c->bps; // Number of sample in data chunk + int sz = s->sz; // buffer size (in samples) + int flag = 0; // Set to 1 if buffer is filled + + int ch, i; + + // Fill all buffers + for(ch = 0; ch < nch; ch++){ + int wi = s->wi; // Reset write index + int16_t* b = s->buf[ch]; // Current buffer + + // Copy data to export buffers + for(i = ch; i < len; i += nch){ + b[wi++] = a[i]; + if(wi >= sz){ // Don't write outside the end of the buffer + flag = 1; + break; + } + } + s->wi = wi % s->sz; + } + + // Export buffer to mmaped area + if(flag){ + // update buffer in mapped area + memcpy(s->mmap_area + SIZE_HEADER, s->buf[0], sz * c->bps * nch); + s->count++; // increment counter (to sync) + memcpy(s->mmap_area + SIZE_HEADER - sizeof(s->count), + &(s->count), sizeof(s->count)); + } + + // We don't modify data, just export it + return data; +} + +/* Allocate memory and set function pointers + af audio filter instance + returns AF_OK or AF_ERROR +*/ +static int af_open( struct af_instance* af ) +{ + af->control = control; + af->uninit = uninit; + af->play = play; + af->mul=1; + af->data = calloc(1, sizeof(struct mp_audio)); + af->setup = calloc(1, sizeof(af_export_t)); + if((af->data == NULL) || (af->setup == NULL)) + return AF_ERROR; + + ((af_export_t *)af->setup)->filename = get_path(SHARED_FILE); + + return AF_OK; +} + +// Description of this filter +struct af_info af_info_export = { + "Sound export filter", + "export", + "Anders; Gustavo Sverzut Barbieri <gustavo.barbieri@ic.unicamp.br>", + "", + AF_FLAGS_REENTRANT, + af_open +}; diff --git a/audio/filter/af_extrastereo.c b/audio/filter/af_extrastereo.c new file mode 100644 index 0000000000..0f7fe36861 --- /dev/null +++ b/audio/filter/af_extrastereo.c @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2004 Alex Beregszaszi & Pierre Lombard + * + * This file is part of MPlayer. + * + * MPlayer 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. + * + * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <inttypes.h> +#include <math.h> +#include <limits.h> + +#include "af.h" + +// Data for specific instances of this filter +typedef struct af_extrastereo_s +{ + float mul; +}af_extrastereo_t; + +static struct mp_audio* play_s16(struct af_instance* af, struct mp_audio* data); +static struct mp_audio* play_float(struct af_instance* af, struct mp_audio* data); + +// Initialization and runtime control +static int control(struct af_instance* af, int cmd, void* arg) +{ + af_extrastereo_t* s = (af_extrastereo_t*)af->setup; + + switch(cmd){ + case AF_CONTROL_REINIT:{ + // Sanity check + if(!arg) return AF_ERROR; + + af->data->rate = ((struct mp_audio*)arg)->rate; + af->data->nch = 2; + if (((struct mp_audio*)arg)->format == AF_FORMAT_FLOAT_NE) + { + af->data->format = AF_FORMAT_FLOAT_NE; + af->data->bps = 4; + af->play = play_float; + }// else + { + af->data->format = AF_FORMAT_S16_NE; + af->data->bps = 2; + af->play = play_s16; + } + + return af_test_output(af,(struct mp_audio*)arg); + } + case AF_CONTROL_COMMAND_LINE:{ + float f; + sscanf((char*)arg,"%f", &f); + s->mul = f; + return AF_OK; + } + case AF_CONTROL_ES_MUL | AF_CONTROL_SET: + s->mul = *(float*)arg; + return AF_OK; + case AF_CONTROL_ES_MUL | AF_CONTROL_GET: + *(float*)arg = s->mul; + return AF_OK; + } + return AF_UNKNOWN; +} + +// Deallocate memory +static void uninit(struct af_instance* af) +{ + free(af->data); + free(af->setup); +} + +// Filter data through filter +static struct mp_audio* play_s16(struct af_instance* af, struct mp_audio* data) +{ + af_extrastereo_t *s = af->setup; + register int i = 0; + int16_t *a = (int16_t*)data->audio; // Audio data + int len = data->len/2; // Number of samples + int avg, l, r; + + for (i = 0; i < len; i+=2) + { + avg = (a[i] + a[i + 1]) / 2; + + l = avg + (int)(s->mul * (a[i] - avg)); + r = avg + (int)(s->mul * (a[i + 1] - avg)); + + a[i] = clamp(l, SHRT_MIN, SHRT_MAX); + a[i + 1] = clamp(r, SHRT_MIN, SHRT_MAX); + } + + return data; +} + +static struct mp_audio* play_float(struct af_instance* af, struct mp_audio* data) +{ + af_extrastereo_t *s = af->setup; + register int i = 0; + float *a = (float*)data->audio; // Audio data + int len = data->len/4; // Number of samples + float avg, l, r; + + for (i = 0; i < len; i+=2) + { + avg = (a[i] + a[i + 1]) / 2; + + l = avg + (s->mul * (a[i] - avg)); + r = avg + (s->mul * (a[i + 1] - avg)); + + a[i] = af_softclip(l); + a[i + 1] = af_softclip(r); + } + + return data; +} + +// Allocate memory and set function pointers +static int af_open(struct af_instance* af){ + af->control=control; + af->uninit=uninit; + af->play=play_s16; + af->mul=1; + af->data=calloc(1,sizeof(struct mp_audio)); + af->setup=calloc(1,sizeof(af_extrastereo_t)); + if(af->data == NULL || af->setup == NULL) + return AF_ERROR; + + ((af_extrastereo_t*)af->setup)->mul = 2.5; + return AF_OK; +} + +// Description of this filter +struct af_info af_info_extrastereo = { + "Increase difference between audio channels", + "extrastereo", + "Alex Beregszaszi & Pierre Lombard", + "", + AF_FLAGS_NOT_REENTRANT, + af_open +}; diff --git a/audio/filter/af_format.c b/audio/filter/af_format.c new file mode 100644 index 0000000000..4ac9caaa85 --- /dev/null +++ b/audio/filter/af_format.c @@ -0,0 +1,519 @@ +/* + * This audio filter changes the format of a data block. Valid + * formats are: AFMT_U8, AFMT_S8, AFMT_S16_LE, AFMT_S16_BE + * AFMT_U16_LE, AFMT_U16_BE, AFMT_S32_LE and AFMT_S32_BE. + * + * This file is part of MPlayer. + * + * MPlayer 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. + * + * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <inttypes.h> +#include <limits.h> +#include <math.h> +#include <sys/types.h> + +#include "config.h" +#include "af.h" +#include "mpbswap.h" + +/* Functions used by play to convert the input audio to the correct + format */ + +/* The below includes retrieves functions for converting to and from + ulaw and alaw */ +#include "af_format_ulaw.h" +#include "af_format_alaw.h" + +// Switch endianness +static void endian(void* in, void* out, int len, int bps); +// From signed to unsigned and the other way +static void si2us(void* data, int len, int bps); +// Change the number of bits per sample +static void change_bps(void* in, void* out, int len, int inbps, int outbps); +// From float to int signed +static void float2int(float* in, void* out, int len, int bps); +// From signed int to float +static void int2float(void* in, float* out, int len, int bps); + +static struct mp_audio* play(struct af_instance* af, struct mp_audio* data); +static struct mp_audio* play_swapendian(struct af_instance* af, struct mp_audio* data); +static struct mp_audio* play_float_s16(struct af_instance* af, struct mp_audio* data); +static struct mp_audio* play_s16_float(struct af_instance* af, struct mp_audio* data); + +// Helper functions to check sanity for input arguments + +// Sanity check for bytes per sample +static int check_bps(int bps) +{ + if(bps != 4 && bps != 3 && bps != 2 && bps != 1){ + mp_msg(MSGT_AFILTER, MSGL_ERR, "[format] The number of bytes per sample" + " must be 1, 2, 3 or 4. Current value is %i \n",bps); + return AF_ERROR; + } + return AF_OK; +} + +// Check for unsupported formats +static int check_format(int format) +{ + char buf[256]; + switch(format & AF_FORMAT_SPECIAL_MASK){ + case(AF_FORMAT_IMA_ADPCM): + case(AF_FORMAT_MPEG2): + case(AF_FORMAT_AC3): + mp_msg(MSGT_AFILTER, MSGL_ERR, "[format] Sample format %s not yet supported \n", + af_fmt2str(format,buf,256)); + return AF_ERROR; + } + return AF_OK; +} + +// Initialization and runtime control +static int control(struct af_instance* af, int cmd, void* arg) +{ + switch(cmd){ + case AF_CONTROL_REINIT:{ + char buf1[256]; + char buf2[256]; + struct mp_audio *data = arg; + + // Make sure this filter isn't redundant + if(af->data->format == data->format && + af->data->bps == data->bps) + return AF_DETACH; + + // Allow trivial AC3-endianness conversion + if (!AF_FORMAT_IS_AC3(af->data->format) || !AF_FORMAT_IS_AC3(data->format)) + // Check for errors in configuration + if((AF_OK != check_bps(data->bps)) || + (AF_OK != check_format(data->format)) || + (AF_OK != check_bps(af->data->bps)) || + (AF_OK != check_format(af->data->format))) + return AF_ERROR; + + mp_msg(MSGT_AFILTER, MSGL_V, "[format] Changing sample format from %s to %s\n", + af_fmt2str(data->format,buf1,256), + af_fmt2str(af->data->format,buf2,256)); + + af->data->rate = data->rate; + af->data->nch = data->nch; + af->mul = (double)af->data->bps / data->bps; + + af->play = play; // set default + + // look whether only endianness differences are there + if ((af->data->format & ~AF_FORMAT_END_MASK) == + (data->format & ~AF_FORMAT_END_MASK)) + { + mp_msg(MSGT_AFILTER, MSGL_V, "[format] Accelerated endianness conversion only\n"); + af->play = play_swapendian; + } + if ((data->format == AF_FORMAT_FLOAT_NE) && + (af->data->format == AF_FORMAT_S16_NE)) + { + mp_msg(MSGT_AFILTER, MSGL_V, "[format] Accelerated %s to %s conversion\n", + af_fmt2str(data->format,buf1,256), + af_fmt2str(af->data->format,buf2,256)); + af->play = play_float_s16; + } + if ((data->format == AF_FORMAT_S16_NE) && + (af->data->format == AF_FORMAT_FLOAT_NE)) + { + mp_msg(MSGT_AFILTER, MSGL_V, "[format] Accelerated %s to %s conversion\n", + af_fmt2str(data->format,buf1,256), + af_fmt2str(af->data->format,buf2,256)); + af->play = play_s16_float; + } + return AF_OK; + } + case AF_CONTROL_COMMAND_LINE:{ + int format = af_str2fmt_short(bstr0(arg)); + if (format == -1) { + mp_msg(MSGT_AFILTER, MSGL_ERR, "[format] %s is not a valid format\n", (char *)arg); + return AF_ERROR; + } + if(AF_OK != af->control(af,AF_CONTROL_FORMAT_FMT | AF_CONTROL_SET,&format)) + return AF_ERROR; + return AF_OK; + } + case AF_CONTROL_FORMAT_FMT | AF_CONTROL_SET:{ + // Check for errors in configuration + if(!AF_FORMAT_IS_AC3(*(int*)arg) && AF_OK != check_format(*(int*)arg)) + return AF_ERROR; + + af->data->format = *(int*)arg; + af->data->bps = af_fmt2bits(af->data->format)/8; + + return AF_OK; + } + } + return AF_UNKNOWN; +} + +// Deallocate memory +static void uninit(struct af_instance* af) +{ + if (af->data) + free(af->data->audio); + free(af->data); + af->setup = 0; +} + +static struct mp_audio* play_swapendian(struct af_instance* af, struct mp_audio* data) +{ + struct mp_audio* l = af->data; // Local data + struct mp_audio* c = data; // Current working data + int len = c->len/c->bps; // Length in samples of current audio block + + if(AF_OK != RESIZE_LOCAL_BUFFER(af,data)) + return NULL; + + endian(c->audio,l->audio,len,c->bps); + + c->audio = l->audio; + c->format = l->format; + + return c; +} + +static struct mp_audio* play_float_s16(struct af_instance* af, struct mp_audio* data) +{ + struct mp_audio* l = af->data; // Local data + struct mp_audio* c = data; // Current working data + int len = c->len/4; // Length in samples of current audio block + + if(AF_OK != RESIZE_LOCAL_BUFFER(af,data)) + return NULL; + + float2int(c->audio, l->audio, len, 2); + + c->audio = l->audio; + c->len = len*2; + c->bps = 2; + c->format = l->format; + + return c; +} + +static struct mp_audio* play_s16_float(struct af_instance* af, struct mp_audio* data) +{ + struct mp_audio* l = af->data; // Local data + struct mp_audio* c = data; // Current working data + int len = c->len/2; // Length in samples of current audio block + + if(AF_OK != RESIZE_LOCAL_BUFFER(af,data)) + return NULL; + + int2float(c->audio, l->audio, len, 2); + + c->audio = l->audio; + c->len = len*4; + c->bps = 4; + c->format = l->format; + + return c; +} + +// Filter data through filter +static struct mp_audio* play(struct af_instance* af, struct mp_audio* data) +{ + struct mp_audio* l = af->data; // Local data + struct mp_audio* c = data; // Current working data + int len = c->len/c->bps; // Length in samples of current audio block + + if(AF_OK != RESIZE_LOCAL_BUFFER(af,data)) + return NULL; + + // Change to cpu native endian format + if((c->format&AF_FORMAT_END_MASK)!=AF_FORMAT_NE) + endian(c->audio,c->audio,len,c->bps); + + // Conversion table + if((c->format & AF_FORMAT_SPECIAL_MASK) == AF_FORMAT_MU_LAW) { + from_ulaw(c->audio, l->audio, len, l->bps, l->format&AF_FORMAT_POINT_MASK); + if(AF_FORMAT_A_LAW == (l->format&AF_FORMAT_SPECIAL_MASK)) + to_ulaw(l->audio, l->audio, len, 1, AF_FORMAT_SI); + if((l->format&AF_FORMAT_SIGN_MASK) == AF_FORMAT_US) + si2us(l->audio,len,l->bps); + } else if((c->format & AF_FORMAT_SPECIAL_MASK) == AF_FORMAT_A_LAW) { + from_alaw(c->audio, l->audio, len, l->bps, l->format&AF_FORMAT_POINT_MASK); + if(AF_FORMAT_A_LAW == (l->format&AF_FORMAT_SPECIAL_MASK)) + to_alaw(l->audio, l->audio, len, 1, AF_FORMAT_SI); + if((l->format&AF_FORMAT_SIGN_MASK) == AF_FORMAT_US) + si2us(l->audio,len,l->bps); + } else if((c->format & AF_FORMAT_POINT_MASK) == AF_FORMAT_F) { + switch(l->format&AF_FORMAT_SPECIAL_MASK){ + case(AF_FORMAT_MU_LAW): + to_ulaw(c->audio, l->audio, len, c->bps, c->format&AF_FORMAT_POINT_MASK); + break; + case(AF_FORMAT_A_LAW): + to_alaw(c->audio, l->audio, len, c->bps, c->format&AF_FORMAT_POINT_MASK); + break; + default: + float2int(c->audio, l->audio, len, l->bps); + if((l->format&AF_FORMAT_SIGN_MASK) == AF_FORMAT_US) + si2us(l->audio,len,l->bps); + break; + } + } else { + // Input must be int + + // Change signed/unsigned + if((c->format&AF_FORMAT_SIGN_MASK) != (l->format&AF_FORMAT_SIGN_MASK)){ + si2us(c->audio,len,c->bps); + } + // Convert to special formats + switch(l->format&(AF_FORMAT_SPECIAL_MASK|AF_FORMAT_POINT_MASK)){ + case(AF_FORMAT_MU_LAW): + to_ulaw(c->audio, l->audio, len, c->bps, c->format&AF_FORMAT_POINT_MASK); + break; + case(AF_FORMAT_A_LAW): + to_alaw(c->audio, l->audio, len, c->bps, c->format&AF_FORMAT_POINT_MASK); + break; + case(AF_FORMAT_F): + int2float(c->audio, l->audio, len, c->bps); + break; + default: + // Change the number of bits + if(c->bps != l->bps) + change_bps(c->audio,l->audio,len,c->bps,l->bps); + else + memcpy(l->audio,c->audio,len*c->bps); + break; + } + } + + // Switch from cpu native endian to the correct endianness + if((l->format&AF_FORMAT_END_MASK)!=AF_FORMAT_NE) + endian(l->audio,l->audio,len,l->bps); + + // Set output data + c->audio = l->audio; + c->len = len*l->bps; + c->bps = l->bps; + c->format = l->format; + return c; +} + +// Allocate memory and set function pointers +static int af_open(struct af_instance* af){ + af->control=control; + af->uninit=uninit; + af->play=play; + af->mul=1; + af->data=calloc(1,sizeof(struct mp_audio)); + if(af->data == NULL) + return AF_ERROR; + return AF_OK; +} + +// Description of this filter +struct af_info af_info_format = { + "Sample format conversion", + "format", + "Anders", + "", + AF_FLAGS_REENTRANT, + af_open +}; + +static inline uint32_t load24bit(void* data, int pos) { +#if BYTE_ORDER == BIG_ENDIAN + return (((uint32_t)((uint8_t*)data)[3*pos])<<24) | + (((uint32_t)((uint8_t*)data)[3*pos+1])<<16) | + (((uint32_t)((uint8_t*)data)[3*pos+2])<<8); +#else + return (((uint32_t)((uint8_t*)data)[3*pos])<<8) | + (((uint32_t)((uint8_t*)data)[3*pos+1])<<16) | + (((uint32_t)((uint8_t*)data)[3*pos+2])<<24); +#endif +} + +static inline void store24bit(void* data, int pos, uint32_t expanded_value) { +#if BYTE_ORDER == BIG_ENDIAN + ((uint8_t*)data)[3*pos]=expanded_value>>24; + ((uint8_t*)data)[3*pos+1]=expanded_value>>16; + ((uint8_t*)data)[3*pos+2]=expanded_value>>8; +#else + ((uint8_t*)data)[3*pos]=expanded_value>>8; + ((uint8_t*)data)[3*pos+1]=expanded_value>>16; + ((uint8_t*)data)[3*pos+2]=expanded_value>>24; +#endif +} + +// Function implementations used by play +static void endian(void* in, void* out, int len, int bps) +{ + register int i; + switch(bps){ + case(2):{ + for(i=0;i<len;i++){ + ((uint16_t*)out)[i]=bswap_16(((uint16_t*)in)[i]); + } + break; + } + case(3):{ + register uint8_t s; + for(i=0;i<len;i++){ + s=((uint8_t*)in)[3*i]; + ((uint8_t*)out)[3*i]=((uint8_t*)in)[3*i+2]; + if (in != out) + ((uint8_t*)out)[3*i+1]=((uint8_t*)in)[3*i+1]; + ((uint8_t*)out)[3*i+2]=s; + } + break; + } + case(4):{ + for(i=0;i<len;i++){ + ((uint32_t*)out)[i]=bswap_32(((uint32_t*)in)[i]); + } + break; + } + } +} + +static void si2us(void* data, int len, int bps) +{ + register long i = -(len * bps); + register uint8_t *p = &((uint8_t *)data)[len * bps]; +#if AF_FORMAT_NE == AF_FORMAT_LE + p += bps - 1; +#endif + if (len <= 0) return; + do { + p[i] ^= 0x80; + } while (i += bps); +} + +static void change_bps(void* in, void* out, int len, int inbps, int outbps) +{ + register int i; + switch(inbps){ + case(1): + switch(outbps){ + case(2): + for(i=0;i<len;i++) + ((uint16_t*)out)[i]=((uint16_t)((uint8_t*)in)[i])<<8; + break; + case(3): + for(i=0;i<len;i++) + store24bit(out, i, ((uint32_t)((uint8_t*)in)[i])<<24); + break; + case(4): + for(i=0;i<len;i++) + ((uint32_t*)out)[i]=((uint32_t)((uint8_t*)in)[i])<<24; + break; + } + break; + case(2): + switch(outbps){ + case(1): + for(i=0;i<len;i++) + ((uint8_t*)out)[i]=(uint8_t)((((uint16_t*)in)[i])>>8); + break; + case(3): + for(i=0;i<len;i++) + store24bit(out, i, ((uint32_t)((uint16_t*)in)[i])<<16); + break; + case(4): + for(i=0;i<len;i++) + ((uint32_t*)out)[i]=((uint32_t)((uint16_t*)in)[i])<<16; + break; + } + break; + case(3): + switch(outbps){ + case(1): + for(i=0;i<len;i++) + ((uint8_t*)out)[i]=(uint8_t)(load24bit(in, i)>>24); + break; + case(2): + for(i=0;i<len;i++) + ((uint16_t*)out)[i]=(uint16_t)(load24bit(in, i)>>16); + break; + case(4): + for(i=0;i<len;i++) + ((uint32_t*)out)[i]=(uint32_t)load24bit(in, i); + break; + } + break; + case(4): + switch(outbps){ + case(1): + for(i=0;i<len;i++) + ((uint8_t*)out)[i]=(uint8_t)((((uint32_t*)in)[i])>>24); + break; + case(2): + for(i=0;i<len;i++) + ((uint16_t*)out)[i]=(uint16_t)((((uint32_t*)in)[i])>>16); + break; + case(3): + for(i=0;i<len;i++) + store24bit(out, i, ((uint32_t*)in)[i]); + break; + } + break; + } +} + +static void float2int(float* in, void* out, int len, int bps) +{ + register int i; + switch(bps){ + case(1): + for(i=0;i<len;i++) + ((int8_t*)out)[i] = lrintf(127.0 * clamp(in[i], -1.0f, +1.0f)); + break; + case(2): + for(i=0;i<len;i++) + ((int16_t*)out)[i] = lrintf(32767.0 * clamp(in[i], -1.0f, +1.0f)); + break; + case(3): + for(i=0;i<len;i++) + store24bit(out, i, lrintf(2147483647.0 * clamp(in[i], -1.0f, +1.0f))); + break; + case(4): + for(i=0;i<len;i++) + ((int32_t*)out)[i] = lrintf(2147483647.0 * clamp(in[i], -1.0f, +1.0f)); + break; + } +} + +static void int2float(void* in, float* out, int len, int bps) +{ + register int i; + switch(bps){ + case(1): + for(i=0;i<len;i++) + out[i]=(1.0/128.0)*((int8_t*)in)[i]; + break; + case(2): + for(i=0;i<len;i++) + out[i]=(1.0/32768.0)*((int16_t*)in)[i]; + break; + case(3): + for(i=0;i<len;i++) + out[i]=(1.0/2147483648.0)*((int32_t)load24bit(in, i)); + break; + case(4): + for(i=0;i<len;i++) + out[i]=(1.0/2147483648.0)*((int32_t*)in)[i]; + break; + } +} diff --git a/audio/filter/af_format_alaw.h b/audio/filter/af_format_alaw.h new file mode 100644 index 0000000000..d7c00884f7 --- /dev/null +++ b/audio/filter/af_format_alaw.h @@ -0,0 +1,324 @@ +/* + * Copyright (C) 2002 Anders Johansson ajh@watri.uwa.edu.au + * + * This file is based on a part of libsndfile, the work of + * Erik de Castro Lopo <erikd@zip.com.au>. + * + * This file is part of MPlayer. + * + * MPlayer 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. + * + * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPLAYER_AF_FORMAT_ALAW_H +#define MPLAYER_AF_FORMAT_ALAW_H + +#include <inttypes.h> + +#include "af.h" + +// Conversion tables (the function are below) +static short alaw_decode [128] = +{ -5504, -5248, -6016, -5760, -4480, -4224, -4992, -4736, + -7552, -7296, -8064, -7808, -6528, -6272, -7040, -6784, + -2752, -2624, -3008, -2880, -2240, -2112, -2496, -2368, + -3776, -3648, -4032, -3904, -3264, -3136, -3520, -3392, + -22016, -20992, -24064, -23040, -17920, -16896, -19968, -18944, + -30208, -29184, -32256, -31232, -26112, -25088, -28160, -27136, + -11008, -10496, -12032, -11520, -8960, -8448, -9984, -9472, + -15104, -14592, -16128, -15616, -13056, -12544, -14080, -13568, + -344, -328, -376, -360, -280, -264, -312, -296, + -472, -456, -504, -488, -408, -392, -440, -424, + -88, -72, -120, -104, -24, -8, -56, -40, + -216, -200, -248, -232, -152, -136, -184, -168, + -1376, -1312, -1504, -1440, -1120, -1056, -1248, -1184, + -1888, -1824, -2016, -1952, -1632, -1568, -1760, -1696, + -688, -656, -752, -720, -560, -528, -624, -592, + -944, -912, -1008, -976, -816, -784, -880, -848 +} ; /* alaw_decode */ + +static unsigned char alaw_encode [2049] = +{ 0xD5, 0xD4, 0xD7, 0xD6, 0xD1, 0xD0, 0xD3, 0xD2, 0xDD, 0xDC, 0xDF, 0xDE, + 0xD9, 0xD8, 0xDB, 0xDA, 0xC5, 0xC4, 0xC7, 0xC6, 0xC1, 0xC0, 0xC3, 0xC2, + 0xCD, 0xCC, 0xCF, 0xCE, 0xC9, 0xC8, 0xCB, 0xCA, 0xF5, 0xF5, 0xF4, 0xF4, + 0xF7, 0xF7, 0xF6, 0xF6, 0xF1, 0xF1, 0xF0, 0xF0, 0xF3, 0xF3, 0xF2, 0xF2, + 0xFD, 0xFD, 0xFC, 0xFC, 0xFF, 0xFF, 0xFE, 0xFE, 0xF9, 0xF9, 0xF8, 0xF8, + 0xFB, 0xFB, 0xFA, 0xFA, 0xE5, 0xE5, 0xE5, 0xE5, 0xE4, 0xE4, 0xE4, 0xE4, + 0xE7, 0xE7, 0xE7, 0xE7, 0xE6, 0xE6, 0xE6, 0xE6, 0xE1, 0xE1, 0xE1, 0xE1, + 0xE0, 0xE0, 0xE0, 0xE0, 0xE3, 0xE3, 0xE3, 0xE3, 0xE2, 0xE2, 0xE2, 0xE2, + 0xED, 0xED, 0xED, 0xED, 0xEC, 0xEC, 0xEC, 0xEC, 0xEF, 0xEF, 0xEF, 0xEF, + 0xEE, 0xEE, 0xEE, 0xEE, 0xE9, 0xE9, 0xE9, 0xE9, 0xE8, 0xE8, 0xE8, 0xE8, + 0xEB, 0xEB, 0xEB, 0xEB, 0xEA, 0xEA, 0xEA, 0xEA, 0x95, 0x95, 0x95, 0x95, + 0x95, 0x95, 0x95, 0x95, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, + 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x96, 0x96, 0x96, 0x96, + 0x96, 0x96, 0x96, 0x96, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x93, 0x93, 0x93, 0x93, + 0x93, 0x93, 0x93, 0x93, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, + 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9C, 0x9C, 0x9C, 0x9C, + 0x9C, 0x9C, 0x9C, 0x9C, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, + 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x99, 0x99, 0x99, 0x99, + 0x99, 0x99, 0x99, 0x99, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, + 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9A, 0x9A, 0x9A, 0x9A, + 0x9A, 0x9A, 0x9A, 0x9A, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, + 0x8D, 0x8D, 0x8D, 0x8D, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, + 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8F, 0x8F, 0x8F, 0x8F, + 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, + 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, + 0x8E, 0x8E, 0x8E, 0x8E, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, + 0x8B, 0x8B, 0x8B, 0x8B, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, + 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0xB5, 0xB5, 0xB5, 0xB5, + 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, + 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, + 0xB5, 0xB5, 0xB5, 0xB5, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, + 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, + 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, + 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, + 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, + 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB6, 0xB6, 0xB6, 0xB6, + 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, + 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, + 0xB6, 0xB6, 0xB6, 0xB6, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, + 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, + 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, + 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, + 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, + 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB3, 0xB3, 0xB3, 0xB3, + 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, + 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, + 0xB3, 0xB3, 0xB3, 0xB3, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, + 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, + 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, + 0xBD, 0xBD, 0xBD, 0xBD, 0xBD, 0xBD, 0xBD, 0xBD, 0xBD, 0xBD, 0xBD, 0xBD, + 0xBD, 0xBD, 0xBD, 0xBD, 0xBD, 0xBD, 0xBD, 0xBD, 0xBD, 0xBD, 0xBD, 0xBD, + 0xBD, 0xBD, 0xBD, 0xBD, 0xBD, 0xBD, 0xBD, 0xBD, 0xBC, 0xBC, 0xBC, 0xBC, + 0xBC, 0xBC, 0xBC, 0xBC, 0xBC, 0xBC, 0xBC, 0xBC, 0xBC, 0xBC, 0xBC, 0xBC, + 0xBC, 0xBC, 0xBC, 0xBC, 0xBC, 0xBC, 0xBC, 0xBC, 0xBC, 0xBC, 0xBC, 0xBC, + 0xBC, 0xBC, 0xBC, 0xBC, 0xBF, 0xBF, 0xBF, 0xBF, 0xBF, 0xBF, 0xBF, 0xBF, + 0xBF, 0xBF, 0xBF, 0xBF, 0xBF, 0xBF, 0xBF, 0xBF, 0xBF, 0xBF, 0xBF, 0xBF, + 0xBF, 0xBF, 0xBF, 0xBF, 0xBF, 0xBF, 0xBF, 0xBF, 0xBF, 0xBF, 0xBF, 0xBF, + 0xBE, 0xBE, 0xBE, 0xBE, 0xBE, 0xBE, 0xBE, 0xBE, 0xBE, 0xBE, 0xBE, 0xBE, + 0xBE, 0xBE, 0xBE, 0xBE, 0xBE, 0xBE, 0xBE, 0xBE, 0xBE, 0xBE, 0xBE, 0xBE, + 0xBE, 0xBE, 0xBE, 0xBE, 0xBE, 0xBE, 0xBE, 0xBE, 0xB9, 0xB9, 0xB9, 0xB9, + 0xB9, 0xB9, 0xB9, 0xB9, 0xB9, 0xB9, 0xB9, 0xB9, 0xB9, 0xB9, 0xB9, 0xB9, + 0xB9, 0xB9, 0xB9, 0xB9, 0xB9, 0xB9, 0xB9, 0xB9, 0xB9, 0xB9, 0xB9, 0xB9, + 0xB9, 0xB9, 0xB9, 0xB9, 0xB8, 0xB8, 0xB8, 0xB8, 0xB8, 0xB8, 0xB8, 0xB8, + 0xB8, 0xB8, 0xB8, 0xB8, 0xB8, 0xB8, 0xB8, 0xB8, 0xB8, 0xB8, 0xB8, 0xB8, + 0xB8, 0xB8, 0xB8, 0xB8, 0xB8, 0xB8, 0xB8, 0xB8, 0xB8, 0xB8, 0xB8, 0xB8, + 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, + 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, + 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBA, 0xBA, 0xBA, 0xBA, + 0xBA, 0xBA, 0xBA, 0xBA, 0xBA, 0xBA, 0xBA, 0xBA, 0xBA, 0xBA, 0xBA, 0xBA, + 0xBA, 0xBA, 0xBA, 0xBA, 0xBA, 0xBA, 0xBA, 0xBA, 0xBA, 0xBA, 0xBA, 0xBA, + 0xBA, 0xBA, 0xBA, 0xBA, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, + 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, + 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, + 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, + 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, + 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA4, 0xA4, 0xA4, 0xA4, + 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, + 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, + 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, + 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, + 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, + 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, + 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, + 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, + 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, + 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, + 0xA7, 0xA7, 0xA7, 0xA7, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, + 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, + 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, + 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, + 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, + 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA1, 0xA1, 0xA1, 0xA1, + 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, + 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, + 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, + 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, + 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, + 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, + 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, + 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, + 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, + 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, + 0xA0, 0xA0, 0xA0, 0xA0, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, + 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, + 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, + 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, + 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, + 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA2, 0xA2, 0xA2, 0xA2, + 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, + 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, + 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, + 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, + 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, + 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, + 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, + 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, + 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, + 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, + 0xAD, 0xAD, 0xAD, 0xAD, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, + 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, + 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, + 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, + 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, + 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAF, 0xAF, 0xAF, 0xAF, + 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, + 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, + 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, + 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, + 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, + 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, + 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, + 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, + 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, + 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, + 0xAE, 0xAE, 0xAE, 0xAE, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, + 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, + 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, + 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, + 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, + 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xA8, 0xA8, 0xA8, 0xA8, + 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, + 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, + 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, + 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, + 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, + 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, + 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, + 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, + 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, + 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, + 0xAB, 0xAB, 0xAB, 0xAB, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x2A +} ; /* alaw_encode */ + +/* Convert from alaw to signd int8 to signed int32 or float */ +static int from_alaw(void* in, void* out, int len, int bps, int format) +{ + register int i; + // Make sure the input parametrs are OK + if(format & (AF_FORMAT_SPECIAL_MASK | AF_FORMAT_US)) + return AF_ERROR; + + // Convert to int or to float + if((format & AF_FORMAT_POINT_MASK) == AF_FORMAT_I){ + switch(bps){ + case(1): + for(i=0;i<len;i++){ + if(((int8_t*)in)[i] & 0x80) + ((int8_t*)out)[i] = (-1 * alaw_decode[(((int8_t*)in)[i]) & 0x7F]) >> 8; + else + ((int8_t*)out)[i] = (alaw_decode[(((int8_t*)in)[i]) & 0x7F]) >> 8; + } + break; + case(2): + for(i=0;i<len;i++){ + if(((int8_t*)in)[i] & 0x80) + ((int16_t*)out)[i] = -1 * alaw_decode[(((int8_t*)in)[i]) & 0x7F]; + else + ((int16_t*)out)[i] = alaw_decode[(((int8_t*)in)[i]) & 0x7F]; + } + break; + case(4): + for(i=0;i<len;i++){ + if(((int8_t*)in)[i] & 0x80) + ((int32_t*)out)[i] = (-1 * alaw_decode[(((int8_t*)in)[i]) & 0x7F]) << 16; + else + ((int32_t*)out)[i] = (alaw_decode[(((int8_t*)in)[i]) & 0x7F]) << 16; + } + break; + default: + return AF_ERROR; + } + } + else{ + for(i=0;i<len;i++){ + if(((int8_t*)in)[i] & 0x80) + ((float*)out)[i] = -1.0/32768.0 * (float)alaw_decode[(((int8_t*)in)[i]) & 0x7F]; + else + ((float*)out)[i] = +1.0/32768.0 * (float)alaw_decode[(((int8_t*)in)[i]) & 0x7F]; + } + } + return AF_OK; +} + +/* Convert from signed int8 to signed int32 or float to alaw */ +static int to_alaw(void* in, void* out, int len, int bps, int format) +{ + register int i; + // Make sure the input parametrs are OK + if(format & (AF_FORMAT_SPECIAL_MASK | AF_FORMAT_US)) + return AF_ERROR; + + // Convert from int or to float + if((format & AF_FORMAT_POINT_MASK) == AF_FORMAT_I){ + switch(bps){ + case(1): + for(i=0;i<len;i++){ + if(((int8_t*)in)[i] >= 0) + ((int8_t*)out)[i] = alaw_encode[((int8_t*)in)[i] << 4]; + else + ((int8_t*)out)[i] = 0x7F & alaw_encode[-((int8_t*)in)[i] << 4]; + } + break; + case(2): + for(i=0;i<len;i++){ + if(((int16_t*)in)[i] >= 0) + ((int8_t*)out)[i] = alaw_encode[((int16_t*)in)[i] / 16]; + else + ((int8_t*)out)[i] = 0x7F & alaw_encode[((int16_t*)in)[i] / -16]; + } + break; + case(4): + for(i=0;i<len;i++){ + if(((int32_t*)in)[i] >= 0) + ((int8_t*)out)[i] = alaw_encode[((int32_t*)in)[i] >> (16 + 4)]; + else + ((int8_t*)out)[i] = 0x7F & alaw_encode[-((int32_t*)in)[i] >> (16 + 4)]; + } + break; + default: + return AF_ERROR; + } + } + else{ + for(i=0;i<len;i++){ + if(((float*)in)[i] >= 0) + ((int8_t*)out)[i] = alaw_encode[(int)(32767.0/16.0 * ((float*)in)[i])]; + else + ((int8_t*)out)[i] = 0x7F & alaw_encode[(int)(-32767.0/16.0 * ((float*)in)[i])]; + } + } + return AF_OK; +} +#endif /* MPLAYER_AF_FORMAT_ALAW_H */ diff --git a/audio/filter/af_format_ulaw.h b/audio/filter/af_format_ulaw.h new file mode 100644 index 0000000000..5167a1593a --- /dev/null +++ b/audio/filter/af_format_ulaw.h @@ -0,0 +1,837 @@ +/* + * Copyright (C) 2002 Anders Johansson ajh@watri.uwa.edu.au + * + * This file is based on a part of libsndfile, the work of + * Erik de Castro Lopo <erikd@zip.com.au>. + * + * This file is part of MPlayer. + * + * MPlayer 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. + * + * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPLAYER_AF_FORMAT_ULAW_H +#define MPLAYER_AF_FORMAT_ULAW_H + +#include <inttypes.h> + +#include "af.h" +// Conversion tables (the function are below) +static short ulaw_decode[128] = +{ -32124, -31100, -30076, -29052, -28028, -27004, -25980, -24956, + -23932, -22908, -21884, -20860, -19836, -18812, -17788, -16764, + -15996, -15484, -14972, -14460, -13948, -13436, -12924, -12412, + -11900, -11388, -10876, -10364, -9852, -9340, -8828, -8316, + -7932, -7676, -7420, -7164, -6908, -6652, -6396, -6140, + -5884, -5628, -5372, -5116, -4860, -4604, -4348, -4092, + -3900, -3772, -3644, -3516, -3388, -3260, -3132, -3004, + -2876, -2748, -2620, -2492, -2364, -2236, -2108, -1980, + -1884, -1820, -1756, -1692, -1628, -1564, -1500, -1436, + -1372, -1308, -1244, -1180, -1116, -1052, -988, -924, + -876, -844, -812, -780, -748, -716, -684, -652, + -620, -588, -556, -524, -492, -460, -428, -396, + -372, -356, -340, -324, -308, -292, -276, -260, + -244, -228, -212, -196, -180, -164, -148, -132, + -120, -112, -104, -96, -88, -80, -72, -64, + -56, -48, -40, -32, -24, -16, -8, 0, +} ; + +static unsigned char ulaw_encode[8193] = +{ 0xFF, 0xFE, 0xFE, 0xFD, 0xFD, 0xFC, 0xFC, 0xFB, 0xFB, 0xFA, 0xFA, 0xF9, + 0xF9, 0xF8, 0xF8, 0xF7, 0xF7, 0xF6, 0xF6, 0xF5, 0xF5, 0xF4, 0xF4, 0xF3, + 0xF3, 0xF2, 0xF2, 0xF1, 0xF1, 0xF0, 0xF0, 0xEF, 0xEF, 0xEF, 0xEF, 0xEE, + 0xEE, 0xEE, 0xEE, 0xED, 0xED, 0xED, 0xED, 0xEC, 0xEC, 0xEC, 0xEC, 0xEB, + 0xEB, 0xEB, 0xEB, 0xEA, 0xEA, 0xEA, 0xEA, 0xE9, 0xE9, 0xE9, 0xE9, 0xE8, + 0xE8, 0xE8, 0xE8, 0xE7, 0xE7, 0xE7, 0xE7, 0xE6, 0xE6, 0xE6, 0xE6, 0xE5, + 0xE5, 0xE5, 0xE5, 0xE4, 0xE4, 0xE4, 0xE4, 0xE3, 0xE3, 0xE3, 0xE3, 0xE2, + 0xE2, 0xE2, 0xE2, 0xE1, 0xE1, 0xE1, 0xE1, 0xE0, 0xE0, 0xE0, 0xE0, 0xDF, + 0xDF, 0xDF, 0xDF, 0xDF, 0xDF, 0xDF, 0xDF, 0xDE, 0xDE, 0xDE, 0xDE, 0xDE, + 0xDE, 0xDE, 0xDE, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDC, + 0xDC, 0xDC, 0xDC, 0xDC, 0xDC, 0xDC, 0xDC, 0xDB, 0xDB, 0xDB, 0xDB, 0xDB, + 0xDB, 0xDB, 0xDB, 0xDA, 0xDA, 0xDA, 0xDA, 0xDA, 0xDA, 0xDA, 0xDA, 0xD9, + 0xD9, 0xD9, 0xD9, 0xD9, 0xD9, 0xD9, 0xD9, 0xD8, 0xD8, 0xD8, 0xD8, 0xD8, + 0xD8, 0xD8, 0xD8, 0xD7, 0xD7, 0xD7, 0xD7, 0xD7, 0xD7, 0xD7, 0xD7, 0xD6, + 0xD6, 0xD6, 0xD6, 0xD6, 0xD6, 0xD6, 0xD6, 0xD5, 0xD5, 0xD5, 0xD5, 0xD5, + 0xD5, 0xD5, 0xD5, 0xD4, 0xD4, 0xD4, 0xD4, 0xD4, 0xD4, 0xD4, 0xD4, 0xD3, + 0xD3, 0xD3, 0xD3, 0xD3, 0xD3, 0xD3, 0xD3, 0xD2, 0xD2, 0xD2, 0xD2, 0xD2, + 0xD2, 0xD2, 0xD2, 0xD1, 0xD1, 0xD1, 0xD1, 0xD1, 0xD1, 0xD1, 0xD1, 0xD0, + 0xD0, 0xD0, 0xD0, 0xD0, 0xD0, 0xD0, 0xD0, 0xCF, 0xCF, 0xCF, 0xCF, 0xCF, + 0xCF, 0xCF, 0xCF, 0xCF, 0xCF, 0xCF, 0xCF, 0xCF, 0xCF, 0xCF, 0xCF, 0xCE, + 0xCE, 0xCE, 0xCE, 0xCE, 0xCE, 0xCE, 0xCE, 0xCE, 0xCE, 0xCE, 0xCE, 0xCE, + 0xCE, 0xCE, 0xCE, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, + 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, + 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCB, + 0xCB, 0xCB, 0xCB, 0xCB, 0xCB, 0xCB, 0xCB, 0xCB, 0xCB, 0xCB, 0xCB, 0xCB, + 0xCB, 0xCB, 0xCB, 0xCA, 0xCA, 0xCA, 0xCA, 0xCA, 0xCA, 0xCA, 0xCA, 0xCA, + 0xCA, 0xCA, 0xCA, 0xCA, 0xCA, 0xCA, 0xCA, 0xC9, 0xC9, 0xC9, 0xC9, 0xC9, + 0xC9, 0xC9, 0xC9, 0xC9, 0xC9, 0xC9, 0xC9, 0xC9, 0xC9, 0xC9, 0xC9, 0xC8, + 0xC8, 0xC8, 0xC8, 0xC8, 0xC8, 0xC8, 0xC8, 0xC8, 0xC8, 0xC8, 0xC8, 0xC8, + 0xC8, 0xC8, 0xC8, 0xC7, 0xC7, 0xC7, 0xC7, 0xC7, 0xC7, 0xC7, 0xC7, 0xC7, + 0xC7, 0xC7, 0xC7, 0xC7, 0xC7, 0xC7, 0xC7, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, + 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0xC5, + 0xC5, 0xC5, 0xC5, 0xC5, 0xC5, 0xC5, 0xC5, 0xC5, 0xC5, 0xC5, 0xC5, 0xC5, + 0xC5, 0xC5, 0xC5, 0xC4, 0xC4, 0xC4, 0xC4, 0xC4, 0xC4, 0xC4, 0xC4, 0xC4, + 0xC4, 0xC4, 0xC4, 0xC4, 0xC4, 0xC4, 0xC4, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, + 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC2, + 0xC2, 0xC2, 0xC2, 0xC2, 0xC2, 0xC2, 0xC2, 0xC2, 0xC2, 0xC2, 0xC2, 0xC2, + 0xC2, 0xC2, 0xC2, 0xC1, 0xC1, 0xC1, 0xC1, 0xC1, 0xC1, 0xC1, 0xC1, 0xC1, + 0xC1, 0xC1, 0xC1, 0xC1, 0xC1, 0xC1, 0xC1, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, + 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xBF, + 0xBF, 0xBF, 0xBF, 0xBF, 0xBF, 0xBF, 0xBF, 0xBF, 0xBF, 0xBF, 0xBF, 0xBF, + 0xBF, 0xBF, 0xBF, 0xBF, 0xBF, 0xBF, 0xBF, 0xBF, 0xBF, 0xBF, 0xBF, 0xBF, + 0xBF, 0xBF, 0xBF, 0xBF, 0xBF, 0xBF, 0xBF, 0xBE, 0xBE, 0xBE, 0xBE, 0xBE, + 0xBE, 0xBE, 0xBE, 0xBE, 0xBE, 0xBE, 0xBE, 0xBE, 0xBE, 0xBE, 0xBE, 0xBE, + 0xBE, 0xBE, 0xBE, 0xBE, 0xBE, 0xBE, 0xBE, 0xBE, 0xBE, 0xBE, 0xBE, 0xBE, + 0xBE, 0xBE, 0xBE, 0xBD, 0xBD, 0xBD, 0xBD, 0xBD, 0xBD, 0xBD, 0xBD, 0xBD, + 0xBD, 0xBD, 0xBD, 0xBD, 0xBD, 0xBD, 0xBD, 0xBD, 0xBD, 0xBD, 0xBD, 0xBD, + 0xBD, 0xBD, 0xBD, 0xBD, 0xBD, 0xBD, 0xBD, 0xBD, 0xBD, 0xBD, 0xBD, 0xBC, + 0xBC, 0xBC, 0xBC, 0xBC, 0xBC, 0xBC, 0xBC, 0xBC, 0xBC, 0xBC, 0xBC, 0xBC, + 0xBC, 0xBC, 0xBC, 0xBC, 0xBC, 0xBC, 0xBC, 0xBC, 0xBC, 0xBC, 0xBC, 0xBC, + 0xBC, 0xBC, 0xBC, 0xBC, 0xBC, 0xBC, 0xBC, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, + 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, + 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, + 0xBB, 0xBB, 0xBB, 0xBA, 0xBA, 0xBA, 0xBA, 0xBA, 0xBA, 0xBA, 0xBA, 0xBA, + 0xBA, 0xBA, 0xBA, 0xBA, 0xBA, 0xBA, 0xBA, 0xBA, 0xBA, 0xBA, 0xBA, 0xBA, + 0xBA, 0xBA, 0xBA, 0xBA, 0xBA, 0xBA, 0xBA, 0xBA, 0xBA, 0xBA, 0xBA, 0xB9, + 0xB9, 0xB9, 0xB9, 0xB9, 0xB9, 0xB9, 0xB9, 0xB9, 0xB9, 0xB9, 0xB9, 0xB9, + 0xB9, 0xB9, 0xB9, 0xB9, 0xB9, 0xB9, 0xB9, 0xB9, 0xB9, 0xB9, 0xB9, 0xB9, + 0xB9, 0xB9, 0xB9, 0xB9, 0xB9, 0xB9, 0xB9, 0xB8, 0xB8, 0xB8, 0xB8, 0xB8, + 0xB8, 0xB8, 0xB8, 0xB8, 0xB8, 0xB8, 0xB8, 0xB8, 0xB8, 0xB8, 0xB8, 0xB8, + 0xB8, 0xB8, 0xB8, 0xB8, 0xB8, 0xB8, 0xB8, 0xB8, 0xB8, 0xB8, 0xB8, 0xB8, + 0xB8, 0xB8, 0xB8, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, + 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, + 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB6, + 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, + 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, + 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, + 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, + 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, + 0xB5, 0xB5, 0xB5, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, + 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, + 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB3, + 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, + 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, + 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, + 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, + 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, + 0xB2, 0xB2, 0xB2, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, + 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, + 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB0, + 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, + 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, + 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, + 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, + 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, + 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, + 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, + 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAE, + 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, + 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, + 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, + 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, + 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, + 0xAE, 0xAE, 0xAE, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, + 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, + 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, + 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, + 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, + 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, + 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, + 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, + 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, + 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, + 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAB, + 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, + 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, + 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, + 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, + 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, + 0xAB, 0xAB, 0xAB, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, + 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, + 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, + 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, + 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, + 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xA8, + 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, + 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, + 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, + 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, + 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, + 0xA8, 0xA8, 0xA8, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, + 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, + 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, + 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, + 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, + 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, + 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, + 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, + 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, + 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, + 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA5, + 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, + 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, + 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, + 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, + 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, + 0xA5, 0xA5, 0xA5, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, + 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, + 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, + 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, + 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, + 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, + 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, + 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, + 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, + 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, + 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA2, + 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, + 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, + 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, + 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, + 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, + 0xA2, 0xA2, 0xA2, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, + 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, + 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, + 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, + 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, + 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, + 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, + 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, + 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, + 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, + 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0x9F, + 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, + 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, + 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, + 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, + 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, + 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, + 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, + 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, + 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, + 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, + 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, + 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, + 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, + 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, + 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, + 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, + 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, + 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, + 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, + 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, + 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, + 0x9E, 0x9E, 0x9E, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, + 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, + 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, + 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, + 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, + 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, + 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, + 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, + 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, + 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, + 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9C, + 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, + 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, + 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, + 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, + 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, + 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, + 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, + 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, + 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, + 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, + 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, + 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, + 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, + 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, + 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, + 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, + 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, + 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, + 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, + 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, + 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, + 0x9B, 0x9B, 0x9B, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, + 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, + 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, + 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, + 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, + 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, + 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, + 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, + 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, + 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, + 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x99, + 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, + 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, + 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, + 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, + 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, + 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, + 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, + 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, + 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, + 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, + 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x98, 0x98, 0x98, 0x98, 0x98, + 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, + 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, + 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, + 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, + 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, + 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, + 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, + 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, + 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, + 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, + 0x98, 0x98, 0x98, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, + 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, + 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, + 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, + 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, + 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, + 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, + 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, + 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, + 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, + 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x96, + 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, + 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, + 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, + 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, + 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, + 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, + 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, + 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, + 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, + 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, + 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x95, 0x95, 0x95, 0x95, 0x95, + 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, + 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, + 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, + 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, + 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, + 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, + 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, + 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, + 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, + 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, + 0x95, 0x95, 0x95, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, + 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, + 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, + 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, + 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, + 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, + 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, + 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, + 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, + 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, + 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x93, + 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, + 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, + 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, + 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, + 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, + 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, + 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, + 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, + 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, + 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, + 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x92, 0x92, 0x92, 0x92, 0x92, + 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, + 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, + 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, + 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, + 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, + 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, + 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, + 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, + 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, + 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, + 0x92, 0x92, 0x92, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, + 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, + 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, + 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, + 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, + 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, + 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, + 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, + 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, + 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, + 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, + 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, + 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, + 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, + 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, + 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, + 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, + 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, + 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, + 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, + 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, + 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, + 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, + 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, + 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, + 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, + 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, + 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, + 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, + 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, + 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, + 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8E, + 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, + 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, + 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, + 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, + 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, + 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, + 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, + 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, + 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, + 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, + 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, + 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, + 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, + 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, + 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, + 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, + 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, + 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, + 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, + 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, + 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, + 0x8E, 0x8E, 0x8E, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, + 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, + 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, + 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, + 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, + 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, + 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, + 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, + 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, + 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, + 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, + 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, + 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, + 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, + 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, + 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, + 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, + 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, + 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, + 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, + 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, + 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, + 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, + 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, + 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, + 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, + 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, + 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, + 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, + 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, + 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, + 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, + 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, + 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, + 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, + 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, + 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, + 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, + 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, + 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, + 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, + 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, + 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8B, + 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, + 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, + 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, + 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, + 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, + 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, + 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, + 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, + 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, + 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, + 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, + 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, + 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, + 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, + 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, + 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, + 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, + 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, + 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, + 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, + 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, + 0x8B, 0x8B, 0x8B, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, + 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, + 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, + 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, + 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, + 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, + 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, + 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, + 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, + 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, + 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, + 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, + 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, + 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, + 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, + 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, + 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, + 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, + 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, + 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, + 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, + 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00 +} ; + + +/* Convert from ulaw to signd int8 to signed int32 or float */ +static int from_ulaw(void* in, void* out, int len, int bps, int format) +{ + register int i; + // Make sure the input parametrs are OK + if(format & (AF_FORMAT_SPECIAL_MASK | AF_FORMAT_US)) + return AF_ERROR; + + // Convert to int or to float + if((format & AF_FORMAT_POINT_MASK) == AF_FORMAT_I){ + switch(bps){ + case(1): + for(i=0;i<len;i++){ + if(((int8_t*)in)[i] & 0x80) + ((int8_t*)out)[i] = (-1 * ulaw_decode[(((int8_t*)in)[i]) & 0x7F]) >> 8; + else + ((int8_t*)out)[i] = (ulaw_decode[(((int8_t*)in)[i]) & 0x7F]) >> 8; + } + break; + case(2): + for(i=0;i<len;i++){ + if(((int8_t*)in)[i] & 0x80) + ((int16_t*)out)[i] = -1 * ulaw_decode[(((int8_t*)in)[i]) & 0x7F]; + else + ((int16_t*)out)[i] = ulaw_decode[(((int8_t*)in)[i]) & 0x7F]; + } + break; + case(4): + for(i=0;i<len;i++){ + if(((int8_t*)in)[i] & 0x80) + ((int32_t*)out)[i] = (-1 * ulaw_decode[(((int8_t*)in)[i]) & 0x7F]) << 16; + else + ((int32_t*)out)[i] = (ulaw_decode[(((int8_t*)in)[i]) & 0x7F]) << 16; + } + break; + default: + return AF_ERROR; + } + } + else{ + for(i=0;i<len;i++){ + if(((int8_t*)in)[i] & 0x80) + ((float*)out)[i] = -1.0/32768.0 * (float)ulaw_decode[(((int8_t*)in)[i]) & 0x7F]; + else + ((float*)out)[i] = +1.0/32768.0 * (float)ulaw_decode[(((int8_t*)in)[i]) & 0x7F]; + } + } + return AF_OK; +} + +/* Convert from signed int8 to signed int32 or float to ulaw */ +static int to_ulaw(void* in, void* out, int len, int bps, int format) +{ + register int i; + // Make sure the input parametrs are OK + if(format & (AF_FORMAT_SPECIAL_MASK | AF_FORMAT_US)) + return AF_ERROR; + + // Convert from int or to float + if((format & AF_FORMAT_POINT_MASK) == AF_FORMAT_I){ + switch(bps){ + case(1): + for(i=0;i<len;i++){ + if(((int8_t*)in)[i] >= 0) + ((int8_t*)out)[i] = ulaw_encode[((int8_t*)in)[i] << 6]; + else + ((int8_t*)out)[i] = 0x7F & ulaw_encode[-((int8_t*)in)[i] << 6]; + } + break; + case(2): + for(i=0;i<len;i++){ + if(((int16_t*)in)[i] >= 0) + ((int8_t*)out)[i] = ulaw_encode[((int16_t*)in)[i] / 4]; + else + ((int8_t*)out)[i] = 0x7F & ulaw_encode[((int16_t*)in)[i] / -4]; + } + break; + case(4): + for(i=0;i<len;i++){ + if(((int32_t*)in)[i] >= 0) + ((int8_t*)out)[i] = ulaw_encode[((int32_t*)in)[i] >> (16 + 2)]; + else + ((int8_t*)out)[i] = 0x7F & ulaw_encode[-((int32_t*)in)[i] >> (16 + 2)]; + } + break; + default: + return AF_ERROR; + } + } + else{ + for(i=0;i<len;i++){ + if(((float*)in)[i] >= 0) + ((int8_t*)out)[i] = ulaw_encode[(int)(32767.0/4.0 * ((float*)in)[i])]; + else + ((int8_t*)out)[i] = 0x7F & ulaw_encode[(int)(-32767.0/4.0 * ((float*)in)[i])]; + } + } + return AF_OK; +} + +#endif /* MPLAYER_AF_FORMAT_ULAW_H */ diff --git a/audio/filter/af_hrtf.c b/audio/filter/af_hrtf.c new file mode 100644 index 0000000000..4f5eedb29d --- /dev/null +++ b/audio/filter/af_hrtf.c @@ -0,0 +1,670 @@ +/* + * Experimental audio filter that mixes 5.1 and 5.1 with matrix + * encoded rear channels into headphone signal using FIR filtering + * with HRTF. + * + * This file is part of MPlayer. + * + * MPlayer 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. + * + * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +//#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <inttypes.h> + +#include <math.h> +#include <libavutil/common.h> + +#include "af.h" +#include "dsp.h" + +/* HRTF filter coefficients and adjustable parameters */ +#include "af_hrtf.h" + +typedef struct af_hrtf_s { + /* Lengths */ + int dlbuflen, hrflen, basslen; + /* L, C, R, Ls, Rs channels */ + float *lf, *rf, *lr, *rr, *cf, *cr; + const float *cf_ir, *af_ir, *of_ir, *ar_ir, *or_ir, *cr_ir; + int cf_o, af_o, of_o, ar_o, or_o, cr_o; + /* Bass */ + float *ba_l, *ba_r; + float *ba_ir; + /* Whether to matrix decode the rear center channel */ + int matrix_mode; + /* How to decode the input: + 0 = 5/5+1 channels + 1 = 2 channels + 2 = matrix encoded 2 channels */ + int decode_mode; + /* Full wave rectified (FWR) amplitudes and gain used to steer the + active matrix decoding of front channels (variable names + lpr/lmr means Lt + Rt, Lt - Rt) */ + float l_fwr, r_fwr, lpr_fwr, lmr_fwr; + float adapt_l_gain, adapt_r_gain, adapt_lpr_gain, adapt_lmr_gain; + /* Matrix input decoding require special FWR buffer, since the + decoding is done in place. */ + float *fwrbuf_l, *fwrbuf_r, *fwrbuf_lr, *fwrbuf_rr; + /* Rear channel delay buffer for matrix decoding */ + float *rear_dlbuf; + /* Full wave rectified amplitude and gain used to steer the active + matrix decoding of center rear channel */ + float lr_fwr, rr_fwr, lrprr_fwr, lrmrr_fwr; + float adapt_lr_gain, adapt_rr_gain; + float adapt_lrprr_gain, adapt_lrmrr_gain; + /* Cyclic position on the ring buffer */ + int cyc_pos; + int print_flag; +} af_hrtf_t; + +/* Convolution on a ring buffer + * nx: length of the ring buffer + * nk: length of the convolution kernel + * sx: ring buffer + * sk: convolution kernel + * offset: offset on the ring buffer, can be + */ +static float conv(const int nx, const int nk, const float *sx, const float *sk, + const int offset) +{ + /* k = reminder of offset / nx */ + int k = offset >= 0 ? offset % nx : nx + (offset % nx); + + if(nk + k <= nx) + return af_filter_fir(nk, sx + k, sk); + else + return af_filter_fir(nk + k - nx, sx, sk + nx - k) + + af_filter_fir(nx - k, sx + k, sk); +} + +/* Detect when the impulse response starts (significantly) */ +static int pulse_detect(const float *sx) +{ + /* nmax must be the reference impulse response length (128) minus + s->hrflen */ + const int nmax = 128 - HRTFFILTLEN; + const float thresh = IRTHRESH; + int i; + + for(i = 0; i < nmax; i++) + if(fabs(sx[i]) > thresh) + return i; + return 0; +} + +/* Fuzzy matrix coefficient transfer function to "lock" the matrix on + a effectively passive mode if the gain is approximately 1 */ +static inline float passive_lock(float x) +{ + const float x1 = x - 1; + const float ax1s = fabs(x - 1) * (1.0 / MATAGCLOCK); + + return x1 - x1 / (1 + ax1s * ax1s) + 1; +} + +/* Unified active matrix decoder for 2 channel matrix encoded surround + sources */ +static inline void matrix_decode(short *in, const int k, const int il, + const int ir, const int decode_rear, + const int dlbuflen, + float l_fwr, float r_fwr, + float lpr_fwr, float lmr_fwr, + float *adapt_l_gain, float *adapt_r_gain, + float *adapt_lpr_gain, float *adapt_lmr_gain, + float *lf, float *rf, float *lr, + float *rr, float *cf) +{ + const int kr = (k + MATREARDELAY) % dlbuflen; + float l_gain = (l_fwr + r_fwr) / + (1 + l_fwr + l_fwr); + float r_gain = (l_fwr + r_fwr) / + (1 + r_fwr + r_fwr); + /* The 2nd axis has strong gain fluctuations, and therefore require + limits. The factor corresponds to the 1 / amplification of (Lt + - Rt) when (Lt, Rt) is strongly correlated. (e.g. during + dialogues). It should be bigger than -12 dB to prevent + distortion. */ + float lmr_lim_fwr = lmr_fwr > M9_03DB * lpr_fwr ? + lmr_fwr : M9_03DB * lpr_fwr; + float lpr_gain = (lpr_fwr + lmr_lim_fwr) / + (1 + lpr_fwr + lpr_fwr); + float lmr_gain = (lpr_fwr + lmr_lim_fwr) / + (1 + lmr_lim_fwr + lmr_lim_fwr); + float lmr_unlim_gain = (lpr_fwr + lmr_fwr) / + (1 + lmr_fwr + lmr_fwr); + float lpr, lmr; + float l_agc, r_agc, lpr_agc, lmr_agc; + float f, d_gain, c_gain, c_agc_cfk; + +#if 0 + static int counter = 0; + static FILE *fp_out; + + if(counter == 0) + fp_out = fopen("af_hrtf.log", "w"); + if(counter % 240 == 0) + fprintf(fp_out, "%g %g %g %g %g ", counter * (1.0 / 48000), + l_gain, r_gain, lpr_gain, lmr_gain); +#endif + + /*** AXIS NO. 1: (Lt, Rt) -> (C, Ls, Rs) ***/ + /* AGC adaption */ + d_gain = (fabs(l_gain - *adapt_l_gain) + + fabs(r_gain - *adapt_r_gain)) * 0.5; + f = d_gain * (1.0 / MATAGCTRIG); + f = MATAGCDECAY - MATAGCDECAY / (1 + f * f); + *adapt_l_gain = (1 - f) * *adapt_l_gain + f * l_gain; + *adapt_r_gain = (1 - f) * *adapt_r_gain + f * r_gain; + /* Matrix */ + l_agc = in[il] * passive_lock(*adapt_l_gain); + r_agc = in[ir] * passive_lock(*adapt_r_gain); + cf[k] = (l_agc + r_agc) * M_SQRT1_2; + if(decode_rear) { + lr[kr] = rr[kr] = (l_agc - r_agc) * M_SQRT1_2; + /* Stereo rear channel is steered with the same AGC steering as + the decoding matrix. Note this requires a fast updating AGC + at the order of 20 ms (which is the case here). */ + lr[kr] *= (l_fwr + l_fwr) / + (1 + l_fwr + r_fwr); + rr[kr] *= (r_fwr + r_fwr) / + (1 + l_fwr + r_fwr); + } + + /*** AXIS NO. 2: (Lt + Rt, Lt - Rt) -> (L, R) ***/ + lpr = (in[il] + in[ir]) * M_SQRT1_2; + lmr = (in[il] - in[ir]) * M_SQRT1_2; + /* AGC adaption */ + d_gain = fabs(lmr_unlim_gain - *adapt_lmr_gain); + f = d_gain * (1.0 / MATAGCTRIG); + f = MATAGCDECAY - MATAGCDECAY / (1 + f * f); + *adapt_lpr_gain = (1 - f) * *adapt_lpr_gain + f * lpr_gain; + *adapt_lmr_gain = (1 - f) * *adapt_lmr_gain + f * lmr_gain; + /* Matrix */ + lpr_agc = lpr * passive_lock(*adapt_lpr_gain); + lmr_agc = lmr * passive_lock(*adapt_lmr_gain); + lf[k] = (lpr_agc + lmr_agc) * M_SQRT1_2; + rf[k] = (lpr_agc - lmr_agc) * M_SQRT1_2; + + /*** CENTER FRONT CANCELLATION ***/ + /* A heuristic approach exploits that Lt + Rt gain contains the + information about Lt, Rt correlation. This effectively reshapes + the front and rear "cones" to concentrate Lt + Rt to C and + introduce Lt - Rt in L, R. */ + /* 0.67677 is the emprical lower bound for lpr_gain. */ + c_gain = 8 * (*adapt_lpr_gain - 0.67677); + c_gain = c_gain > 0 ? c_gain : 0; + /* c_gain should not be too high, not even reaching full + cancellation (~ 0.50 - 0.55 at current AGC implementation), or + the center will s0und too narrow. */ + c_gain = MATCOMPGAIN / (1 + c_gain * c_gain); + c_agc_cfk = c_gain * cf[k]; + lf[k] -= c_agc_cfk; + rf[k] -= c_agc_cfk; + cf[k] += c_agc_cfk + c_agc_cfk; +#if 0 + if(counter % 240 == 0) + fprintf(fp_out, "%g %g %g %g %g\n", + *adapt_l_gain, *adapt_r_gain, + *adapt_lpr_gain, *adapt_lmr_gain, + c_gain); + counter++; +#endif +} + +static inline void update_ch(af_hrtf_t *s, short *in, const int k) +{ + const int fwr_pos = (k + FWRDURATION) % s->dlbuflen; + /* Update the full wave rectified total amplitude */ + /* Input matrix decoder */ + if(s->decode_mode == HRTF_MIX_MATRIX2CH) { + s->l_fwr += abs(in[0]) - fabs(s->fwrbuf_l[fwr_pos]); + s->r_fwr += abs(in[1]) - fabs(s->fwrbuf_r[fwr_pos]); + s->lpr_fwr += abs(in[0] + in[1]) - + fabs(s->fwrbuf_l[fwr_pos] + s->fwrbuf_r[fwr_pos]); + s->lmr_fwr += abs(in[0] - in[1]) - + fabs(s->fwrbuf_l[fwr_pos] - s->fwrbuf_r[fwr_pos]); + } + /* Rear matrix decoder */ + if(s->matrix_mode) { + s->lr_fwr += abs(in[2]) - fabs(s->fwrbuf_lr[fwr_pos]); + s->rr_fwr += abs(in[3]) - fabs(s->fwrbuf_rr[fwr_pos]); + s->lrprr_fwr += abs(in[2] + in[3]) - + fabs(s->fwrbuf_lr[fwr_pos] + s->fwrbuf_rr[fwr_pos]); + s->lrmrr_fwr += abs(in[2] - in[3]) - + fabs(s->fwrbuf_lr[fwr_pos] - s->fwrbuf_rr[fwr_pos]); + } + + switch (s->decode_mode) { + case HRTF_MIX_51: + /* 5/5+1 channel sources */ + s->lf[k] = in[0]; + s->cf[k] = in[4]; + s->rf[k] = in[1]; + s->fwrbuf_lr[k] = s->lr[k] = in[2]; + s->fwrbuf_rr[k] = s->rr[k] = in[3]; + break; + case HRTF_MIX_MATRIX2CH: + /* Matrix encoded 2 channel sources */ + s->fwrbuf_l[k] = in[0]; + s->fwrbuf_r[k] = in[1]; + matrix_decode(in, k, 0, 1, 1, s->dlbuflen, + s->l_fwr, s->r_fwr, + s->lpr_fwr, s->lmr_fwr, + &(s->adapt_l_gain), &(s->adapt_r_gain), + &(s->adapt_lpr_gain), &(s->adapt_lmr_gain), + s->lf, s->rf, s->lr, s->rr, s->cf); + break; + case HRTF_MIX_STEREO: + /* Stereo sources */ + s->lf[k] = in[0]; + s->rf[k] = in[1]; + s->cf[k] = s->lr[k] = s->rr[k] = 0; + break; + } + + /* We need to update the bass compensation delay line, too. */ + s->ba_l[k] = in[0] + in[4] + in[2]; + s->ba_r[k] = in[4] + in[1] + in[3]; +} + +/* Initialization and runtime control */ +static int control(struct af_instance *af, int cmd, void* arg) +{ + af_hrtf_t *s = af->setup; + int test_output_res; + char mode; + + switch(cmd) { + case AF_CONTROL_REINIT: + af->data->rate = ((struct mp_audio*)arg)->rate; + if(af->data->rate != 48000) { + // automatic samplerate adjustment in the filter chain + // is not yet supported. + mp_msg(MSGT_AFILTER, MSGL_ERR, + "[hrtf] ERROR: Sampling rate is not 48000 Hz (%d)!\n", + af->data->rate); + return AF_ERROR; + } + af->data->nch = ((struct mp_audio*)arg)->nch; + if(af->data->nch == 2) { + /* 2 channel input */ + if(s->decode_mode != HRTF_MIX_MATRIX2CH) { + /* Default behavior is stereo mixing. */ + s->decode_mode = HRTF_MIX_STEREO; + } + } + else if (af->data->nch < 5) + af->data->nch = 5; + af->data->format = AF_FORMAT_S16_NE; + af->data->bps = 2; + test_output_res = af_test_output(af, (struct mp_audio*)arg); + af->mul = 2.0 / af->data->nch; + // after testing input set the real output format + af->data->nch = 2; + s->print_flag = 1; + return test_output_res; + case AF_CONTROL_COMMAND_LINE: + sscanf((char*)arg, "%c", &mode); + switch(mode) { + case 'm': + /* Use matrix rear decoding. */ + s->matrix_mode = 1; + break; + case 's': + /* Input needs matrix decoding. */ + s->decode_mode = HRTF_MIX_MATRIX2CH; + break; + case '0': + s->matrix_mode = 0; + break; + default: + mp_msg(MSGT_AFILTER, MSGL_ERR, + "[hrtf] Mode is neither 'm', 's', nor '0' (%c).\n", + mode); + return AF_ERROR; + } + s->print_flag = 1; + return AF_OK; + } + + return AF_UNKNOWN; +} + +/* Deallocate memory */ +static void uninit(struct af_instance *af) +{ + if(af->setup) { + af_hrtf_t *s = af->setup; + + free(s->lf); + free(s->rf); + free(s->lr); + free(s->rr); + free(s->cf); + free(s->cr); + free(s->ba_l); + free(s->ba_r); + free(s->ba_ir); + free(s->fwrbuf_l); + free(s->fwrbuf_r); + free(s->fwrbuf_lr); + free(s->fwrbuf_rr); + free(af->setup); + } + if(af->data) + free(af->data->audio); + free(af->data); +} + +/* Filter data through filter + +Two "tricks" are used to compensate the "color" of the KEMAR data: + +1. The KEMAR data is refiltered to ensure that the front L, R channels +on the same side of the ear are equalized (especially in the high +frequencies). + +2. A bass compensation is introduced to ensure that 0-200 Hz are not +damped (without any real 3D acoustical image, however). +*/ +static struct mp_audio* play(struct af_instance *af, struct mp_audio *data) +{ + af_hrtf_t *s = af->setup; + short *in = data->audio; // Input audio data + short *out = NULL; // Output audio data + short *end = in + data->len / sizeof(short); // Loop end + float common, left, right, diff, left_b, right_b; + const int dblen = s->dlbuflen, hlen = s->hrflen, blen = s->basslen; + + if(AF_OK != RESIZE_LOCAL_BUFFER(af, data)) + return NULL; + + if(s->print_flag) { + s->print_flag = 0; + switch (s->decode_mode) { + case HRTF_MIX_51: + mp_msg(MSGT_AFILTER, MSGL_INFO, + "[hrtf] Using HRTF to mix %s discrete surround into " + "L, R channels\n", s->matrix_mode ? "5+1" : "5"); + break; + case HRTF_MIX_STEREO: + mp_msg(MSGT_AFILTER, MSGL_INFO, + "[hrtf] Using HRTF to mix stereo into " + "L, R channels\n"); + break; + case HRTF_MIX_MATRIX2CH: + mp_msg(MSGT_AFILTER, MSGL_INFO, + "[hrtf] Using active matrix to decode 2 channel " + "input, HRTF to mix %s matrix surround into " + "L, R channels\n", "3/2"); + break; + default: + mp_msg(MSGT_AFILTER, MSGL_WARN, + "[hrtf] bogus decode_mode: %d\n", s->decode_mode); + break; + } + + if(s->matrix_mode) + mp_msg(MSGT_AFILTER, MSGL_INFO, + "[hrtf] Using active matrix to decode rear center " + "channel\n"); + } + + out = af->data->audio; + + /* MPlayer's 5 channel layout (notation for the variable): + * + * 0: L (LF), 1: R (RF), 2: Ls (LR), 3: Rs (RR), 4: C (CF), matrix + * encoded: Cs (CR) + * + * or: L = left, C = center, R = right, F = front, R = rear + * + * Filter notation: + * + * CF + * OF AF + * Ear-> + * OR AR + * CR + * + * or: C = center, A = same side, O = opposite, F = front, R = rear + */ + + while(in < end) { + const int k = s->cyc_pos; + + update_ch(s, in, k); + + /* Simulate a 7.5 ms -20 dB echo of the center channel in the + front channels (like reflection from a room wall) - a kind of + psycho-acoustically "cheating" to focus the center front + channel, which is normally hard to be perceived as front */ + s->lf[k] += CFECHOAMPL * s->cf[(k + CFECHODELAY) % s->dlbuflen]; + s->rf[k] += CFECHOAMPL * s->cf[(k + CFECHODELAY) % s->dlbuflen]; + + switch (s->decode_mode) { + case HRTF_MIX_51: + case HRTF_MIX_MATRIX2CH: + /* Mixer filter matrix */ + common = conv(dblen, hlen, s->cf, s->cf_ir, k + s->cf_o); + if(s->matrix_mode) { + /* In matrix decoding mode, the rear channel gain must be + renormalized, as there is an additional channel. */ + matrix_decode(in, k, 2, 3, 0, s->dlbuflen, + s->lr_fwr, s->rr_fwr, + s->lrprr_fwr, s->lrmrr_fwr, + &(s->adapt_lr_gain), &(s->adapt_rr_gain), + &(s->adapt_lrprr_gain), &(s->adapt_lrmrr_gain), + s->lr, s->rr, NULL, NULL, s->cr); + common += + conv(dblen, hlen, s->cr, s->cr_ir, k + s->cr_o) * + M1_76DB; + left = + ( conv(dblen, hlen, s->lf, s->af_ir, k + s->af_o) + + conv(dblen, hlen, s->rf, s->of_ir, k + s->of_o) + + (conv(dblen, hlen, s->lr, s->ar_ir, k + s->ar_o) + + conv(dblen, hlen, s->rr, s->or_ir, k + s->or_o)) * + M1_76DB + common); + right = + ( conv(dblen, hlen, s->rf, s->af_ir, k + s->af_o) + + conv(dblen, hlen, s->lf, s->of_ir, k + s->of_o) + + (conv(dblen, hlen, s->rr, s->ar_ir, k + s->ar_o) + + conv(dblen, hlen, s->lr, s->or_ir, k + s->or_o)) * + M1_76DB + common); + } else { + left = + ( conv(dblen, hlen, s->lf, s->af_ir, k + s->af_o) + + conv(dblen, hlen, s->rf, s->of_ir, k + s->of_o) + + conv(dblen, hlen, s->lr, s->ar_ir, k + s->ar_o) + + conv(dblen, hlen, s->rr, s->or_ir, k + s->or_o) + + common); + right = + ( conv(dblen, hlen, s->rf, s->af_ir, k + s->af_o) + + conv(dblen, hlen, s->lf, s->of_ir, k + s->of_o) + + conv(dblen, hlen, s->rr, s->ar_ir, k + s->ar_o) + + conv(dblen, hlen, s->lr, s->or_ir, k + s->or_o) + + common); + } + break; + case HRTF_MIX_STEREO: + left = + ( conv(dblen, hlen, s->lf, s->af_ir, k + s->af_o) + + conv(dblen, hlen, s->rf, s->of_ir, k + s->of_o)); + right = + ( conv(dblen, hlen, s->rf, s->af_ir, k + s->af_o) + + conv(dblen, hlen, s->lf, s->of_ir, k + s->of_o)); + break; + default: + /* make gcc happy */ + left = 0.0; + right = 0.0; + break; + } + + /* Bass compensation for the lower frequency cut of the HRTF. A + cross talk of the left and right channel is introduced to + match the directional characteristics of higher frequencies. + The bass will not have any real 3D perception, but that is + OK (note at 180 Hz, the wavelength is about 2 m, and any + spatial perception is impossible). */ + left_b = conv(dblen, blen, s->ba_l, s->ba_ir, k); + right_b = conv(dblen, blen, s->ba_r, s->ba_ir, k); + left += (1 - BASSCROSS) * left_b + BASSCROSS * right_b; + right += (1 - BASSCROSS) * right_b + BASSCROSS * left_b; + /* Also mix the LFE channel (if available) */ + if(data->nch >= 6) { + left += in[5] * M3_01DB; + right += in[5] * M3_01DB; + } + + /* Amplitude renormalization. */ + left *= AMPLNORM; + right *= AMPLNORM; + + switch (s->decode_mode) { + case HRTF_MIX_51: + case HRTF_MIX_STEREO: + /* "Cheating": linear stereo expansion to amplify the 3D + perception. Note: Too much will destroy the acoustic space + and may even result in headaches. */ + diff = STEXPAND2 * (left - right); + out[0] = av_clip_int16(left + diff); + out[1] = av_clip_int16(right - diff); + break; + case HRTF_MIX_MATRIX2CH: + /* Do attempt any stereo expansion with matrix encoded + sources. The L, R channels are already stereo expanded + by the steering, any further stereo expansion will sound + very unnatural. */ + out[0] = av_clip_int16(left); + out[1] = av_clip_int16(right); + break; + } + + /* Next sample... */ + in = &in[data->nch]; + out = &out[af->data->nch]; + (s->cyc_pos)--; + if(s->cyc_pos < 0) + s->cyc_pos += dblen; + } + + /* Set output data */ + data->audio = af->data->audio; + data->len = data->len / data->nch * 2; + data->nch = 2; + + return data; +} + +static int allocate(af_hrtf_t *s) +{ + if ((s->lf = malloc(s->dlbuflen * sizeof(float))) == NULL) return -1; + if ((s->rf = malloc(s->dlbuflen * sizeof(float))) == NULL) return -1; + if ((s->lr = malloc(s->dlbuflen * sizeof(float))) == NULL) return -1; + if ((s->rr = malloc(s->dlbuflen * sizeof(float))) == NULL) return -1; + if ((s->cf = malloc(s->dlbuflen * sizeof(float))) == NULL) return -1; + if ((s->cr = malloc(s->dlbuflen * sizeof(float))) == NULL) return -1; + if ((s->ba_l = malloc(s->dlbuflen * sizeof(float))) == NULL) return -1; + if ((s->ba_r = malloc(s->dlbuflen * sizeof(float))) == NULL) return -1; + if ((s->fwrbuf_l = + malloc(s->dlbuflen * sizeof(float))) == NULL) return -1; + if ((s->fwrbuf_r = + malloc(s->dlbuflen * sizeof(float))) == NULL) return -1; + if ((s->fwrbuf_lr = + malloc(s->dlbuflen * sizeof(float))) == NULL) return -1; + if ((s->fwrbuf_rr = + malloc(s->dlbuflen * sizeof(float))) == NULL) return -1; + return 0; +} + +/* Allocate memory and set function pointers */ +static int af_open(struct af_instance* af) +{ + int i; + af_hrtf_t *s; + float fc; + + af->control = control; + af->uninit = uninit; + af->play = play; + af->mul = 1; + af->data = calloc(1, sizeof(struct mp_audio)); + af->setup = calloc(1, sizeof(af_hrtf_t)); + if((af->data == NULL) || (af->setup == NULL)) + return AF_ERROR; + + s = af->setup; + + s->dlbuflen = DELAYBUFLEN; + s->hrflen = HRTFFILTLEN; + s->basslen = BASSFILTLEN; + + s->cyc_pos = s->dlbuflen - 1; + /* With a full (two axis) steering matrix decoder, s->matrix_mode + should not be enabled lightly (it will also steer the Ls, Rs + channels). */ + s->matrix_mode = 0; + s->decode_mode = HRTF_MIX_51; + + s->print_flag = 1; + + if (allocate(s) != 0) { + mp_msg(MSGT_AFILTER, MSGL_ERR, "[hrtf] Memory allocation error.\n"); + return AF_ERROR; + } + + for(i = 0; i < s->dlbuflen; i++) + s->lf[i] = s->rf[i] = s->lr[i] = s->rr[i] = s->cf[i] = + s->cr[i] = 0; + + s->lr_fwr = + s->rr_fwr = 0; + + s->cf_ir = cf_filt + (s->cf_o = pulse_detect(cf_filt)); + s->af_ir = af_filt + (s->af_o = pulse_detect(af_filt)); + s->of_ir = of_filt + (s->of_o = pulse_detect(of_filt)); + s->ar_ir = ar_filt + (s->ar_o = pulse_detect(ar_filt)); + s->or_ir = or_filt + (s->or_o = pulse_detect(or_filt)); + s->cr_ir = cr_filt + (s->cr_o = pulse_detect(cr_filt)); + + if((s->ba_ir = malloc(s->basslen * sizeof(float))) == NULL) { + mp_msg(MSGT_AFILTER, MSGL_ERR, "[hrtf] Memory allocation error.\n"); + return AF_ERROR; + } + fc = 2.0 * BASSFILTFREQ / (float)af->data->rate; + if(af_filter_design_fir(s->basslen, s->ba_ir, &fc, LP | KAISER, 4 * M_PI) == + -1) { + mp_msg(MSGT_AFILTER, MSGL_ERR, "[hrtf] Unable to design low-pass " + "filter.\n"); + return AF_ERROR; + } + for(i = 0; i < s->basslen; i++) + s->ba_ir[i] *= BASSGAIN; + + return AF_OK; +} + +/* Description of this filter */ +struct af_info af_info_hrtf = { + "HRTF Headphone", + "hrtf", + "ylai", + "", + AF_FLAGS_REENTRANT, + af_open +}; diff --git a/audio/filter/af_hrtf.h b/audio/filter/af_hrtf.h new file mode 100644 index 0000000000..887310b57d --- /dev/null +++ b/audio/filter/af_hrtf.h @@ -0,0 +1,511 @@ +/* + * This file is part of MPlayer. + * + * MPlayer 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. + * + * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPLAYER_AF_HRTF_H +#define MPLAYER_AF_HRTF_H + +#define HRTF_MIX_51 0 +#define HRTF_MIX_STEREO 1 +#define HRTF_MIX_MATRIX2CH 2 + +/* Amplitude scaling factors */ +#define M17_0DB 0.1414213562 +#define M9_03DB 0.3535533906 +#define M6_99DB 0.4472135955 +#define M4_77DB 0.5773502692 +#define M3_01DB 0.7071067812 +#define M1_76DB 0.8164965809 + +#define DELAYBUFLEN 1024 /* Length of the delay buffer */ +#define HRTFFILTLEN 64 /* HRTF filter length */ +#define IRTHRESH 0.001 /* Impulse response pruning thresh. */ + +#define AMPLNORM M6_99DB /* Overall amplitude renormalization */ + +#define BASSFILTFREQ 180 /* Bass compensation filter cut (Hz) */ +#define BASSFILTLEN 193 /* Bass compensation filter length */ +#define BASSGAIN M_SQRT2 /* Bass compensation gain */ +#define BASSCROSS 0.35 /* Bass cross talk */ + +#define FWRDURATION 240 /* FWR average duration (samples) */ +#define MATREARDELAY 720 /* Matrix mode rear delay (samples) */ + +#define MATAGCTRIG 8.0 /* (Fuzzy) AGC trigger */ +#define MATAGCDECAY 1.0 /* AGC baseline decay rate (1/samp.) */ +#define MATAGCLOCK 0.2 /* AGC range (around 1) where the + matrix behaves passively */ +#define MATCOMPGAIN 0.37 /* Cross talk compensation gain, + 0.50 - 0.55 is full cancellation. */ + +#define CFECHODELAY 360 /* Center front echo delay (samples) */ +#define CFECHOAMPL M17_0DB /* Center front echo amplitude */ + +#define STEXPAND2 0.07 /* Stereo expansion / 2 */ + +/* Head related impulse response (HRIR) derived from KEMAR measurement + data by Bill Gardner <billg@media.mit.edu> and Keith Martin + <kdm@media.mit.edu> + + URL: http://sound.media.mit.edu/KEMAR.html + + Distributed under GPL with authors' permission +*/ + +/* EQUALIZED KEMAR HRIR + +How to generate these data: + +1. You need the MIT Media Lab's KEMAR data, read it into an software + capable of signal/time series analysis (like Mathematica, Matlab, + ...) + +2. Construct an equalizing FIR inverse filter by calculating the + transfer function of the front, same side impulse response, then + take 1 over the absolute magnitude. + +3. Cut the poles in the inverse filter's transfer function + specification by limiting the amplification to 2.5 (note, this + number assumes that you have correct signal processing + normalization of the Fourier transform). + +4. Design the FIR inverse filter by calculating the inverse Fourier + transform, then chopping the coefficients down to a reasonable + number (N = 15 is used here), apply the Kaiser-Bessel window (alpha + = 2 is used here). Note the objective is remove the color bias + only (as if you are using an equalizer), _not_ to do a full inverse + filtering. (Note: beta = pi*alpha in other notation.) + + For N = 15, alpha = 2, you should get the following impulse + response: + + 0.001001558668605168, 0.00698457265741865, 0.040453643039829436, + 0.012230541722147855, -0.11939760844854072, 0.16468099899755967, + -0.30297563073747436, 1.3140211791355982, -0.30297563073747436, + 0.16468099899755967, -0.11939760844854072, 0.012230541722147855, + 0.040453643039829436, 0.00698457265741865, 0.001001558668605168 + +5. Linearly convolve all KEMAR HRIR with this inverse filter. + +6. Resample from 44.1 kHz sampling frequency to 48 kHz. +*/ + +/* Center front (-5 degree) - not 0 degree in order to create a clear + front image from a finite distance */ +static const float cf_filt[128] = { + -0.00008638082319075036, 0.0003198059946385229, + -0.0005010631339162132, 0.0011424741331126876, + -0.001584220794688753, 0.001742715363246275, + -0.0011080796626780694, 0.0001651829990860167, + 0.005235028337314985, 0.0035223828473357776, + 0.010057681388431303, -0.033469432129545514, + 0.013391253316233523, 0.004858462839827063, + 0.08172161220103627, 0.26158596134500023, + -0.12420314583323326, -0.14298458356097565, + 0.14421897280453896, -0.1635792507629016, + -0.02187136722480014, 0.2426863044711817, + 0.07229814207917194, 0.0942742651913879, + 0.29856830878076834, 0.2944146162057754, + -0.12122157003421209, -0.19640092165631157, + 0.11623836502034968, -0.05794027397995521, + -0.34313138341973776, -0.19487516249168105, + 0.010118993953802401, -0.09460218797710966, + -0.16761521117359582, 0.004154461610153861, + 0.052768641758969316, -0.00041823982226147407, + 0.021634960445143514, 0.07562793486871108, + 0.08895407129506479, 0.039857755093416214, + 0.044257936180422945, 0.061557584906101664, + 0.015547268541895703, -0.023908191934932484, + 0.009498030443468223, 0.03816269003221337, + -0.009820500607303615, -0.042003975527908084, + -0.03335447117311547, -0.029294510859746596, + -0.05212623136198511, -0.073427547153205, + -0.061190797824958836, -0.04620925059966413, + -0.04204619420702159, -0.02331915902615157, + 0.00211481411477094, 0.00852563995740107, + 0.008766809731743399, 0.008666632180812078, + 0.018279202191625352, 0.02924332751289675, + 0.022293148257836494, 0.012362146008584188, + 0.008572582458807008, 0.006491370763597344, + -0.0019366944997535774, -0.006318669309634434, + -0.006457921690218669, -0.015050265524669974, + -0.02110660282616213, -0.017027809096377904, + -0.01651052305334348, -0.022770064150046673, + -0.01999875754219472, -0.012294792027337775, + -0.011506057031057188, -0.011448970577312903, + -0.004823572302580925, 0.0022451134042777883, + 0.004145473526859826, 0.005629030064546135, + 0.008588029213398785, 0.010092048834844231, + 0.007182013245552008, 0.0014600979508720656, + -0.0038314646272511756, -0.003443901997881347, + -0.0029483418254804047, -0.007609357112679647, + -0.006518368948030822, -0.004495803701497202, + -0.007109113004849672, -0.008346237278084265, + -0.005560847336252453, -0.002993453167553188, + -0.005122897816824269, -0.004389782626604215, + -0.0010912633695218108, -0.0019712029474458835, + -0.005870162265802235, -0.005626159534954128, + -0.00027254977910844407, 0.0013794425431633785, + -0.0005919083190430062, -0.0007861203545416682, + -0.0007049560240893946, -0.0032720188494468868, + -0.004460645567968504, -0.0032018528193571696, + -0.0030579229375062105, -0.003593998902656612, + -0.0038032977997776445, -0.004613776010454773, + -0.0059796549143736845, -0.00420126194319768, + -0.0012374419948287222, 0.0008572699213050608, + 0.0021490971020081094, 0.00047295283198381995, + -0.0009670277915884887, -0.001354440866080231, + -0.002962902746547851, -0.00533935813338141, + -0.005469203016468759, -0.004355784273189485 +}; +/* Front, same side (30 degree) */ +static const float af_filt[128] = { + -0.004140580614755493, 0.005790934614385445, + 0.003318916682081112, 0.014257145544366063, + 0.007328442487127339, -0.06550381777876194, + 0.03502225818161845, -0.013090579770708259, + 0.2517776798694195, 0.420770489950659, + -0.3418854608834852, -0.24371032493696737, + 0.04901356150030018, -0.1778083521632833, + 0.26448004245714163, 0.23245199964546834, + 0.033053145803936305, 0.46811222821062415, + 0.5359265986255611, -0.011912195468533787, + -0.39763432601411647, -0.034482864386898314, + 0.029445398240649626, -0.3850940407863262, + -0.3272328478175581, -0.14701421403616477, + -0.08522137400169517, -0.14936851633336035, + -0.09432605283433723, 0.0991200405937827, + 0.011075012089917331, -0.0051036489980274685, + 0.0933903289749412, 0.1344189369609565, + 0.10507466913017807, 0.04240159324684365, + 0.06559270110638656, 0.026984119875617524, + -0.03359846103238096, -0.018000197099174275, + 0.042031818548178244, 0.03849039666888434, + -0.02450829674011345, -0.03407882403088576, + -0.029230189282961977, -0.046964865291761734, + -0.09458258700116245, -0.1527349330901158, + -0.15411577687826097, -0.08761679790956928, + -0.033623549089171874, -0.007204768531481949, + 0.008273425020444852, 0.021368717994908505, + 0.04366608267875025, 0.05660907333076205, + 0.06775726495503939, 0.05621881735271431, + 0.03576231950669927, 0.02500825834889175, + 0.015423811076054601, 0.007903258334503761, + -0.0053873014137761945, -0.006987955469434698, + -0.012027972007598602, -0.025228281243816594, + -0.026225091797257318, -0.023809293997344882, + -0.03250172017712295, -0.03195696301067249, + -0.01784813952189948, -0.01663267233760342, + -0.016184530450468065, -0.011659883749357463, + -0.0035378511240219163, -0.0005485800790443406, + 0.0018432660108168625, 0.011634844139907534, + 0.018333603402051105, 0.020447379185133056, + 0.00850783664147828, 0.0004694148911037838, + -0.0017047130409786676, -0.0022409152834483997, + -0.000860472174892845, -0.004111075059198666, + -0.003527843382056666, -0.009640160874903018, + -0.01750044574231376, -0.015613389403672443, + -0.010790028120953001, -0.0095313499955768, + -0.007469721416726809, -0.0019186578145117315, + -0.00014977322572890802, -0.0029803838028179728, + -0.006520567233727221, 0.000035015132033882596, + 0.009245098100543752, 0.009896930052308656, + 0.008316744929565786, 0.004575207140193997, + -0.0000647420103997081, -0.004502916832871627, + -0.004225962213251224, -0.002886014126381486, + -0.006416834142585976, -0.007156609995423569, + -0.008840274447579472, -0.01441763751386817, + -0.015435817484659574, -0.00924487254924743, + -0.0021571721940235205, 0.0028540722992305453, + 0.00273577475088536, -0.000788412365513805, + -0.0032650029728365907, -0.003880217646231338, + -0.0035302087299613778, -0.0038531436176586246, + -0.0011921632190514074, -0.0020722967099011938, + -0.004985351145629344, -0.0042375588844648735, + -0.003030360463006021, -0.0014161075428041471, + -0.0005083025643192044, 0.00035096963769606926 +}; +/* Front, opposite (-30 degree) */ +static const float of_filt[128] = { + -0.000013472538374193126, -0.00008048061877079751, + 0.000043927265781258155, -0.000017931700794858892, + -0.000034774602476112886, -0.00009576223008735474, + 0.0001557797638630691, -0.00018742885883751094, + 0.00026512448626705716, -0.0001451040203319678, + -0.00008263233117758043, 0.0006486245853639179, + -0.0010631408451846698, 0.0026571994100746143, + 0.0014179177997092787, 0.0062326502956616256, + -0.008194149324545333, -0.006568029415878379, + 0.009538759710818582, 0.012309193558632693, + 0.12336638055838955, 0.046164307101829005, + -0.10228706407884815, 0.04047687260345798, + -0.00296595313977046, -0.07057949208414134, + 0.08172114840714612, 0.08736490764127891, + 0.05105250431333021, 0.11627179512747428, + 0.20717888490340705, 0.09375052213570291, + -0.09784374168330194, -0.010493571845901443, + 0.053131894303891716, -0.10157443971694806, + -0.16264032634244974, -0.05402369511361273, + -0.0274403608654217, -0.09860277022495063, + -0.06841875821090282, -0.004434574400066223, + -0.0005222661652743502, -0.006231881259827263, + 0.014410397820340159, 0.04885649512730243, + 0.04361962569042684, 0.03399214029009391, + 0.04961073933475931, 0.04067325604132289, + 0.007850647519227257, 0.004564440466905299, + 0.02257107958021618, 0.008183791928884486, + -0.014913479343180557, -0.018685938460856224, + -0.01745737397226911, -0.02327177054233603, + -0.03723048632685227, -0.044739390162299685, + -0.042651220125613766, -0.03730017561004743, + -0.029039465434276192, -0.01885087458914294, + -0.01207127752277769, -0.006779800724164512, + -0.001930416967444157, 0.000029454577995528385, + 0.0013822760965755472, 0.0014799128583230202, + 0.0002068200609199832, 0.0022254295286201083, + 0.005143858159434566, 0.0018580542060917013, + -0.0019426046325146259, -0.0014464042108543495, + -0.0034430083560735582, -0.009692758426099499, + -0.011840035292593485, -0.010716508855893968, + -0.012939889036853034, -0.0121846427935653, + -0.006198503315630782, -0.0023186723099380305, + -0.002679872498314837, -0.003086020446226295, + -0.0015709623347698936, -0.0008147490468332398, + -0.0012384575726770983, -0.0005212877089109362, + 0.0017707578744906142, 0.001324932723905786, + -0.0017023653780617696, -0.0045108927752919425, + -0.005422155613096912, -0.0039489323837623835, + -0.005295995750506547, -0.00629706566356189, + -0.004685732198036754, -0.0048076735568143, + -0.005978864279217503, -0.005928999306332966, + -0.004187703549017582, -0.003213999896976475, + -0.0028068699816073414, -0.0010889703907593833, + 0.0003276714243495386, -0.0013015007040186994, + -0.003208050402434782, -0.0025115319088208545, + -0.0013787553006401076, -0.0018279087370218635, + -0.0025904836507747754, -0.002071221947222004, + -0.0026424212922485594, -0.0039837031817577, + -0.0041635566057380965, -0.004355223489150822, + -0.004350395332709937, -0.0036693292471930263, + -0.003386384394185026, -0.003972568655001128, + -0.004332336840023821, -0.002648767912111827, + -0.001384410080218114, -0.0011353792711849466, + -0.0013726264946164232, -0.0020075119315034313 +}; +/* Rear, same side (135 degree) */ +static const float ar_filt[128] = { + 0.004573315040810066, 0.0013592578059426913, + 0.01553271930902704, -0.0002356117224941317, + -0.05746098219774702, 0.03430688963370445, + 0.00808371687447385, 0.21893535841158596, + 0.2984357591724814, -0.3302799746504719, + -0.3194029149806245, 0.21633225051331056, + 0.24371260938097083, -0.08843705549751085, + 0.03939684701343366, 0.45386926431114494, + 0.07599118140753386, -0.18114706160474578, + 0.285640624686038, 0.4049515236666218, + -0.05347890222071792, -0.31464359045319074, + -0.1033502246468194, -0.04553593949283157, + -0.1880747731157464, -0.13629090230626037, + -0.10435789106123239, -0.19818232801888755, + -0.16701805476330397, -0.022793111199284, + 0.058049696762683685, 0.007048321372693906, + -0.002966419183225961, 0.10140569697797763, + 0.11648999956673124, 0.05218347182779882, + 0.028427001212735392, 0.04151900310166159, + -0.0006960604221423734, -0.05898623212226975, + -0.03801934531173312, -0.029306970535287986, + -0.04549125782835908, -0.0599222718506552, + -0.058299618975430116, -0.03765579129720727, + -0.03559302657499581, -0.020647901025903054, + -0.005720957338744348, -0.0041915732688915545, + -0.0011470880098346143, 0.008737404798553, + 0.023444168098121512, 0.024204226042172663, + 0.01894897166475026, 0.020807655257479588, + 0.021570431128040954, 0.006800556178576289, + -0.009000089216921362, -0.010969824547067934, + -0.0033653428332822374, -0.012676936164668659, + -0.026739938673413587, -0.023427869194287573, + -0.023302007105117244, -0.023647155590533712, + -0.021289317515613106, -0.009120487305069884, + 0.0009251551667728967, 0.00004285344125653763, + -0.00009042365479456271, 0.00022573242339446494, + 0.00720168491586098, 0.007111875505402431, + 0.003186514817683482, 0.00810087718334745, + 0.012619557025922575, 0.007854726400013397, + -0.0024013592881066267, -0.001452457473161119, + -0.0025535188366093945, -0.012428911627809337, + -0.013729251536694145, -0.0070099675146427344, + -0.007165284278706593, -0.01639289295622301, + -0.015831795079778305, -0.007305768485523729, + -0.003608863157004021, -0.0032640528878698084, + 0.0030901263998481944, 0.00749497566124848, + 0.002515185532327241, 0.00004840875738621367, + 0.0017596043486043966, 0.0046229941553338144, + 0.0034259167322926096, 0.003707347634186093, + 0.0035584806528586328, -0.0019078936035275198, + -0.006057343815214898, -0.0069262470468817, + -0.004345020728618624, -0.004177623355574794, + -0.005373506556122508, -0.006624933928893836, + -0.008679541408588839, -0.010718719681595322, + -0.011392246979594496, -0.007893917064389902, + -0.0027572935365832536, -0.00006064707149834412, + -0.0012512537319656323, -0.0024501501002409786, + -0.0022106788572895998, -0.00234124933370301, + -0.0008953445167066823, 0.0005393670625637734, + -0.00033175600142209297, -0.004023994309351289, + -0.008655472335784443, -0.009899957354849682, + -0.008664952919996412, -0.00553483124503576, + -0.003735336089277662, -0.002754824348643885, + -0.0026884314856593368, -0.004084181815125924 +}; +/* Rear, opposite (-135 degree) */ +static const float or_filt[128] = { + 0.0001220944028243897, -0.000021785381808441314, + 5.823057988603169e-6, -0.00001217768176447613, + -0.00006123604397345513, 5.574117262531134e-6, + -0.00004935331914366778, 1.1771577934768211e-6, + -0.000059236211621095756, 9.503536190497286e-6, + -0.0001494445696103564, 0.00012248858284145305, + -0.0000963975321456313, 6.017905197665205e-6, + 0.00003353395360402643, -0.0001931511015359506, + 0.0005113536523931485, -0.0005676652619386114, + 0.0012057159755477467, 0.0009370492250339692, + 0.004596472288877981, -0.0018831773384237068, + -0.008208535225621212, 0.0038178646400751056, + 0.008726517739105965, 0.06664363898418262, + 0.06788684221502142, -0.04492315162807267, + -0.04019906311255255, 0.026203059677375153, + 0.013678129114847544, -0.014334962223993527, + 0.010141709596167392, 0.11559131576945537, + 0.1586402064538425, 0.059975334707967023, + 0.004671725963777715, 0.031498678282775874, + 0.014338626006524587, -0.014749719448472231, + -0.02055508237941379, -0.05407690143992048, + -0.07767559836886143, -0.05029091786216801, + -0.030808335706574427, -0.03401958135442541, + -0.030520368430288967, -0.014168302104259355, + 0.011907621259989802, 0.014286081013069, + 0.006377467879613449, 0.018546823568277478, + 0.028297012771618273, 0.025222339408338186, + 0.021931611353415138, 0.019708894333646355, + 0.01729258494072014, 0.017468204169564034, + 0.009729094845051928, -0.002976992018531901, + -0.00956986166277019, -0.016125733548332074, + -0.02934094241442545, -0.04133767871051455, + -0.043536981145416466, -0.0385966307108608, + -0.02784453599342459, -0.018995135307247116, + -0.012849534096536747, -0.004437491064613308, + 0.00028385411598204655, 0.003776874988516643, + 0.008069432041547833, 0.008764754183751848, + 0.008020908861878062, 0.006830351461360802, + 0.002218330884267235, -0.0020478725582339444, + -0.003997428121462543, -0.007066287373515421, + -0.00940847412544698, -0.010938998446237963, + -0.011775483016151306, -0.011391103919484287, + -0.010586061195163017, -0.009842793078929053, + -0.007753202010139829, -0.00569213732353025, + -0.006506783349722073, -0.005346134281903736, + -0.003913089814898934, -0.0036091443854759727, + -0.0020328564301266287, 0.00017932870773467683, + 0.0032779786679056357, 0.003969695813293966, + 0.0020339334412434987, -0.00011345940675415259, + -0.0018344103399567666, -0.003556764701666365, + -0.004263523639408391, -0.002940568582022133, + -0.0034341188272627556, -0.006023399920020824, + -0.0077456903203677865, -0.007912219312377842, + -0.00625202770436523, -0.00530785086116117, + -0.005569722659634311, -0.004664448462594344, + -0.0037747773914077747, -0.004175649656985592, + -0.004659601521384289, -0.005008602967819641, + -0.004730625079902729, -0.0034039554356604146, + -0.0017110333873406587, -0.0006091938771510242, + -0.0016051679050678297, -0.003312864664007262, + -0.004505512715977288, -0.004152222189861692, + -0.003218596419678823, -0.0027277806209877343, + -0.001715005444317267, -0.0012589960071233749, + -0.001852908777923165, -0.002540339553144362 +}; +/* Center rear (180 degree) */ +static const float cr_filt[128] = { + -0.00005989110716536726, -0.00022790291829128702, + 0.0002659166098971966, -0.0003774772716776257, + 0.0004540309551867803, -0.000420238187386368, + 0.00025518536450885686, 0.00028285526288953955, + -0.001016391007574093, 0.0028634984299063795, + 0.0021574799687976045, 0.01035121276682072, + -0.010481720917298163, -0.013197198495899292, + 0.0031928225328717195, 0.02903137843618603, + 0.1632429772511569, 0.1047487989875262, + -0.10464685060623742, -0.09260196288035998, + -0.007514241993554443, 0.013596249226741712, + -0.019876166508450258, 0.1255626123599804, + 0.3648170359521724, 0.19458249753223478, + -0.04434070930031298, 0.046582528121935265, + 0.09484427347230277, -0.03137795311969644, + -0.10297437925363695, -0.09351091015917065, + -0.1129521092162064, -0.14925322995658827, + -0.1231466295584665, -0.06356719756705227, + -0.05442277895126282, -0.07568433015661316, + -0.023314932828602003, 0.04260950721318558, + 0.02249026315598923, 0.02048195669571197, + 0.05651342117268278, 0.05885038917623213, + 0.03797102097397795, 0.011767394419953451, + 0.00560502503429481, 0.005051125343961189, + -0.012925933188033823, -0.023918884651306566, + -0.013251659441678816, -0.010694772488866284, + -0.03080486448617846, -0.03661278237783158, + -0.0379227303416262, -0.042189005718490775, + -0.026595666344596286, -0.009759025956801257, + -0.002064986663513004, -0.002420117028098389, + -0.006629991977552491, 0.004619970897631026, + 0.019450642967537877, 0.0173521119057514, + 0.017641425439988062, 0.02270029598048491, + 0.018976431925275348, 0.009299852902290885, + -0.001695039371619912, -0.00675162574265618, + -0.009380968871003034, -0.011208396125485165, + -0.01308640049201482, -0.0165636375633249, + -0.022004099870933345, -0.025173458684139286, + -0.016918759559175375, -0.00865150653575917, + -0.006999929082792643, -0.005454830010518988, + -0.0021129521131095317, 0.00018717090054046307, + -0.0002864344788569993, 0.0017615225381095569, + 0.006985907557802283, 0.010577308310476465, + 0.006466104789306027, -0.0014988738575948326, + -0.0039669755229277195, -0.0065156971200080235, + -0.009343206924192169, -0.0076430644693577495, + -0.004395214976600924, -0.003052735340422483, + -0.007019103043066595, -0.00974109267696527, + -0.007968015032797376, -0.007801513845528344, + -0.007535748903681969, -0.003543341967287925, + 0.0015083125553729722, 0.0023345972556147025, + -0.0010043623069557037, -0.0025295765105203746, + -0.0023701840891643634, -0.0005908186035024362, + 0.0029826252289082847, 0.004829048542117764, + 0.004488360022902081, 0.00002643748103005408, + -0.0042100779212597295, -0.006170600558114495, + -0.007267149164680168, -0.006825522903494639, + -0.006899834372739123, -0.0073493916110062675, + -0.009554351265163382, -0.011790297433830197, + -0.010645796603734424, -0.0064661575394022106, + -0.002026743466524137, -0.0004337034584909932, + -0.0011172647031654614, -0.0017947816283674731, + -0.00255615052036616, -0.0017721562881944813, + -0.0002379619297227554, 0.0007130120121089036 +}; + +#endif /* MPLAYER_AF_HRTF_H */ diff --git a/audio/filter/af_karaoke.c b/audio/filter/af_karaoke.c new file mode 100644 index 0000000000..965eb8f40d --- /dev/null +++ b/audio/filter/af_karaoke.c @@ -0,0 +1,98 @@ +/* + * simple voice removal filter + * + * copyright (c) 2006 Reynaldo H. Verdejo Pinochet + * Based on code by Alex Beregszaszi for his 'center' filter. + * + * This file is part of MPlayer. + * + * MPlayer 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. + * + * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "af.h" + +// Data for specific instances of this filter + +// Initialization and runtime control +static int control(struct af_instance* af, int cmd, void* arg) +{ + switch(cmd){ + case AF_CONTROL_REINIT: + af->data->rate = ((struct mp_audio*)arg)->rate; + af->data->nch = ((struct mp_audio*)arg)->nch; + af->data->format= AF_FORMAT_FLOAT_NE; + af->data->bps = 4; + return af_test_output(af,(struct mp_audio*)arg); + } + return AF_UNKNOWN; +} + +// Deallocate memory +static void uninit(struct af_instance* af) +{ + free(af->data); +} + +// Filter data through filter +static struct mp_audio* play(struct af_instance* af, struct mp_audio* data) +{ + struct mp_audio* c = data; // Current working data + float* a = c->audio; // Audio data + int len = c->len/4; // Number of samples in current audio block + int nch = c->nch; // Number of channels + register int i; + + /* + FIXME1 add a low band pass filter to avoid suppressing + centered bass/drums + FIXME2 better calculated* attenuation factor + */ + + for(i=0;i<len;i+=nch) + { + a[i] = (a[i] - a[i+1]) * 0.7; + a[i+1]=a[i]; + } + + return c; +} + +// Allocate memory and set function pointers +static int af_open(struct af_instance* af){ + af->control = control; + af->uninit = uninit; + af->play = play; + af->mul = 1; + af->data = calloc(1,sizeof(struct mp_audio)); + + if(af->data == NULL) + return AF_ERROR; + + return AF_OK; +} + +// Description of this filter +struct af_info af_info_karaoke = { + "Simple karaoke/voice-removal audio filter", + "karaoke", + "Reynaldo H. Verdejo Pinochet", + "", + AF_FLAGS_NOT_REENTRANT, + af_open +}; diff --git a/audio/filter/af_ladspa.c b/audio/filter/af_ladspa.c new file mode 100644 index 0000000000..c1b3f24360 --- /dev/null +++ b/audio/filter/af_ladspa.c @@ -0,0 +1,915 @@ +/* + * LADSPA plugin loader + * + * Written by Ivo van Poorten <ivop@euronet.nl> + * Copyright (C) 2004, 2005 + * + * This file is part of MPlayer. + * + * MPlayer 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. + * + * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* ------------------------------------------------------------------------- */ + +/* Global Includes */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <inttypes.h> +#include <math.h> +#include <limits.h> + +#include <dlfcn.h> +#include <ladspa.h> + +/* ------------------------------------------------------------------------- */ + +/* Local Includes */ + +#include "af.h" + +/* ------------------------------------------------------------------------- */ + +/* Filter specific data */ + +typedef struct af_ladspa_s +{ + int status; /**< Status of the filter. + * Either AF_OK or AF_ERROR + * Because MPlayer re-inits audio filters that + * _clearly_ returned AF_ERROR anyway, I use this + * in play() to skip the processing and return + * the data unchanged. + */ + + int activated; /**< 0 or 1. Activate LADSPA filters only once, even + * if the buffers get resized, to avoid a stuttering + * filter. + */ + + char *file; + char *label; + + char *myname; /**< It's easy to have a concatenation of file and label */ + + void *libhandle; + const LADSPA_Descriptor *plugin_descriptor; + + int nports; + + int ninputs; + int *inputs; + + int noutputs; + int *outputs; + + int ninputcontrols; + int *inputcontrolsmap; /**< Map input port number [0-] to actual port */ + float *inputcontrols; + + int noutputcontrols; + int *outputcontrolsmap; + float *outputcontrols; + + int nch; /**< number of channels */ + int bufsize; + float **inbufs; + float **outbufs; + LADSPA_Handle *chhandles; + +} af_ladspa_t; + +/* ------------------------------------------------------------------------- */ + +static int af_open(struct af_instance *af); +static int af_ladspa_malloc_failed(char*); + +/* ------------------------------------------------------------------------- */ + +/* Description */ + +struct af_info af_info_ladspa = { + "LADSPA plugin loader", + "ladspa", + "Ivo van Poorten", + "", + AF_FLAGS_REENTRANT, + af_open +}; + +/* ------------------------------------------------------------------------- */ + +/* By lack of a better word (in my vocabulary) this is called 'parse'. + * Feel free to suggest an alternative. + */ + +/** \brief Check for inputs, outputs and controls of a given filter. + * + * This function counts and checks all input, output and control ports + * of the filter that was loaded. If it turns out to be a valid + * filter for MPlayer use, it prints out a list of all controls and + * the corresponding range of its value at message level MSGL_V. + * + * \param setup Current setup of the filter. Must have its + * plugin_descriptor set! + * + * \return Returns AF_OK if it has a valid input/output/controls + * configuration. Else, it returns AF_ERROR. + */ + +static int af_ladspa_parse_plugin(af_ladspa_t *setup) { + int p, i; + const LADSPA_Descriptor *pdes = setup->plugin_descriptor; + LADSPA_PortDescriptor d; + LADSPA_PortRangeHint hint; + + if (!setup->libhandle) + return AF_ERROR; /* only call parse after a succesful load */ + if (!setup->plugin_descriptor) + return AF_ERROR; /* same as above */ + + /* let's do it */ + + setup->nports = pdes->PortCount; + + /* allocate memory for all inputs/outputs/controls */ + + setup->inputs = calloc(setup->nports, sizeof(int)); + if (!setup->inputs) return af_ladspa_malloc_failed(setup->myname); + + setup->outputs = calloc(setup->nports, sizeof(int)); + if (!setup->outputs) return af_ladspa_malloc_failed(setup->myname); + + setup->inputcontrolsmap = calloc(setup->nports, sizeof(int)); + if (!setup->inputcontrolsmap) return af_ladspa_malloc_failed(setup->myname); + + setup->inputcontrols = calloc(setup->nports, sizeof(float)); + if (!setup->inputcontrols) return af_ladspa_malloc_failed(setup->myname); + + setup->outputcontrolsmap = calloc(setup->nports, sizeof(int)); + if (!setup->outputcontrolsmap) return af_ladspa_malloc_failed(setup->myname); + + setup->outputcontrols = calloc(setup->nports, sizeof(float)); + if (!setup->outputcontrols) return af_ladspa_malloc_failed(setup->myname); + + /* set counts to zero */ + + setup->ninputs = 0; + setup->noutputs = 0; + setup->ninputcontrols = 0; + setup->noutputcontrols = 0; + + /* check all ports, see what type it is and set variables according to + * what we have found + */ + + for (p=0; p<setup->nports; p++) { + d = pdes->PortDescriptors[p]; + + if (LADSPA_IS_PORT_AUDIO(d)) { + if (LADSPA_IS_PORT_INPUT(d)) { + setup->inputs[setup->ninputs] = p; + setup->ninputs++; + } else if (LADSPA_IS_PORT_OUTPUT(d)) { + setup->outputs[setup->noutputs] = p; + setup->noutputs++; + } + } + + if (LADSPA_IS_PORT_CONTROL(d)) { + if (LADSPA_IS_PORT_INPUT(d)) { + setup->inputcontrolsmap[setup->ninputcontrols] = p; + setup->ninputcontrols++; + /* set control to zero. set values after reading the rest + * of the suboptions and check LADSPA_?_HINT's later. + */ + setup->inputcontrols[p] = 0.0f; + } else if (LADSPA_IS_PORT_OUTPUT(d)) { + /* read and handle these too, otherwise filters that have them + * will sig11 + */ + setup->outputcontrolsmap[setup->noutputcontrols]=p; + setup->noutputcontrols++; + setup->outputcontrols[p] = 0.0f; + } + } + + } + + if (setup->ninputs == 0) { + mp_msg(MSGT_AFILTER, MSGL_WARN, "%s: %s\n", setup->myname, + _("WARNING! This LADSPA plugin has no audio inputs.\n The incoming audio signal will be lost.")); + } else if (setup->ninputs == 1) { + mp_msg(MSGT_AFILTER, MSGL_V, "%s: this is a mono effect\n", setup->myname); + } else if (setup->ninputs == 2) { + mp_msg(MSGT_AFILTER, MSGL_V, "%s: this is a stereo effect\n", setup->myname); + } else { + mp_msg(MSGT_AFILTER, MSGL_V, "%s: this is a %i-channel effect, " + "support is experimental\n", setup->myname, setup->ninputs); + } + + if (setup->noutputs == 0) { + mp_msg(MSGT_AFILTER, MSGL_ERR, "%s: %s\n", setup->myname, + _("This LADSPA plugin has no audio outputs.")); + return AF_ERROR; + } + + if (setup->noutputs != setup->ninputs ) { + mp_msg(MSGT_AFILTER, MSGL_ERR, "%s: %s\n", setup->myname, + _("The number of audio inputs and audio outputs of the LADSPA plugin differ.")); + return AF_ERROR; + } + + mp_msg(MSGT_AFILTER, MSGL_V, "%s: this plugin has %d input control(s)\n", + setup->myname, setup->ninputcontrols); + + /* Print list of controls and its range of values it accepts */ + + for (i=0; i<setup->ninputcontrols; i++) { + p = setup->inputcontrolsmap[i]; + hint = pdes->PortRangeHints[p]; + mp_msg(MSGT_AFILTER, MSGL_V, " --- %d %s [", i, pdes->PortNames[p]); + + if (LADSPA_IS_HINT_BOUNDED_BELOW(hint.HintDescriptor)) { + mp_msg(MSGT_AFILTER, MSGL_V, "%0.2f , ", hint.LowerBound); + } else { + mp_msg(MSGT_AFILTER, MSGL_V, "... , "); + } + + if (LADSPA_IS_HINT_BOUNDED_ABOVE(hint.HintDescriptor)) { + mp_msg(MSGT_AFILTER, MSGL_V, "%0.2f]\n", hint.UpperBound); + } else { + mp_msg(MSGT_AFILTER, MSGL_V, "...]\n"); + } + + } + + return AF_OK; +} + +/* ------------------------------------------------------------------------- */ + +/* This function might "slightly" look like dlopenLADSPA in the LADSPA SDK :-) + * But, I changed a few things, because imho it was broken. It did not support + * relative paths, only absolute paths that start with a / + * I think ../../some/dir/foobar.so is just as valid. And if one wants to call + * his library '...somename...so' he's crazy, but it should be allowed. + * So, search the path first, try plain *filename later. + * Also, try adding .so first! I like the recursion the SDK did, but it's + * better the other way around. -af ladspa=cmt:amp_stereo:0.5 is easier to type + * than -af ladspa=cmt.so:amp_stereo:0.5 :-)) + */ + +/** \brief dlopen() wrapper + * + * This is a wrapper around dlopen(). It tries various variations of the + * filename (with or without the addition of the .so extension) in various + * directories specified by the LADSPA_PATH environment variable. If all fails + * it tries the filename directly as an absolute path to the library. + * + * \param filename filename of the library to load. + * \param flag see dlopen(3) for a description of the flags. + * + * \return returns a pointer to the loaded library on success, or + * NULL if it fails to load. + */ + +static void* mydlopen(const char *filename, int flag) { + char *buf; + const char *end, *start, *ladspapath; + int endsinso, needslash; + size_t filenamelen; + void *result = NULL; + +#if defined(__MINGW32__) || defined(__CYGWIN__) + /* For Windows there's only absolute path support. + * If you have a Windows machine, feel free to fix this. + * (path separator, shared objects extension, et cetera). */ + mp_msg(MSGT_AFILTER, MSGL_V, "\ton windows, only absolute pathnames " + "are supported\n"); + mp_msg(MSGT_AFILTER, MSGL_V, "\ttrying %s\n", filename); + return dlopen(filename, flag); +#endif + + filenamelen = strlen(filename); + + endsinso = 0; + if (filenamelen > 3) + endsinso = (strcmp(filename+filenamelen-3, ".so") == 0); + if (!endsinso) { + buf=malloc(filenamelen+4); + strcpy(buf, filename); + strcat(buf, ".so"); + result=mydlopen(buf, flag); + free(buf); + } + + if (result) + return result; + + ladspapath=getenv("LADSPA_PATH"); + + if (ladspapath) { + + start=ladspapath; + while (*start != '\0') { + end=start; + while ( (*end != ':') && (*end != '\0') ) + end++; + + buf=malloc(filenamelen + 2 + (end-start) ); + if (end > start) + strncpy(buf, start, end-start); + needslash=0; + if (end > start) + if (*(end-1) != '/') { + needslash = 1; + buf[end-start] = '/'; + } + strcpy(buf+needslash+(end-start), filename); + + mp_msg(MSGT_AFILTER, MSGL_V, "\ttrying %s\n", buf); + result=dlopen(buf, flag); + + free(buf); + if (result) + return result; + + start = end; + if (*start == ':') + start++; + } /* end while there's still more in the path */ + } /* end if there's a ladspapath */ + + /* last resort, just open it again, so the dlerror() message is correct */ + mp_msg(MSGT_AFILTER, MSGL_V, "\ttrying %s\n", filename); + return dlopen(filename,flag); +} + +/* ------------------------------------------------------------------------- */ + +/** \brief Load a LADSPA Plugin + * + * This function loads the LADSPA plugin specified by the file and label + * that are present in the setup variable. First, it loads the library. + * If it fails, it returns AF_ERROR. If not, it continues to look for the + * specified label. If it finds it, it sets the plugin_descriptor inside + * setup and returns AF_OK. If it doesn't, it returns AF_ERROR. Special case + * is a label called 'help'. In that case, it prints a list of all available + * labels (filters) in the library specified by file. + * + * \param setup Current setup of the filter. Contains filename and label. + * + * \return Either AF_ERROR or AF_OK, depending on the success of the operation. + */ + +static int af_ladspa_load_plugin(af_ladspa_t *setup) { + const LADSPA_Descriptor *ladspa_descriptor; + LADSPA_Descriptor_Function descriptor_function; + int i; + + /* load library */ + mp_msg(MSGT_AFILTER, MSGL_V, "%s: loading ladspa plugin library %s\n", + setup->myname, setup->file); + + setup->libhandle = mydlopen(setup->file, RTLD_NOW); + + if (!setup->libhandle) { + mp_msg(MSGT_AFILTER, MSGL_ERR, "%s: %s %s\n\t%s\n", setup->myname, + _("failed to load"), setup->file, dlerror() ); + return AF_ERROR; + } + + mp_msg(MSGT_AFILTER, MSGL_V, "%s: library found.\n", setup->myname); + + /* find descriptor function */ + dlerror(); + descriptor_function = (LADSPA_Descriptor_Function) dlsym (setup->libhandle, + "ladspa_descriptor"); + + if (!descriptor_function) { + mp_msg(MSGT_AFILTER, MSGL_ERR, "%s: %s\n\t%s\n", setup->myname, + _("Couldn't find ladspa_descriptor() function in the specified library file."), dlerror()); + return AF_ERROR; + } + + /* if label == help, list all labels in library and exit */ + + if (strcmp(setup->label, "help") == 0) { + mp_msg(MSGT_AFILTER, MSGL_INFO, "%s: %s %s:\n", setup->myname, + _("available labels in"), setup->file); + for (i=0; ; i++) { + ladspa_descriptor = descriptor_function(i); + if (ladspa_descriptor == NULL) { + return AF_ERROR; + } + mp_msg(MSGT_AFILTER, MSGL_INFO, " %-16s - %s (%lu)\n", + ladspa_descriptor->Label, + ladspa_descriptor->Name, + ladspa_descriptor->UniqueID); + } + } + + mp_msg(MSGT_AFILTER, MSGL_V, "%s: looking for label\n", setup->myname); + + /* find label in library */ + for (i=0; ; i++) { + ladspa_descriptor = descriptor_function(i); + if (ladspa_descriptor == NULL) { + mp_msg(MSGT_AFILTER, MSGL_ERR, "%s: %s\n", setup->myname, + _("Couldn't find label in plugin library.")); + return AF_ERROR; + } + if (strcmp(ladspa_descriptor->Label, setup->label) == 0) { + setup->plugin_descriptor = ladspa_descriptor; + mp_msg(MSGT_AFILTER, MSGL_V, "%s: %s found\n", setup->myname, + setup->label); + return AF_OK; + } + } + + return AF_OK; +} + +/* ------------------------------------------------------------------------- */ + +/** \brief Print a malloc() failed error message. + * + * Generic function which can be called if a call to malloc(), calloc(), + * strdup(), et cetera, failed. It prints a message to the console and + * returns AF_ERROR. + * + * \return AF_ERROR + */ + +static int af_ladspa_malloc_failed(char *myname) { + mp_msg(MSGT_AFILTER, MSGL_ERR, "%s: %s", myname, "Memory allocation failed.\n"); + return AF_ERROR; +} + +/* ------------------------------------------------------------------------- */ + +/** \brief Controls the filter. + * + * Control the behaviour of the filter. + * + * Commands: + * CONTROL_REINIT Sets the af structure with proper values for number + * of channels, rate, format, et cetera. + * CONTROL_COMMAND_LINE Parses the suboptions given to this filter + * through arg. It first parses the filename and + * the label. After that, it loads the filter + * and finds out its proprties. Then in continues + * parsing the controls given on the commandline, + * if any are needed. + * + * \param af Audio filter instance + * \param cmd The command to execute + * \param arg Arguments to the command + * + * \return Either AF_ERROR or AF_OK, depending on the succes of the + * operation. + */ + +static int control(struct af_instance *af, int cmd, void *arg) { + af_ladspa_t *setup = (af_ladspa_t*) af->setup; + int i, r; + float val; + + switch(cmd) { + case AF_CONTROL_REINIT: + mp_msg(MSGT_AFILTER, MSGL_V, "%s: (re)init\n", setup->myname); + + if (!arg) return AF_ERROR; + + /* accept FLOAT, let af_format do conversion */ + + af->data->rate = ((struct mp_audio*)arg)->rate; + af->data->nch = ((struct mp_audio*)arg)->nch; + af->data->format = AF_FORMAT_FLOAT_NE; + af->data->bps = 4; + + /* arg->len is not set here yet, so init of buffers and connecting the + * filter, has to be done in play() :-/ + */ + + return af_test_output(af, (struct mp_audio*)arg); + case AF_CONTROL_COMMAND_LINE: { + char *buf; + char *line = arg; + + mp_msg(MSGT_AFILTER, MSGL_V, "%s: parse suboptions\n", setup->myname); + + /* suboption parser here! + * format is (ladspa=)file:label:controls.... + */ + + if (!line) { + mp_msg(MSGT_AFILTER, MSGL_ERR, "%s: %s\n", setup->myname, + _("No suboptions specified.")); + return AF_ERROR; + } + + buf = malloc(strlen(line)+1); + if (!buf) return af_ladspa_malloc_failed(setup->myname); + + /* file... */ + buf[0] = '\0'; + sscanf(line, "%[^:]", buf); + if (buf[0] == '\0') { + mp_msg(MSGT_AFILTER, MSGL_ERR, "%s: %s\n", setup->myname, + _("No library file specified.")); + free(buf); + return AF_ERROR; + } + line += strlen(buf); + setup->file = strdup(buf); + if (!setup->file) return af_ladspa_malloc_failed(setup->myname); + mp_msg(MSGT_AFILTER, MSGL_V, "%s: file --> %s\n", setup->myname, + setup->file); + if (*line != '\0') line++; /* read ':' */ + + /* label... */ + buf[0] = '\0'; + sscanf(line, "%[^:]", buf); + if (buf[0] == '\0') { + mp_msg(MSGT_AFILTER, MSGL_ERR, "%s: %s\n", setup->myname, + _("No filter label specified.")); + free(buf); + return AF_ERROR; + } + line += strlen(buf); + setup->label = strdup(buf); + if (!setup->label) return af_ladspa_malloc_failed(setup->myname); + mp_msg(MSGT_AFILTER, MSGL_V, "%s: label --> %s\n", setup->myname, + setup->label); +/* if (*line != '0') line++; */ /* read ':' */ + + free(buf); /* no longer needed */ + + /* set new setup->myname */ + + free(setup->myname); + setup->myname = calloc(strlen(af_info_ladspa.name)+strlen(setup->file)+ + strlen(setup->label)+6, 1); + snprintf(setup->myname, strlen(af_info_ladspa.name)+ + strlen(setup->file)+strlen(setup->label)+6, "%s: (%s:%s)", + af_info_ladspa.name, setup->file, setup->label); + + /* load plugin :) */ + + if ( af_ladspa_load_plugin(setup) != AF_OK ) + return AF_ERROR; + + /* see what inputs, outputs and controls this plugin has */ + if ( af_ladspa_parse_plugin(setup) != AF_OK ) + return AF_ERROR; + + /* ninputcontrols is set by now, read control values from arg */ + + for(i=0; i<setup->ninputcontrols; i++) { + if (!line || *line != ':') { + mp_msg(MSGT_AFILTER, MSGL_ERR, "%s: %s\n", setup->myname, + _("Not enough controls specified on the command line.")); + return AF_ERROR; + } + line++; + r = sscanf(line, "%f", &val); + if (r!=1) { + mp_msg(MSGT_AFILTER, MSGL_ERR, "%s: %s\n", setup->myname, + _("Not enough controls specified on the command line.")); + return AF_ERROR; + } + setup->inputcontrols[setup->inputcontrolsmap[i]] = val; + line = strchr(line, ':'); + } + + mp_msg(MSGT_AFILTER, MSGL_V, "%s: input controls: ", setup->myname); + for(i=0; i<setup->ninputcontrols; i++) { + mp_msg(MSGT_AFILTER, MSGL_V, "%0.4f ", + setup->inputcontrols[setup->inputcontrolsmap[i]]); + } + mp_msg(MSGT_AFILTER, MSGL_V, "\n"); + + /* check boundaries of inputcontrols */ + + mp_msg(MSGT_AFILTER, MSGL_V, "%s: checking boundaries of input controls\n", + setup->myname); + for(i=0; i<setup->ninputcontrols; i++) { + int p = setup->inputcontrolsmap[i]; + LADSPA_PortRangeHint hint = + setup->plugin_descriptor->PortRangeHints[p]; + val = setup->inputcontrols[p]; + + if (LADSPA_IS_HINT_BOUNDED_BELOW(hint.HintDescriptor) && + val < hint.LowerBound) { + mp_tmsg(MSGT_AFILTER, MSGL_ERR, "%s: Input control #%d is below lower boundary of %0.4f.\n", + setup->myname, i, hint.LowerBound); + return AF_ERROR; + } + if (LADSPA_IS_HINT_BOUNDED_ABOVE(hint.HintDescriptor) && + val > hint.UpperBound) { + mp_tmsg(MSGT_AFILTER, MSGL_ERR, "%s: Input control #%d is above upper boundary of %0.4f.\n", + setup->myname, i, hint.UpperBound); + return AF_ERROR; + } + } + mp_msg(MSGT_AFILTER, MSGL_V, "%s: all controls have sane values\n", + setup->myname); + + /* All is well! */ + setup->status = AF_OK; + + return AF_OK; } + } + + return AF_UNKNOWN; +} + +/* ------------------------------------------------------------------------- */ + +/** \brief Uninitialise the LADSPA Plugin Loader filter. + * + * This function deactivates the plugin(s), cleans up, frees all allocated + * memory and exits. + * + * \return No return value. + */ + +static void uninit(struct af_instance *af) { + int i; + + free(af->data); + if (af->setup) { + af_ladspa_t *setup = (af_ladspa_t*) af->setup; + const LADSPA_Descriptor *pdes = setup->plugin_descriptor; + + if (setup->myname) { + mp_msg(MSGT_AFILTER, MSGL_V, "%s: cleaning up\n", setup->myname); + free(setup->myname); + } + + if (setup->chhandles) { + for(i=0; i<setup->nch; i+=setup->ninputs) { + if (pdes->deactivate) pdes->deactivate(setup->chhandles[i]); + if (pdes->cleanup) pdes->cleanup(setup->chhandles[i]); + } + free(setup->chhandles); + } + + free(setup->file); + free(setup->label); + free(setup->inputcontrolsmap); + free(setup->inputcontrols); + free(setup->outputcontrolsmap); + free(setup->outputcontrols); + free(setup->inputs); + free(setup->outputs); + + if (setup->inbufs) { + for(i=0; i<setup->nch; i++) + free(setup->inbufs[i]); + free(setup->inbufs); + } + + if (setup->outbufs) { + for(i=0; i<setup->nch; i++) + free(setup->outbufs[i]); + free(setup->outbufs); + } + + if (setup->libhandle) + dlclose(setup->libhandle); + + free(setup); + setup = NULL; + } +} + +/* ------------------------------------------------------------------------- */ + +/** \brief Process chunk of audio data through the selected LADSPA Plugin. + * + * \param af Pointer to audio filter instance + * \param data Pointer to chunk of audio data + * + * \return Either AF_ERROR or AF_OK + */ + +static struct mp_audio* play(struct af_instance *af, struct mp_audio *data) { + af_ladspa_t *setup = af->setup; + const LADSPA_Descriptor *pdes = setup->plugin_descriptor; + float *audio = (float*)data->audio; + int nsamples = data->len/4; /* /4 because it's 32-bit float */ + int nch = data->nch; + int rate = data->rate; + int i, p; + + if (setup->status !=AF_OK) + return data; + + /* See if it's the first call. If so, setup inbufs/outbufs, instantiate + * plugin, connect ports and activate plugin + */ + + /* 2004-12-07: Also check if the buffersize has to be changed! + * data->len is not constant per se! re-init buffers. + */ + + if ( (setup->bufsize != nsamples/nch) || (setup->nch != nch) ) { + + /* if setup->nch==0, it's the first call, if not, something has + * changed and all previous mallocs have to be freed + */ + + if (setup->nch != 0) { + mp_msg(MSGT_AFILTER, MSGL_DBG3, "%s: bufsize change; free old buffer\n", + setup->myname); + + if(setup->inbufs) { + for(i=0; i<setup->nch; i++) + free(setup->inbufs[i]); + free(setup->inbufs); + } + if(setup->outbufs) { + for(i=0; i<setup->nch; i++) + free(setup->outbufs[i]); + free(setup->outbufs); + } + } /* everything is freed */ + + setup->bufsize = nsamples/nch; + setup->nch = nch; + + setup->inbufs = calloc(nch, sizeof(float*)); + setup->outbufs = calloc(nch, sizeof(float*)); + + mp_msg(MSGT_AFILTER, MSGL_DBG3, "%s: bufsize = %d\n", + setup->myname, setup->bufsize); + + for(i=0; i<nch; i++) { + setup->inbufs[i] = calloc(setup->bufsize, sizeof(float)); + setup->outbufs[i] = calloc(setup->bufsize, sizeof(float)); + } + + /* only on the first call, there are no handles. */ + + if (!setup->chhandles) { + setup->chhandles = calloc(nch, sizeof(LADSPA_Handle)); + + /* create handles + * for stereo effects, create one handle for two channels + */ + + for(i=0; i<nch; i++) { + + if (i % setup->ninputs) { /* stereo effect */ + /* copy the handle from previous channel */ + setup->chhandles[i] = setup->chhandles[i-1]; + continue; + } + + setup->chhandles[i] = pdes->instantiate(pdes, rate); + } + } + + /* connect input/output ports for each channel/filter instance + * + * always (re)connect ports + */ + + for(i=0; i<nch; i++) { + pdes->connect_port(setup->chhandles[i], + setup->inputs[i % setup->ninputs], + setup->inbufs[i]); + pdes->connect_port(setup->chhandles[i], + setup->outputs[i % setup->ninputs], + setup->outbufs[i]); + + /* connect (input) controls */ + + for (p=0; p<setup->nports; p++) { + LADSPA_PortDescriptor d = pdes->PortDescriptors[p]; + if (LADSPA_IS_PORT_CONTROL(d)) { + if (LADSPA_IS_PORT_INPUT(d)) { + pdes->connect_port(setup->chhandles[i], p, + &(setup->inputcontrols[p]) ); + } else { + pdes->connect_port(setup->chhandles[i], p, + &(setup->outputcontrols[p]) ); + } + } + } + + /* Activate filter (if it isn't already :) ) */ + + if (pdes->activate && !setup->activated && i % setup->ninputs == 0) + pdes->activate(setup->chhandles[i]); + + } /* All channels/filters done! except for... */ + setup->activated = 1; + + /* Stereo effect with one channel left. Use same buffer for left + * and right. connect it to the second port. + */ + + for (p = i; p % setup->ninputs; p++) { + pdes->connect_port(setup->chhandles[i-1], + setup->inputs[p % setup->ninputs], + setup->inbufs[i-1]); + pdes->connect_port(setup->chhandles[i-1], + setup->outputs[p % setup->ninputs], + setup->outbufs[i-1]); + } /* done! */ + + } /* setup for first call/change of bufsize is done. + * normal playing routine follows... + */ + + /* Right now, I use a separate input and output buffer. + * I could change this to in-place processing (inbuf==outbuf), but some + * ladspa filters are broken and are not able to handle that. This seems + * fast enough, so unless somebody complains, it stays this way :) + */ + + /* Fill inbufs */ + + for (p=0; p<setup->bufsize; p++) { + for (i=0; i<nch; i++) { + setup->inbufs[i][p] = audio[p*nch + i]; + } + } + + /* Run filter(s) */ + + for (i=0; i<nch; i+=setup->ninputs) { + pdes->run(setup->chhandles[i], setup->bufsize); + } + + /* Extract outbufs */ + + for (p=0; p<setup->bufsize; p++) { + for (i=0; i<nch; i++) { + audio[p*nch + i] = setup->outbufs[i][p]; + } + } + + /* done */ + + return data; +} + +/* ------------------------------------------------------------------------- */ + +/** \brief Open LADSPA Plugin Loader Filter + * + * \param af Audio Filter instance + * + * \return Either AF_ERROR or AF_OK + */ + +static int af_open(struct af_instance *af) { + + af->control=control; + af->uninit=uninit; + af->play=play; + af->mul=1; + + af->data = calloc(1, sizeof(struct mp_audio)); + if (af->data == NULL) + return af_ladspa_malloc_failed((char*)af_info_ladspa.name); + + af->setup = calloc(1, sizeof(af_ladspa_t)); + if (af->setup == NULL) { + free(af->data); + af->data=NULL; + return af_ladspa_malloc_failed((char*)af_info_ladspa.name); + } + + ((af_ladspa_t*)af->setup)->status = AF_ERROR; /* will be set to AF_OK if + * all went OK and play() + * should proceed. + */ + + ((af_ladspa_t*)af->setup)->myname = strdup(af_info_ladspa.name); + if (!((af_ladspa_t*)af->setup)->myname) + return af_ladspa_malloc_failed((char*)af_info_ladspa.name); + + return AF_OK; +} + +/* ------------------------------------------------------------------------- */ diff --git a/audio/filter/af_lavcac3enc.c b/audio/filter/af_lavcac3enc.c new file mode 100644 index 0000000000..ad78266ad3 --- /dev/null +++ b/audio/filter/af_lavcac3enc.c @@ -0,0 +1,332 @@ +/* + * audio filter for runtime AC-3 encoding with libavcodec. + * + * Copyright (C) 2007 Ulion <ulion A gmail P com> + * + * This file is part of MPlayer. + * + * MPlayer 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. + * + * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <inttypes.h> +#include <assert.h> + +#include <libavcodec/avcodec.h> +#include <libavutil/intreadwrite.h> +#include <libavutil/mem.h> + +#include "config.h" +#include "af.h" +#include "reorder_ch.h" + + +#define AC3_MAX_CHANNELS 6 +#define AC3_MAX_CODED_FRAME_SIZE 3840 +#define AC3_FRAME_SIZE (6 * 256) +const uint16_t ac3_bitrate_tab[19] = { + 32, 40, 48, 56, 64, 80, 96, 112, 128, + 160, 192, 224, 256, 320, 384, 448, 512, 576, 640 +}; + +// Data for specific instances of this filter +typedef struct af_ac3enc_s { + struct AVCodec *lavc_acodec; + struct AVCodecContext *lavc_actx; + int add_iec61937_header; + int bit_rate; + int pending_data_size; + char *pending_data; + int pending_len; + int expect_len; + int min_channel_num; + int in_sampleformat; +} af_ac3enc_t; + +// Initialization and runtime control +static int control(struct af_instance *af, int cmd, void *arg) +{ + af_ac3enc_t *s = af->setup; + struct mp_audio *data = arg; + int i, bit_rate, test_output_res; + static const int default_bit_rate[AC3_MAX_CHANNELS+1] = \ + {0, 96000, 192000, 256000, 384000, 448000, 448000}; + + switch (cmd){ + case AF_CONTROL_REINIT: + if (AF_FORMAT_IS_AC3(data->format) || data->nch < s->min_channel_num) + return AF_DETACH; + + af->data->format = s->in_sampleformat; + af->data->bps = af_fmt2bits(s->in_sampleformat) / 8; + if (data->rate == 48000 || data->rate == 44100 || data->rate == 32000) + af->data->rate = data->rate; + else + af->data->rate = 48000; + if (data->nch > AC3_MAX_CHANNELS) + af->data->nch = AC3_MAX_CHANNELS; + else + af->data->nch = data->nch; + test_output_res = af_test_output(af, data); + + s->pending_len = 0; + s->expect_len = AC3_FRAME_SIZE * data->nch * af->data->bps; + assert(s->expect_len <= s->pending_data_size); + if (s->add_iec61937_header) + af->mul = (double)AC3_FRAME_SIZE * 2 * 2 / s->expect_len; + else + af->mul = (double)AC3_MAX_CODED_FRAME_SIZE / s->expect_len; + + mp_msg(MSGT_AFILTER, MSGL_DBG2, "af_lavcac3enc reinit: %d, %d, %f, %d.\n", + data->nch, data->rate, af->mul, s->expect_len); + + bit_rate = s->bit_rate ? s->bit_rate : default_bit_rate[af->data->nch]; + + if (s->lavc_actx->channels != af->data->nch || + s->lavc_actx->sample_rate != af->data->rate || + s->lavc_actx->bit_rate != bit_rate) { + + avcodec_close(s->lavc_actx); + + // Put sample parameters + s->lavc_actx->channels = af->data->nch; + s->lavc_actx->sample_rate = af->data->rate; + s->lavc_actx->bit_rate = bit_rate; + + if (avcodec_open2(s->lavc_actx, s->lavc_acodec, NULL) < 0) { + mp_tmsg(MSGT_AFILTER, MSGL_ERR, "Couldn't open codec %s, br=%d.\n", "ac3", bit_rate); + return AF_ERROR; + } + } + if (s->lavc_actx->frame_size != AC3_FRAME_SIZE) { + mp_msg(MSGT_AFILTER, MSGL_ERR, "lavcac3enc: unexpected ac3 " + "encoder frame size %d\n", s->lavc_actx->frame_size); + return AF_ERROR; + } + af->data->format = AF_FORMAT_AC3_BE; + af->data->bps = 2; + af->data->nch = 2; + return test_output_res; + case AF_CONTROL_COMMAND_LINE: + mp_msg(MSGT_AFILTER, MSGL_DBG2, "af_lavcac3enc cmdline: %s.\n", (char*)arg); + s->bit_rate = 0; + s->min_channel_num = 0; + s->add_iec61937_header = 0; + sscanf(arg,"%d:%d:%d", &s->add_iec61937_header, &s->bit_rate, + &s->min_channel_num); + if (s->bit_rate < 1000) + s->bit_rate *= 1000; + if (s->bit_rate) { + for (i = 0; i < 19; ++i) + if (ac3_bitrate_tab[i] * 1000 == s->bit_rate) + break; + if (i >= 19) { + mp_msg(MSGT_AFILTER, MSGL_WARN, "af_lavcac3enc unable set unsupported " + "bitrate %d, use default bitrate (check manpage to see " + "supported bitrates).\n", s->bit_rate); + s->bit_rate = 0; + } + } + if (s->min_channel_num == 0) + s->min_channel_num = 5; + mp_msg(MSGT_AFILTER, MSGL_V, "af_lavcac3enc config spdif:%d, bitrate:%d, " + "minchnum:%d.\n", s->add_iec61937_header, s->bit_rate, + s->min_channel_num); + return AF_OK; + } + return AF_UNKNOWN; +} + +// Deallocate memory +static void uninit(struct af_instance* af) +{ + if (af->data) + free(af->data->audio); + free(af->data); + if (af->setup) { + af_ac3enc_t *s = af->setup; + af->setup = NULL; + if(s->lavc_actx) { + avcodec_close(s->lavc_actx); + av_free(s->lavc_actx); + } + free(s->pending_data); + free(s); + } +} + +// Filter data through filter +static struct mp_audio* play(struct af_instance* af, struct mp_audio* data) +{ + af_ac3enc_t *s = af->setup; + struct mp_audio *c = data; // Current working data + struct mp_audio *l; + int len, left, outsize = 0, destsize; + char *buf, *src, *dest; + int max_output_len; + int frame_num = (data->len + s->pending_len) / s->expect_len; + int samplesize = af_fmt2bits(s->in_sampleformat) / 8; + + if (s->add_iec61937_header) + max_output_len = AC3_FRAME_SIZE * 2 * 2 * frame_num; + else + max_output_len = AC3_MAX_CODED_FRAME_SIZE * frame_num; + + if (af->data->len < max_output_len) { + mp_msg(MSGT_AFILTER, MSGL_V, "[libaf] Reallocating memory in module %s, " + "old len = %i, new len = %i\n", af->info->name, af->data->len, + max_output_len); + free(af->data->audio); + af->data->audio = malloc(max_output_len); + if (!af->data->audio) { + mp_msg(MSGT_AFILTER, MSGL_FATAL, "[libaf] Could not allocate memory \n"); + return NULL; + } + af->data->len = max_output_len; + } + + l = af->data; // Local data + buf = l->audio; + src = c->audio; + left = c->len; + + + while (left > 0) { + if (left + s->pending_len < s->expect_len) { + memcpy(s->pending_data + s->pending_len, src, left); + src += left; + s->pending_len += left; + left = 0; + break; + } + + dest = s->add_iec61937_header ? buf + 8 : buf; + destsize = (char *)l->audio + l->len - buf; + + if (s->pending_len) { + int needs = s->expect_len - s->pending_len; + if (needs > 0) { + memcpy(s->pending_data + s->pending_len, src, needs); + src += needs; + left -= needs; + } + + if (c->nch >= 5) + reorder_channel_nch(s->pending_data, + AF_CHANNEL_LAYOUT_MPLAYER_DEFAULT, + AF_CHANNEL_LAYOUT_LAVC_DEFAULT, + c->nch, + s->expect_len / samplesize, samplesize); + + len = avcodec_encode_audio(s->lavc_actx, dest, destsize, + (void *)s->pending_data); + s->pending_len = 0; + } + else { + if (c->nch >= 5) + reorder_channel_nch(src, + AF_CHANNEL_LAYOUT_MPLAYER_DEFAULT, + AF_CHANNEL_LAYOUT_LAVC_DEFAULT, + c->nch, + s->expect_len / samplesize, samplesize); + len = avcodec_encode_audio(s->lavc_actx,dest,destsize,(void *)src); + src += s->expect_len; + left -= s->expect_len; + } + mp_msg(MSGT_AFILTER, MSGL_DBG2, "avcodec_encode_audio got %d, pending %d.\n", + len, s->pending_len); + + if (s->add_iec61937_header) { + int bsmod = dest[5] & 0x7; + + AV_WB16(buf, 0xF872); // iec 61937 syncword 1 + AV_WB16(buf + 2, 0x4E1F); // iec 61937 syncword 2 + buf[4] = bsmod; // bsmod + buf[5] = 0x01; // data-type ac3 + AV_WB16(buf + 6, len << 3); // number of bits in payload + + memset(buf + 8 + len, 0, AC3_FRAME_SIZE * 2 * 2 - 8 - len); + len = AC3_FRAME_SIZE * 2 * 2; + } + + outsize += len; + buf += len; + } + c->audio = l->audio; + c->nch = 2; + c->bps = 2; + c->len = outsize; + mp_msg(MSGT_AFILTER, MSGL_DBG2, "play return size %d, pending %d\n", + outsize, s->pending_len); + return c; +} + +static int af_open(struct af_instance* af){ + + af_ac3enc_t *s = calloc(1,sizeof(af_ac3enc_t)); + af->control=control; + af->uninit=uninit; + af->play=play; + af->mul=1; + af->data=calloc(1,sizeof(struct mp_audio)); + af->setup=s; + + s->lavc_acodec = avcodec_find_encoder_by_name("ac3"); + if (!s->lavc_acodec) { + mp_tmsg(MSGT_AFILTER, MSGL_ERR, "Audio LAVC, couldn't find encoder for codec %s.\n", "ac3"); + return AF_ERROR; + } + + s->lavc_actx = avcodec_alloc_context3(s->lavc_acodec); + if (!s->lavc_actx) { + mp_tmsg(MSGT_AFILTER, MSGL_ERR, "Audio LAVC, couldn't allocate context!\n"); + return AF_ERROR; + } + const enum AVSampleFormat *fmts = s->lavc_acodec->sample_fmts; + for (int i = 0; ; i++) { + if (fmts[i] == AV_SAMPLE_FMT_NONE) { + mp_msg(MSGT_AFILTER, MSGL_ERR, "Audio LAVC, encoder doesn't " + "support expected sample formats!\n"); + return AF_ERROR; + } else if (fmts[i] == AV_SAMPLE_FMT_S16) { + s->in_sampleformat = AF_FORMAT_S16_NE; + s->lavc_actx->sample_fmt = fmts[i]; + break; + } else if (fmts[i] == AV_SAMPLE_FMT_FLT) { + s->in_sampleformat = AF_FORMAT_FLOAT_NE; + s->lavc_actx->sample_fmt = fmts[i]; + break; + } + } + char buf[100]; + mp_msg(MSGT_AFILTER, MSGL_V, "[af_lavcac3enc]: in sample format: %s\n", + af_fmt2str(s->in_sampleformat, buf, 100)); + s->pending_data_size = AF_NCH * AC3_FRAME_SIZE * + af_fmt2bits(s->in_sampleformat) / 8; + s->pending_data = malloc(s->pending_data_size); + + return AF_OK; +} + +struct af_info af_info_lavcac3enc = { + "runtime encode to ac3 using libavcodec", + "lavcac3enc", + "Ulion", + "", + AF_FLAGS_REENTRANT, + af_open +}; diff --git a/audio/filter/af_lavcresample.c b/audio/filter/af_lavcresample.c new file mode 100644 index 0000000000..ce777fed31 --- /dev/null +++ b/audio/filter/af_lavcresample.c @@ -0,0 +1,213 @@ +/* + * Copyright (c) 2004 Michael Niedermayer <michaelni@gmx.at> + * + * This file is part of MPlayer. + * + * MPlayer 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. + * + * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <inttypes.h> + +#include "config.h" +#include "af.h" +#include "libavcodec/avcodec.h" +#include "libavutil/rational.h" + +// Data for specific instances of this filter +typedef struct af_resample_s{ + struct AVResampleContext *avrctx; + int16_t *in[AF_NCH]; + int in_alloc; + int index; + + int filter_length; + int linear; + int phase_shift; + double cutoff; + + int ctx_out_rate; + int ctx_in_rate; + int ctx_filter_size; + int ctx_phase_shift; + int ctx_linear; + double ctx_cutoff; +}af_resample_t; + + +// Initialization and runtime control +static int control(struct af_instance* af, int cmd, void* arg) +{ + af_resample_t* s = (af_resample_t*)af->setup; + struct mp_audio *data= (struct mp_audio*)arg; + int out_rate, test_output_res; // helpers for checking input format + + switch(cmd){ + case AF_CONTROL_REINIT: + if((af->data->rate == data->rate) || (af->data->rate == 0)) + return AF_DETACH; + + af->data->nch = data->nch; + if (af->data->nch > AF_NCH) af->data->nch = AF_NCH; + af->data->format = AF_FORMAT_S16_NE; + af->data->bps = 2; + af->mul = (double)af->data->rate / data->rate; + af->delay = af->data->nch * s->filter_length / min(af->mul, 1); // *bps*.5 + + if (s->ctx_out_rate != af->data->rate || s->ctx_in_rate != data->rate || s->ctx_filter_size != s->filter_length || + s->ctx_phase_shift != s->phase_shift || s->ctx_linear != s->linear || s->ctx_cutoff != s->cutoff) { + if(s->avrctx) av_resample_close(s->avrctx); + s->avrctx= av_resample_init(af->data->rate, /*in_rate*/data->rate, s->filter_length, s->phase_shift, s->linear, s->cutoff); + s->ctx_out_rate = af->data->rate; + s->ctx_in_rate = data->rate; + s->ctx_filter_size = s->filter_length; + s->ctx_phase_shift = s->phase_shift; + s->ctx_linear = s->linear; + s->ctx_cutoff = s->cutoff; + } + + // hack to make af_test_output ignore the samplerate change + out_rate = af->data->rate; + af->data->rate = data->rate; + test_output_res = af_test_output(af, (struct mp_audio*)arg); + af->data->rate = out_rate; + return test_output_res; + case AF_CONTROL_COMMAND_LINE:{ + s->cutoff= 0.0; + sscanf((char*)arg,"%d:%d:%d:%d:%lf", &af->data->rate, &s->filter_length, &s->linear, &s->phase_shift, &s->cutoff); + if(s->cutoff <= 0.0) s->cutoff= max(1.0 - 6.5/(s->filter_length+8), 0.80); + return AF_OK; + } + case AF_CONTROL_RESAMPLE_RATE | AF_CONTROL_SET: + af->data->rate = *(int*)arg; + return AF_OK; + } + return AF_UNKNOWN; +} + +// Deallocate memory +static void uninit(struct af_instance* af) +{ + if(af->data) + free(af->data->audio); + free(af->data); + if(af->setup){ + int i; + af_resample_t *s = af->setup; + if(s->avrctx) av_resample_close(s->avrctx); + for (i=0; i < AF_NCH; i++) + free(s->in[i]); + free(s); + } +} + +// Filter data through filter +static struct mp_audio* play(struct af_instance* af, struct mp_audio* data) +{ + af_resample_t *s = af->setup; + int i, j, consumed, ret = 0; + int16_t *in = (int16_t*)data->audio; + int16_t *out; + int chans = data->nch; + int in_len = data->len/(2*chans); + int out_len = in_len * af->mul + 10; + int16_t tmp[AF_NCH][out_len]; + + if(AF_OK != RESIZE_LOCAL_BUFFER(af,data)) + return NULL; + + out= (int16_t*)af->data->audio; + + out_len= min(out_len, af->data->len/(2*chans)); + + if(s->in_alloc < in_len + s->index){ + s->in_alloc= in_len + s->index; + for(i=0; i<chans; i++){ + s->in[i]= realloc(s->in[i], s->in_alloc*sizeof(int16_t)); + } + } + + if(chans==1){ + memcpy(&s->in[0][s->index], in, in_len * sizeof(int16_t)); + }else if(chans==2){ + for(j=0; j<in_len; j++){ + s->in[0][j + s->index]= *(in++); + s->in[1][j + s->index]= *(in++); + } + }else{ + for(j=0; j<in_len; j++){ + for(i=0; i<chans; i++){ + s->in[i][j + s->index]= *(in++); + } + } + } + in_len += s->index; + + for(i=0; i<chans; i++){ + ret= av_resample(s->avrctx, tmp[i], s->in[i], &consumed, in_len, out_len, i+1 == chans); + } + out_len= ret; + + s->index= in_len - consumed; + for(i=0; i<chans; i++){ + memmove(s->in[i], s->in[i] + consumed, s->index*sizeof(int16_t)); + } + + if(chans==1){ + memcpy(out, tmp[0], out_len*sizeof(int16_t)); + }else if(chans==2){ + for(j=0; j<out_len; j++){ + *(out++)= tmp[0][j]; + *(out++)= tmp[1][j]; + } + }else{ + for(j=0; j<out_len; j++){ + for(i=0; i<chans; i++){ + *(out++)= tmp[i][j]; + } + } + } + + data->audio = af->data->audio; + data->len = out_len*chans*2; + data->rate = af->data->rate; + return data; +} + +static int af_open(struct af_instance* af){ + af_resample_t *s = calloc(1,sizeof(af_resample_t)); + af->control=control; + af->uninit=uninit; + af->play=play; + af->mul=1; + af->data=calloc(1,sizeof(struct mp_audio)); + s->filter_length= 16; + s->cutoff= max(1.0 - 6.5/(s->filter_length+8), 0.80); + s->phase_shift= 10; +// s->setup = RSMP_INT | FREQ_SLOPPY; + af->setup=s; + return AF_OK; +} + +struct af_info af_info_lavcresample = { + "Sample frequency conversion using libavcodec", + "lavcresample", + "Michael Niedermayer", + "", + AF_FLAGS_REENTRANT, + af_open +}; diff --git a/audio/filter/af_pan.c b/audio/filter/af_pan.c new file mode 100644 index 0000000000..8b1783ee84 --- /dev/null +++ b/audio/filter/af_pan.c @@ -0,0 +1,210 @@ +/* + * Copyright (C) 2002 Anders Johansson ajh@atri.curtin.edu.au + * + * This file is part of MPlayer. + * + * MPlayer 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. + * + * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <stdio.h> +#include <stdlib.h> + +#include <inttypes.h> +#include <math.h> +#include <limits.h> + +#include "af.h" + +// Data for specific instances of this filter +typedef struct af_pan_s +{ + int nch; // Number of output channels; zero means same as input + float level[AF_NCH][AF_NCH]; // Gain level for each channel +}af_pan_t; + +// Initialization and runtime control +static int control(struct af_instance* af, int cmd, void* arg) +{ + af_pan_t* s = af->setup; + + switch(cmd){ + case AF_CONTROL_REINIT: + // Sanity check + if(!arg) return AF_ERROR; + + af->data->rate = ((struct mp_audio*)arg)->rate; + af->data->format = AF_FORMAT_FLOAT_NE; + af->data->bps = 4; + af->data->nch = s->nch ? s->nch: ((struct mp_audio*)arg)->nch; + af->mul = (double)af->data->nch / ((struct mp_audio*)arg)->nch; + + if((af->data->format != ((struct mp_audio*)arg)->format) || + (af->data->bps != ((struct mp_audio*)arg)->bps)){ + ((struct mp_audio*)arg)->format = af->data->format; + ((struct mp_audio*)arg)->bps = af->data->bps; + return AF_FALSE; + } + return AF_OK; + case AF_CONTROL_COMMAND_LINE:{ + int nch = 0; + int n = 0; + char* cp = NULL; + int j,k; + // Read number of outputs + sscanf((char*)arg,"%i%n", &nch,&n); + if(AF_OK != control(af,AF_CONTROL_PAN_NOUT | AF_CONTROL_SET, &nch)) + return AF_ERROR; + + // Read pan values + cp = &((char*)arg)[n]; + j = 0; k = 0; + while((*cp == ':') && (k < AF_NCH)){ + sscanf(cp, ":%f%n" , &s->level[j][k], &n); + mp_msg(MSGT_AFILTER, MSGL_V, "[pan] Pan level from channel %i to" + " channel %i = %f\n",k,j,s->level[j][k]); + cp =&cp[n]; + j++; + if(j>=nch){ + j = 0; + k++; + } + } + return AF_OK; + } + case AF_CONTROL_PAN_LEVEL | AF_CONTROL_SET:{ + int i; + int ch = ((af_control_ext_t*)arg)->ch; + float* level = ((af_control_ext_t*)arg)->arg; + if (ch >= AF_NCH) + return AF_FALSE; + for(i=0;i<AF_NCH;i++) + s->level[ch][i] = level[i]; + return AF_OK; + } + case AF_CONTROL_PAN_LEVEL | AF_CONTROL_GET:{ + int i; + int ch = ((af_control_ext_t*)arg)->ch; + float* level = ((af_control_ext_t*)arg)->arg; + if (ch >= AF_NCH) + return AF_FALSE; + for(i=0;i<AF_NCH;i++) + level[i] = s->level[ch][i]; + return AF_OK; + } + case AF_CONTROL_PAN_NOUT | AF_CONTROL_SET: + // Reinit must be called after this function has been called + + // Sanity check + if(((int*)arg)[0] <= 0 || ((int*)arg)[0] > AF_NCH){ + mp_msg(MSGT_AFILTER, MSGL_ERR, "[pan] The number of output channels must be" + " between 1 and %i. Current value is %i\n",AF_NCH,((int*)arg)[0]); + return AF_ERROR; + } + s->nch=((int*)arg)[0]; + return AF_OK; + case AF_CONTROL_PAN_NOUT | AF_CONTROL_GET: + *(int*)arg = af->data->nch; + return AF_OK; + case AF_CONTROL_PAN_BALANCE | AF_CONTROL_SET:{ + float val = *(float*)arg; + if (s->nch) + return AF_ERROR; + if (af->data->nch >= 2) { + s->level[0][0] = min(1.f, 1.f - val); + s->level[0][1] = max(0.f, val); + s->level[1][0] = max(0.f, -val); + s->level[1][1] = min(1.f, 1.f + val); + } + return AF_OK; + } + case AF_CONTROL_PAN_BALANCE | AF_CONTROL_GET: + if (s->nch) + return AF_ERROR; + *(float*)arg = s->level[0][1] - s->level[1][0]; + return AF_OK; + } + return AF_UNKNOWN; +} + +// Deallocate memory +static void uninit(struct af_instance* af) +{ + if(af->data) + free(af->data->audio); + free(af->data); + free(af->setup); +} + +// Filter data through filter +static struct mp_audio* play(struct af_instance* af, struct mp_audio* data) +{ + struct mp_audio* c = data; // Current working data + struct mp_audio* l = af->data; // Local data + af_pan_t* s = af->setup; // Setup for this instance + float* in = c->audio; // Input audio data + float* out = NULL; // Output audio data + float* end = in+c->len/4; // End of loop + int nchi = c->nch; // Number of input channels + int ncho = l->nch; // Number of output channels + register int j,k; + + if(AF_OK != RESIZE_LOCAL_BUFFER(af,data)) + return NULL; + + out = l->audio; + // Execute panning + // FIXME: Too slow + while(in < end){ + for(j=0;j<ncho;j++){ + register float x = 0.0; + register float* tin = in; + for(k=0;k<nchi;k++) + x += tin[k] * s->level[j][k]; + out[j] = x; + } + out+= ncho; + in+= nchi; + } + + // Set output data + c->audio = l->audio; + c->len = c->len / c->nch * l->nch; + c->nch = l->nch; + + return c; +} + +// Allocate memory and set function pointers +static int af_open(struct af_instance* af){ + af->control=control; + af->uninit=uninit; + af->play=play; + af->mul=1; + af->data=calloc(1,sizeof(struct mp_audio)); + af->setup=calloc(1,sizeof(af_pan_t)); + if(af->data == NULL || af->setup == NULL) + return AF_ERROR; + return AF_OK; +} + +// Description of this filter +struct af_info af_info_pan = { + "Panning audio filter", + "pan", + "Anders", + "", + AF_FLAGS_REENTRANT, + af_open +}; diff --git a/audio/filter/af_resample.c b/audio/filter/af_resample.c new file mode 100644 index 0000000000..1f0b7cc942 --- /dev/null +++ b/audio/filter/af_resample.c @@ -0,0 +1,394 @@ +/* + * This audio filter changes the sample rate. + * + * Copyright (C) 2002 Anders Johansson ajh@atri.curtin.edu.au + * + * This file is part of MPlayer. + * + * MPlayer 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. + * + * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <inttypes.h> + +#include "libavutil/common.h" +#include "libavutil/mathematics.h" +#include "af.h" +#include "dsp.h" + +/* Below definition selects the length of each poly phase component. + Valid definitions are L8 and L16, where the number denotes the + length of the filter. This definition affects the computational + complexity (see play()), the performance (see filter.h) and the + memory usage. The filter length is chosen to 8 if the machine is + slow and to 16 if the machine is fast and has MMX. +*/ + +#if !HAVE_MMX // This machine is slow +#define L8 +#else +#define L16 +#endif + +#include "af_resample_template.c" + +// Filtering types +#define RSMP_LIN (0<<0) // Linear interpolation +#define RSMP_INT (1<<0) // 16 bit integer +#define RSMP_FLOAT (2<<0) // 32 bit floating point +#define RSMP_MASK (3<<0) + +// Defines for sloppy or exact resampling +#define FREQ_SLOPPY (0<<2) +#define FREQ_EXACT (1<<2) +#define FREQ_MASK (1<<2) + +// Accuracy for linear interpolation +#define STEPACCURACY 32 + +// local data +typedef struct af_resample_s +{ + void* w; // Current filter weights + void** xq; // Circular buffers + uint32_t xi; // Index for circular buffers + uint32_t wi; // Index for w + uint32_t i; // Number of new samples to put in x queue + uint32_t dn; // Down sampling factor + uint32_t up; // Up sampling factor + uint64_t step; // Step size for linear interpolation + uint64_t pt; // Pointer remainder for linear interpolation + int setup; // Setup parameters cmdline or through postcreate +} af_resample_t; + +// Fast linear interpolation resample with modest audio quality +static int linint(struct mp_audio* c,struct mp_audio* l, af_resample_t* s) +{ + uint32_t len = 0; // Number of input samples + uint32_t nch = l->nch; // Words pre transfer + uint64_t step = s->step; + int16_t* in16 = ((int16_t*)c->audio); + int16_t* out16 = ((int16_t*)l->audio); + int32_t* in32 = ((int32_t*)c->audio); + int32_t* out32 = ((int32_t*)l->audio); + uint64_t end = ((((uint64_t)c->len)/2LL)<<STEPACCURACY); + uint64_t pt = s->pt; + uint16_t tmp; + + switch (nch){ + case 1: + while(pt < end){ + out16[len++]=in16[pt>>STEPACCURACY]; + pt+=step; + } + s->pt=pt & ((1LL<<STEPACCURACY)-1); + break; + case 2: + end/=2; + while(pt < end){ + out32[len++]=in32[pt>>STEPACCURACY]; + pt+=step; + } + len=(len<<1); + s->pt=pt & ((1LL<<STEPACCURACY)-1); + break; + default: + end /=nch; + while(pt < end){ + tmp=nch; + do { + tmp--; + out16[len+tmp]=in16[tmp+(pt>>STEPACCURACY)*nch]; + } while (tmp); + len+=nch; + pt+=step; + } + s->pt=pt & ((1LL<<STEPACCURACY)-1); + } + return len; +} + +/* Determine resampling type and format */ +static int set_types(struct af_instance* af, struct mp_audio* data) +{ + af_resample_t* s = af->setup; + int rv = AF_OK; + float rd = 0; + + // Make sure this filter isn't redundant + if((af->data->rate == data->rate) || (af->data->rate == 0)) + return AF_DETACH; + /* If sloppy and small resampling difference (2%) */ + rd = abs((float)af->data->rate - (float)data->rate)/(float)data->rate; + if((((s->setup & FREQ_MASK) == FREQ_SLOPPY) && (rd < 0.02) && + (data->format != (AF_FORMAT_FLOAT_NE))) || + ((s->setup & RSMP_MASK) == RSMP_LIN)){ + s->setup = (s->setup & ~RSMP_MASK) | RSMP_LIN; + af->data->format = AF_FORMAT_S16_NE; + af->data->bps = 2; + mp_msg(MSGT_AFILTER, MSGL_V, "[resample] Using linear interpolation. \n"); + } + else{ + /* If the input format is float or if float is explicitly selected + use float, otherwise use int */ + if((data->format == (AF_FORMAT_FLOAT_NE)) || + ((s->setup & RSMP_MASK) == RSMP_FLOAT)){ + s->setup = (s->setup & ~RSMP_MASK) | RSMP_FLOAT; + af->data->format = AF_FORMAT_FLOAT_NE; + af->data->bps = 4; + } + else{ + s->setup = (s->setup & ~RSMP_MASK) | RSMP_INT; + af->data->format = AF_FORMAT_S16_NE; + af->data->bps = 2; + } + mp_msg(MSGT_AFILTER, MSGL_V, "[resample] Using %s processing and %s frequecy" + " conversion.\n", + ((s->setup & RSMP_MASK) == RSMP_FLOAT)?"floating point":"integer", + ((s->setup & FREQ_MASK) == FREQ_SLOPPY)?"inexact":"exact"); + } + + if(af->data->format != data->format || af->data->bps != data->bps) + rv = AF_FALSE; + data->format = af->data->format; + data->bps = af->data->bps; + af->data->nch = data->nch; + return rv; +} + +// Initialization and runtime control +static int control(struct af_instance* af, int cmd, void* arg) +{ + switch(cmd){ + case AF_CONTROL_REINIT:{ + af_resample_t* s = af->setup; + struct mp_audio* n = arg; // New configuration + int i,d = 0; + int rv = AF_OK; + + // Free space for circular buffers + if(s->xq){ + free(s->xq[0]); + free(s->xq); + s->xq = NULL; + } + + if(AF_DETACH == (rv = set_types(af,n))) + return AF_DETACH; + + // If linear interpolation + if((s->setup & RSMP_MASK) == RSMP_LIN){ + s->pt=0LL; + s->step=((uint64_t)n->rate<<STEPACCURACY)/(uint64_t)af->data->rate+1LL; + mp_msg(MSGT_AFILTER, MSGL_DBG2, "[resample] Linear interpolation step: 0x%016"PRIX64".\n", + s->step); + af->mul = (double)af->data->rate / n->rate; + return rv; + } + + // Calculate up and down sampling factors + d=av_gcd(af->data->rate,n->rate); + + // If sloppy resampling is enabled limit the upsampling factor + if(((s->setup & FREQ_MASK) == FREQ_SLOPPY) && (af->data->rate/d > 5000)){ + int up=af->data->rate/2; + int dn=n->rate/2; + int m=2; + while(af->data->rate/(d*m) > 5000){ + d=av_gcd(up,dn); + up/=2; dn/=2; m*=2; + } + d*=m; + } + + // Create space for circular buffers + s->xq = malloc(n->nch*sizeof(void*)); + s->xq[0] = calloc(n->nch, 2*L*af->data->bps); + for(i=1;i<n->nch;i++) + s->xq[i] = (uint8_t *)s->xq[i-1] + 2*L*af->data->bps; + s->xi = 0; + + // Check if the design needs to be redone + if(s->up != af->data->rate/d || s->dn != n->rate/d){ + float* w; + float* wt; + float fc; + int j; + s->up = af->data->rate/d; + s->dn = n->rate/d; + s->wi = 0; + s->i = 0; + + // Calculate cutoff frequency for filter + fc = 1/(float)(max(s->up,s->dn)); + // Allocate space for polyphase filter bank and prototype filter + w = malloc(sizeof(float) * s->up *L); + free(s->w); + s->w = malloc(L*s->up*af->data->bps); + + // Design prototype filter type using Kaiser window with beta = 10 + if(NULL == w || NULL == s->w || + -1 == af_filter_design_fir(s->up*L, w, &fc, LP|KAISER , 10.0)){ + mp_msg(MSGT_AFILTER, MSGL_ERR, "[resample] Unable to design prototype filter.\n"); + return AF_ERROR; + } + // Copy data from prototype to polyphase filter + wt=w; + for(j=0;j<L;j++){//Columns + for(i=0;i<s->up;i++){//Rows + if((s->setup & RSMP_MASK) == RSMP_INT){ + float t=(float)s->up*32767.0*(*wt); + ((int16_t*)s->w)[i*L+j] = (int16_t)((t>=0.0)?(t+0.5):(t-0.5)); + } + else + ((float*)s->w)[i*L+j] = (float)s->up*(*wt); + wt++; + } + } + free(w); + mp_msg(MSGT_AFILTER, MSGL_V, "[resample] New filter designed up: %i " + "down: %i\n", s->up, s->dn); + } + + // Set multiplier and delay + af->delay = 0; // not set correctly, but shouldn't be too large anyway + af->mul = (double)s->up / s->dn; + return rv; + } + case AF_CONTROL_COMMAND_LINE:{ + af_resample_t* s = af->setup; + int rate=0; + int type=RSMP_INT; + int sloppy=1; + sscanf((char*)arg,"%i:%i:%i", &rate, &sloppy, &type); + s->setup = (sloppy?FREQ_SLOPPY:FREQ_EXACT) | + (clamp(type,RSMP_LIN,RSMP_FLOAT)); + return af->control(af,AF_CONTROL_RESAMPLE_RATE | AF_CONTROL_SET, &rate); + } + case AF_CONTROL_POST_CREATE: + if((((struct af_cfg*)arg)->force & AF_INIT_FORMAT_MASK) == AF_INIT_FLOAT) + ((af_resample_t*)af->setup)->setup = RSMP_FLOAT; + return AF_OK; + case AF_CONTROL_RESAMPLE_RATE | AF_CONTROL_SET: + // Reinit must be called after this function has been called + + // Sanity check + if(((int*)arg)[0] < 8000 || ((int*)arg)[0] > 192000){ + mp_msg(MSGT_AFILTER, MSGL_ERR, "[resample] The output sample frequency " + "must be between 8kHz and 192kHz. Current value is %i \n", + ((int*)arg)[0]); + return AF_ERROR; + } + + af->data->rate=((int*)arg)[0]; + mp_msg(MSGT_AFILTER, MSGL_V, "[resample] Changing sample rate " + "to %iHz\n",af->data->rate); + return AF_OK; + } + return AF_UNKNOWN; +} + +// Deallocate memory +static void uninit(struct af_instance* af) +{ + af_resample_t *s = af->setup; + if (s) { + if (s->xq) free(s->xq[0]); + free(s->xq); + free(s->w); + free(s); + } + if(af->data) + free(af->data->audio); + free(af->data); +} + +// Filter data through filter +static struct mp_audio* play(struct af_instance* af, struct mp_audio* data) +{ + int len = 0; // Length of output data + struct mp_audio* c = data; // Current working data + struct mp_audio* l = af->data; // Local data + af_resample_t* s = af->setup; + + if(AF_OK != RESIZE_LOCAL_BUFFER(af,data)) + return NULL; + + // Run resampling + switch(s->setup & RSMP_MASK){ + case(RSMP_INT): +# define FORMAT_I 1 + if(s->up>s->dn){ +# define UP +# include "af_resample_template.c" +# undef UP + } + else{ +# define DN +# include "af_resample_template.c" +# undef DN + } + break; + case(RSMP_FLOAT): +# undef FORMAT_I +# define FORMAT_F 1 + if(s->up>s->dn){ +# define UP +# include "af_resample_template.c" +# undef UP + } + else{ +# define DN +# include "af_resample_template.c" +# undef DN + } + break; + case(RSMP_LIN): + len = linint(c, l, s); + break; + } + + // Set output data + c->audio = l->audio; + c->len = len*l->bps; + c->rate = l->rate; + + return c; +} + +// Allocate memory and set function pointers +static int af_open(struct af_instance* af){ + af->control=control; + af->uninit=uninit; + af->play=play; + af->mul=1; + af->data=calloc(1,sizeof(struct mp_audio)); + af->setup=calloc(1,sizeof(af_resample_t)); + if(af->data == NULL || af->setup == NULL) + return AF_ERROR; + ((af_resample_t*)af->setup)->setup = RSMP_INT | FREQ_SLOPPY; + return AF_OK; +} + +// Description of this plugin +struct af_info af_info_resample = { + "Sample frequency conversion", + "resample", + "Anders", + "", + AF_FLAGS_REENTRANT, + af_open +}; diff --git a/audio/filter/af_resample_template.c b/audio/filter/af_resample_template.c new file mode 100644 index 0000000000..4d4c5922ca --- /dev/null +++ b/audio/filter/af_resample_template.c @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2002 Anders Johansson ajh@atri.curtin.edu.au + * + * This file is part of MPlayer. + * + * MPlayer 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. + * + * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* This file contains the resampling engine, the sample format is + controlled by the FORMAT parameter, the filter length by the L + parameter and the resampling type by UP and DN. This file should + only be included by af_resample.c +*/ + +#undef L +#undef SHIFT +#undef FORMAT +#undef FIR +#undef ADDQUE + +/* The length Lxx definition selects the length of each poly phase + component. Valid definitions are L8 and L16 where the number + defines the nuber of taps. This definition affects the + computational complexity, the performance and the memory usage. +*/ + +/* The FORMAT_x parameter selects the sample format type currently + float and int16 are supported. Thes two formats are selected by + defining eiter FORMAT_F or FORMAT_I. The advantage of using float + is that the amplitude and therefore the SNR isn't affected by the + filtering, the disadvantage is that it is a lot slower. +*/ + +#if defined(FORMAT_I) +#define SHIFT >>16 +#define FORMAT int16_t +#else +#define SHIFT +#define FORMAT float +#endif + +// Short filter +#if defined(L8) + +#define L 8 // Filter length +// Unrolled loop to speed up execution +#define FIR(x,w,y) \ + (y[0]) = ( w[0]*x[0]+w[1]*x[1]+w[2]*x[2]+w[3]*x[3] \ + + w[4]*x[4]+w[5]*x[5]+w[6]*x[6]+w[7]*x[7] ) SHIFT + + + +#else /* L8/L16 */ + +#define L 16 +// Unrolled loop to speed up execution +#define FIR(x,w,y) \ + y[0] = ( w[0] *x[0] +w[1] *x[1] +w[2] *x[2] +w[3] *x[3] \ + + w[4] *x[4] +w[5] *x[5] +w[6] *x[6] +w[7] *x[7] \ + + w[8] *x[8] +w[9] *x[9] +w[10]*x[10]+w[11]*x[11] \ + + w[12]*x[12]+w[13]*x[13]+w[14]*x[14]+w[15]*x[15] ) SHIFT + +#endif /* L8/L16 */ + +// Macro to add data to circular que +#define ADDQUE(xi,xq,in)\ + xq[xi]=xq[(xi)+L]=*(in);\ + xi=((xi)-1)&(L-1); + +#if defined(UP) + + uint32_t ci = l->nch; // Index for channels + uint32_t nch = l->nch; // Number of channels + uint32_t inc = s->up/s->dn; + uint32_t level = s->up%s->dn; + uint32_t up = s->up; + uint32_t dn = s->dn; + uint32_t ns = c->len/l->bps; + register FORMAT* w = s->w; + + register uint32_t wi = 0; + register uint32_t xi = 0; + + // Index current channel + while(ci--){ + // Temporary pointers + register FORMAT* x = s->xq[ci]; + register FORMAT* in = ((FORMAT*)c->audio)+ci; + register FORMAT* out = ((FORMAT*)l->audio)+ci; + FORMAT* end = in+ns; // Block loop end + wi = s->wi; xi = s->xi; + + while(in < end){ + register uint32_t i = inc; + if(wi<level) i++; + + ADDQUE(xi,x,in); + in+=nch; + while(i--){ + // Run the FIR filter + FIR((&x[xi]),(&w[wi*L]),out); + len++; out+=nch; + // Update wi to point at the correct polyphase component + wi=(wi+dn)%up; + } + } + + } + // Save values that needs to be kept for next time + s->wi = wi; + s->xi = xi; +#endif /* UP */ + +#if defined(DN) /* DN */ + uint32_t ci = l->nch; // Index for channels + uint32_t nch = l->nch; // Number of channels + uint32_t inc = s->dn/s->up; + uint32_t level = s->dn%s->up; + uint32_t up = s->up; + uint32_t dn = s->dn; + uint32_t ns = c->len/l->bps; + FORMAT* w = s->w; + + register int32_t i = 0; + register uint32_t wi = 0; + register uint32_t xi = 0; + + // Index current channel + while(ci--){ + // Temporary pointers + register FORMAT* x = s->xq[ci]; + register FORMAT* in = ((FORMAT*)c->audio)+ci; + register FORMAT* out = ((FORMAT*)l->audio)+ci; + register FORMAT* end = in+ns; // Block loop end + i = s->i; wi = s->wi; xi = s->xi; + + while(in < end){ + + ADDQUE(xi,x,in); + in+=nch; + if((--i)<=0){ + // Run the FIR filter + FIR((&x[xi]),(&w[wi*L]),out); + len++; out+=nch; + + // Update wi to point at the correct polyphase component + wi=(wi+dn)%up; + + // Insert i number of new samples in queue + i = inc; + if(wi<level) i++; + } + } + } + // Save values that needs to be kept for next time + s->wi = wi; + s->xi = xi; + s->i = i; +#endif /* DN */ diff --git a/audio/filter/af_scaletempo.c b/audio/filter/af_scaletempo.c new file mode 100644 index 0000000000..0bbc220997 --- /dev/null +++ b/audio/filter/af_scaletempo.c @@ -0,0 +1,581 @@ +/* + * scaletempo audio filter + * + * scale tempo while maintaining pitch + * (WSOLA technique with cross correlation) + * inspired by SoundTouch library by Olli Parviainen + * + * basic algorithm + * - produce 'stride' output samples per loop + * - consume stride*scale input samples per loop + * + * to produce smoother transitions between strides, blend next overlap + * samples from last stride with correlated samples of current input + * + * Copyright (c) 2007 Robert Juliano + * + * This file is part of MPlayer. + * + * MPlayer 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. + * + * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <stdlib.h> +#include <string.h> +#include <limits.h> +#include <assert.h> + +#include "af.h" +#include "libavutil/common.h" +#include "subopt-helper.h" + +// Data for specific instances of this filter +typedef struct af_scaletempo_s +{ + // stride + float scale; + float speed; + float frames_stride_scaled; + float frames_stride_error; + int bytes_per_frame; + int bytes_stride; + float bytes_stride_scaled; + int bytes_queue; + int bytes_queued; + int bytes_to_slide; + int8_t* buf_queue; + // overlap + int samples_overlap; + int samples_standing; + int bytes_overlap; + int bytes_standing; + void* buf_overlap; + void* table_blend; + void (*output_overlap)(struct af_scaletempo_s* s, void* out_buf, int bytes_off); + // best overlap + int frames_search; + int num_channels; + void* buf_pre_corr; + void* table_window; + int (*best_overlap_offset)(struct af_scaletempo_s* s); + // command line + float scale_nominal; + float ms_stride; + float percent_overlap; + float ms_search; + short speed_tempo; + short speed_pitch; +} af_scaletempo_t; + +static int fill_queue(struct af_instance* af, struct mp_audio* data, int offset) +{ + af_scaletempo_t* s = af->setup; + int bytes_in = data->len - offset; + int offset_unchanged = offset; + + if (s->bytes_to_slide > 0) { + if (s->bytes_to_slide < s->bytes_queued) { + int bytes_move = s->bytes_queued - s->bytes_to_slide; + memmove(s->buf_queue, + s->buf_queue + s->bytes_to_slide, + bytes_move); + s->bytes_to_slide = 0; + s->bytes_queued = bytes_move; + } else { + int bytes_skip; + s->bytes_to_slide -= s->bytes_queued; + bytes_skip = FFMIN(s->bytes_to_slide, bytes_in); + s->bytes_queued = 0; + s->bytes_to_slide -= bytes_skip; + offset += bytes_skip; + bytes_in -= bytes_skip; + } + } + + if (bytes_in > 0) { + int bytes_copy = FFMIN(s->bytes_queue - s->bytes_queued, bytes_in); + assert(bytes_copy >= 0); + memcpy(s->buf_queue + s->bytes_queued, + (int8_t*)data->audio + offset, + bytes_copy); + s->bytes_queued += bytes_copy; + offset += bytes_copy; + } + + return offset - offset_unchanged; +} + +#define UNROLL_PADDING (4*4) + +static int best_overlap_offset_float(af_scaletempo_t* s) +{ + float *pw, *po, *ppc, *search_start; + float best_corr = INT_MIN; + int best_off = 0; + int i, off; + + pw = s->table_window; + po = s->buf_overlap; + po += s->num_channels; + ppc = s->buf_pre_corr; + for (i=s->num_channels; i<s->samples_overlap; i++) { + *ppc++ = *pw++ * *po++; + } + + search_start = (float*)s->buf_queue + s->num_channels; + for (off=0; off<s->frames_search; off++) { + float corr = 0; + float* ps = search_start; + ppc = s->buf_pre_corr; + for (i=s->num_channels; i<s->samples_overlap; i++) { + corr += *ppc++ * *ps++; + } + if (corr > best_corr) { + best_corr = corr; + best_off = off; + } + search_start += s->num_channels; + } + + return best_off * 4 * s->num_channels; +} + +static int best_overlap_offset_s16(af_scaletempo_t* s) +{ + int32_t *pw, *ppc; + int16_t *po, *search_start; + int64_t best_corr = INT64_MIN; + int best_off = 0; + int off; + long i; + + pw = s->table_window; + po = s->buf_overlap; + po += s->num_channels; + ppc = s->buf_pre_corr; + for (i=s->num_channels; i<s->samples_overlap; i++) { + *ppc++ = ( *pw++ * *po++ ) >> 15; + } + + search_start = (int16_t*)s->buf_queue + s->num_channels; + for (off=0; off<s->frames_search; off++) { + int64_t corr = 0; + int16_t* ps = search_start; + ppc = s->buf_pre_corr; + ppc += s->samples_overlap - s->num_channels; + ps += s->samples_overlap - s->num_channels; + i = -(s->samples_overlap - s->num_channels); + do { + corr += ppc[i+0] * ps[i+0]; + corr += ppc[i+1] * ps[i+1]; + corr += ppc[i+2] * ps[i+2]; + corr += ppc[i+3] * ps[i+3]; + i += 4; + } while (i < 0); + if (corr > best_corr) { + best_corr = corr; + best_off = off; + } + search_start += s->num_channels; + } + + return best_off * 2 * s->num_channels; +} + +static void output_overlap_float(af_scaletempo_t* s, void* buf_out, + int bytes_off) +{ + float* pout = buf_out; + float* pb = s->table_blend; + float* po = s->buf_overlap; + float* pin = (float*)(s->buf_queue + bytes_off); + int i; + for (i=0; i<s->samples_overlap; i++) { + *pout++ = *po - *pb++ * ( *po - *pin++ ); po++; + } +} +static void output_overlap_s16(af_scaletempo_t* s, void* buf_out, + int bytes_off) +{ + int16_t* pout = buf_out; + int32_t* pb = s->table_blend; + int16_t* po = s->buf_overlap; + int16_t* pin = (int16_t*)(s->buf_queue + bytes_off); + int i; + for (i=0; i<s->samples_overlap; i++) { + *pout++ = *po - ( ( *pb++ * ( *po - *pin++ ) ) >> 16 ); po++; + } +} + +// Filter data through filter +static struct mp_audio* play(struct af_instance* af, struct mp_audio* data) +{ + af_scaletempo_t* s = af->setup; + int offset_in; + int max_bytes_out; + int8_t* pout; + + if (s->scale == 1.0) { + af->delay = 0; + return data; + } + + // RESIZE_LOCAL_BUFFER - can't use macro + max_bytes_out = ((int)(data->len / s->bytes_stride_scaled) + 1) * s->bytes_stride; + if (max_bytes_out > af->data->len) { + mp_msg(MSGT_AFILTER, MSGL_V, "[libaf] Reallocating memory in module %s, " + "old len = %i, new len = %i\n",af->info->name,af->data->len,max_bytes_out); + af->data->audio = realloc(af->data->audio, max_bytes_out); + if (!af->data->audio) { + mp_msg(MSGT_AFILTER, MSGL_FATAL, "[libaf] Could not allocate memory\n"); + return NULL; + } + af->data->len = max_bytes_out; + } + + offset_in = fill_queue(af, data, 0); + pout = af->data->audio; + while (s->bytes_queued >= s->bytes_queue) { + int ti; + float tf; + int bytes_off = 0; + + // output stride + if (s->output_overlap) { + if (s->best_overlap_offset) + bytes_off = s->best_overlap_offset(s); + s->output_overlap(s, pout, bytes_off); + } + memcpy(pout + s->bytes_overlap, + s->buf_queue + bytes_off + s->bytes_overlap, + s->bytes_standing); + pout += s->bytes_stride; + + // input stride + memcpy(s->buf_overlap, + s->buf_queue + bytes_off + s->bytes_stride, + s->bytes_overlap); + tf = s->frames_stride_scaled + s->frames_stride_error; + ti = (int)tf; + s->frames_stride_error = tf - ti; + s->bytes_to_slide = ti * s->bytes_per_frame; + + offset_in += fill_queue(af, data, offset_in); + } + + // This filter can have a negative delay when scale > 1: + // output corresponding to some length of input can be decided and written + // after receiving only a part of that input. + af->delay = s->bytes_queued - s->bytes_to_slide; + + data->audio = af->data->audio; + data->len = pout - (int8_t *)af->data->audio; + return data; +} + +// Initialization and runtime control +static int control(struct af_instance* af, int cmd, void* arg) +{ + af_scaletempo_t* s = af->setup; + switch(cmd){ + case AF_CONTROL_REINIT:{ + struct mp_audio* data = (struct mp_audio*)arg; + float srate = data->rate / 1000; + int nch = data->nch; + int bps; + int use_int = 0; + int frames_stride, frames_overlap; + int i, j; + + mp_msg(MSGT_AFILTER, MSGL_V, + "[scaletempo] %.3f speed * %.3f scale_nominal = %.3f\n", + s->speed, s->scale_nominal, s->scale); + + if (s->scale == 1.0) { + if (s->speed_tempo && s->speed_pitch) + return AF_DETACH; + memcpy(af->data, data, sizeof(struct mp_audio)); + af->delay = 0; + af->mul = 1; + return af_test_output(af, data); + } + + af->data->rate = data->rate; + af->data->nch = data->nch; + if ( data->format == AF_FORMAT_S16_LE + || data->format == AF_FORMAT_S16_BE ) { + use_int = 1; + af->data->format = AF_FORMAT_S16_NE; + af->data->bps = bps = 2; + } else { + af->data->format = AF_FORMAT_FLOAT_NE; + af->data->bps = bps = 4; + } + + frames_stride = srate * s->ms_stride; + s->bytes_stride = frames_stride * bps * nch; + s->bytes_stride_scaled = s->scale * s->bytes_stride; + s->frames_stride_scaled = s->scale * frames_stride; + s->frames_stride_error = 0; + af->mul = (double)s->bytes_stride / s->bytes_stride_scaled; + af->delay = 0; + + frames_overlap = frames_stride * s->percent_overlap; + if (frames_overlap <= 0) { + s->bytes_standing = s->bytes_stride; + s->samples_standing = s->bytes_standing / bps; + s->output_overlap = NULL; + s->bytes_overlap = 0; + } else { + s->samples_overlap = frames_overlap * nch; + s->bytes_overlap = frames_overlap * nch * bps; + s->bytes_standing = s->bytes_stride - s->bytes_overlap; + s->samples_standing = s->bytes_standing / bps; + s->buf_overlap = realloc(s->buf_overlap, s->bytes_overlap); + s->table_blend = realloc(s->table_blend, s->bytes_overlap * 4); + if(!s->buf_overlap || !s->table_blend) { + mp_msg(MSGT_AFILTER, MSGL_FATAL, "[scaletempo] Out of memory\n"); + return AF_ERROR; + } + memset(s->buf_overlap, 0, s->bytes_overlap); + if (use_int) { + int32_t* pb = s->table_blend; + int64_t blend = 0; + for (i=0; i<frames_overlap; i++) { + int32_t v = blend / frames_overlap; + for (j=0; j<nch; j++) { + *pb++ = v; + } + blend += 65536; // 2^16 + } + s->output_overlap = output_overlap_s16; + } else { + float* pb = s->table_blend; + for (i=0; i<frames_overlap; i++) { + float v = i / (float)frames_overlap; + for (j=0; j<nch; j++) { + *pb++ = v; + } + } + s->output_overlap = output_overlap_float; + } + } + + s->frames_search = (frames_overlap > 1) ? srate * s->ms_search : 0; + if (s->frames_search <= 0) { + s->best_overlap_offset = NULL; + } else { + if (use_int) { + int64_t t = frames_overlap; + int32_t n = 8589934588LL / (t * t); // 4 * (2^31 - 1) / t^2 + int32_t* pw; + s->buf_pre_corr = realloc(s->buf_pre_corr, s->bytes_overlap * 2 + UNROLL_PADDING); + s->table_window = realloc(s->table_window, s->bytes_overlap * 2 - nch * bps * 2); + if(!s->buf_pre_corr || !s->table_window) { + mp_msg(MSGT_AFILTER, MSGL_FATAL, "[scaletempo] Out of memory\n"); + return AF_ERROR; + } + memset((char *)s->buf_pre_corr + s->bytes_overlap * 2, 0, UNROLL_PADDING); + pw = s->table_window; + for (i=1; i<frames_overlap; i++) { + int32_t v = ( i * (t - i) * n ) >> 15; + for (j=0; j<nch; j++) { + *pw++ = v; + } + } + s->best_overlap_offset = best_overlap_offset_s16; + } else { + float* pw; + s->buf_pre_corr = realloc(s->buf_pre_corr, s->bytes_overlap); + s->table_window = realloc(s->table_window, s->bytes_overlap - nch * bps); + if(!s->buf_pre_corr || !s->table_window) { + mp_msg(MSGT_AFILTER, MSGL_FATAL, "[scaletempo] Out of memory\n"); + return AF_ERROR; + } + pw = s->table_window; + for (i=1; i<frames_overlap; i++) { + float v = i * (frames_overlap - i); + for (j=0; j<nch; j++) { + *pw++ = v; + } + } + s->best_overlap_offset = best_overlap_offset_float; + } + } + + s->bytes_per_frame = bps * nch; + s->num_channels = nch; + + s->bytes_queue + = (s->frames_search + frames_stride + frames_overlap) * bps * nch; + s->buf_queue = realloc(s->buf_queue, s->bytes_queue + UNROLL_PADDING); + if(!s->buf_queue) { + mp_msg(MSGT_AFILTER, MSGL_FATAL, "[scaletempo] Out of memory\n"); + return AF_ERROR; + } + + s->bytes_queued = 0; + s->bytes_to_slide = 0; + + mp_msg (MSGT_AFILTER, MSGL_DBG2, "[scaletempo] " + "%.2f stride_in, %i stride_out, %i standing, " + "%i overlap, %i search, %i queue, %s mode\n", + s->frames_stride_scaled, + (int)(s->bytes_stride / nch / bps), + (int)(s->bytes_standing / nch / bps), + (int)(s->bytes_overlap / nch / bps), + s->frames_search, + (int)(s->bytes_queue / nch / bps), + (use_int?"s16":"float")); + + return af_test_output(af, (struct mp_audio*)arg); + } + case AF_CONTROL_PLAYBACK_SPEED | AF_CONTROL_SET:{ + if (s->speed_tempo) { + if (s->speed_pitch) { + break; + } + s->speed = *(float*)arg; + s->scale = s->speed * s->scale_nominal; + } else { + if (s->speed_pitch) { + s->speed = 1 / *(float*)arg; + s->scale = s->speed * s->scale_nominal; + break; + } + } + return AF_OK; + } + case AF_CONTROL_SCALETEMPO_AMOUNT | AF_CONTROL_SET:{ + s->scale = *(float*)arg; + s->scale = s->speed * s->scale_nominal; + return AF_OK; + } + case AF_CONTROL_SCALETEMPO_AMOUNT | AF_CONTROL_GET: + *(float*)arg = s->scale; + return AF_OK; + case AF_CONTROL_COMMAND_LINE:{ + strarg_t speed = {}; + opt_t subopts[] = { + {"scale", OPT_ARG_FLOAT, &s->scale_nominal, NULL}, + {"stride", OPT_ARG_FLOAT, &s->ms_stride, NULL}, + {"overlap", OPT_ARG_FLOAT, &s->percent_overlap, NULL}, + {"search", OPT_ARG_FLOAT, &s->ms_search, NULL}, + {"speed", OPT_ARG_STR, &speed, NULL}, + {NULL}, + }; + if (subopt_parse(arg, subopts) != 0) { + return AF_ERROR; + } + if (s->scale_nominal <= 0) { + mp_msg(MSGT_AFILTER, MSGL_ERR, "[scaletempo] %s: %s: scale > 0\n", + mp_gtext("error parsing command line"), + mp_gtext("value out of range")); + return AF_ERROR; + } + if (s->ms_stride <= 0) { + mp_msg(MSGT_AFILTER, MSGL_ERR, "[scaletempo] %s: %s: stride > 0\n", + mp_gtext("error parsing command line"), + mp_gtext("value out of range")); + return AF_ERROR; + } + if (s->percent_overlap < 0 || s->percent_overlap > 1) { + mp_msg(MSGT_AFILTER, MSGL_ERR, + "[scaletempo] %s: %s: 0 <= overlap <= 1\n", + mp_gtext("error parsing command line"), + mp_gtext("value out of range")); + return AF_ERROR; + } + if (s->ms_search < 0) { + mp_msg(MSGT_AFILTER, MSGL_ERR, "[scaletempo] %s: %s: search >= 0\n", + mp_gtext("error parsing command line"), + mp_gtext("value out of range")); + return AF_ERROR; + } + if (speed.len > 0) { + if (strcmp(speed.str, "pitch") == 0) { + s->speed_tempo = 0; + s->speed_pitch = 1; + } else if (strcmp(speed.str, "tempo") == 0) { + s->speed_tempo = 1; + s->speed_pitch = 0; + } else if (strcmp(speed.str, "none") == 0) { + s->speed_tempo = 0; + s->speed_pitch = 0; + } else if (strcmp(speed.str, "both") == 0) { + s->speed_tempo = 1; + s->speed_pitch = 1; + } else { + mp_msg(MSGT_AFILTER, MSGL_ERR, + "[scaletempo] %s: %s: speed=[pitch|tempo|none|both]\n", + mp_gtext("error parsing command line"), + mp_gtext("value out of range")); + return AF_ERROR; + } + } + s->scale = s->speed * s->scale_nominal; + mp_msg(MSGT_AFILTER, MSGL_DBG2, "[scaletempo] %6.3f scale, %6.2f stride, %6.2f overlap, %6.2f search, speed = %s\n", s->scale_nominal, s->ms_stride, s->percent_overlap, s->ms_search, (s->speed_tempo?(s->speed_pitch?"tempo and speed":"tempo"):(s->speed_pitch?"pitch":"none"))); + return AF_OK; + } + } + return AF_UNKNOWN; +} + +// Deallocate memory +static void uninit(struct af_instance* af) +{ + af_scaletempo_t* s = af->setup; + free(af->data->audio); + free(af->data); + free(s->buf_queue); + free(s->buf_overlap); + free(s->buf_pre_corr); + free(s->table_blend); + free(s->table_window); + free(af->setup); +} + +// Allocate memory and set function pointers +static int af_open(struct af_instance* af){ + af_scaletempo_t* s; + + af->control = control; + af->uninit = uninit; + af->play = play; + af->mul = 1; + af->data = calloc(1,sizeof(struct mp_audio)); + af->setup = calloc(1,sizeof(af_scaletempo_t)); + if(af->data == NULL || af->setup == NULL) + return AF_ERROR; + + s = af->setup; + s->scale = s->speed = s->scale_nominal = 1.0; + s->speed_tempo = 1; + s->speed_pitch = 0; + s->ms_stride = 60; + s->percent_overlap = .20; + s->ms_search = 14; + + return AF_OK; +} + +// Description of this filter +struct af_info af_info_scaletempo = { + "Scale audio tempo while maintaining pitch", + "scaletempo", + "Robert Juliano", + "", + AF_FLAGS_REENTRANT, + af_open +}; diff --git a/audio/filter/af_sinesuppress.c b/audio/filter/af_sinesuppress.c new file mode 100644 index 0000000000..36f7189f00 --- /dev/null +++ b/audio/filter/af_sinesuppress.c @@ -0,0 +1,184 @@ +/* + * Copyright (C) 2006 Michael Niedermayer + * Copyright (C) 2004 Alex Beregszaszi + * based upon af_extrastereo.c by Pierre Lombard + * + * This file is part of MPlayer. + * + * MPlayer 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. + * + * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <inttypes.h> +#include <math.h> +#include <limits.h> + +#include "af.h" + +// Data for specific instances of this filter +typedef struct af_sinesuppress_s +{ + double freq; + double decay; + double real; + double imag; + double ref; + double pos; +}af_sinesuppress_t; + +static struct mp_audio* play_s16(struct af_instance* af, struct mp_audio* data); +//static struct mp_audio* play_float(struct af_instance* af, struct mp_audio* data); + +// Initialization and runtime control +static int control(struct af_instance* af, int cmd, void* arg) +{ + af_sinesuppress_t* s = (af_sinesuppress_t*)af->setup; + + switch(cmd){ + case AF_CONTROL_REINIT:{ + // Sanity check + if(!arg) return AF_ERROR; + + af->data->rate = ((struct mp_audio*)arg)->rate; + af->data->nch = 1; +#if 0 + if (((struct mp_audio*)arg)->format == AF_FORMAT_FLOAT_NE) + { + af->data->format = AF_FORMAT_FLOAT_NE; + af->data->bps = 4; + af->play = play_float; + }// else +#endif + { + af->data->format = AF_FORMAT_S16_NE; + af->data->bps = 2; + af->play = play_s16; + } + + return af_test_output(af,(struct mp_audio*)arg); + } + case AF_CONTROL_COMMAND_LINE:{ + float f1,f2; + sscanf((char*)arg,"%f:%f", &f1,&f2); + s->freq = f1; + s->decay = f2; + return AF_OK; + } + case AF_CONTROL_SS_FREQ | AF_CONTROL_SET: + s->freq = *(float*)arg; + return AF_OK; + case AF_CONTROL_SS_FREQ | AF_CONTROL_GET: + *(float*)arg = s->freq; + return AF_OK; + case AF_CONTROL_SS_DECAY | AF_CONTROL_SET: + s->decay = *(float*)arg; + return AF_OK; + case AF_CONTROL_SS_DECAY | AF_CONTROL_GET: + *(float*)arg = s->decay; + return AF_OK; + } + return AF_UNKNOWN; +} + +// Deallocate memory +static void uninit(struct af_instance* af) +{ + free(af->data); + free(af->setup); +} + +// Filter data through filter +static struct mp_audio* play_s16(struct af_instance* af, struct mp_audio* data) +{ + af_sinesuppress_t *s = af->setup; + register int i = 0; + int16_t *a = (int16_t*)data->audio; // Audio data + int len = data->len/2; // Number of samples + + for (i = 0; i < len; i++) + { + double co= cos(s->pos); + double si= sin(s->pos); + + s->real += co * a[i]; + s->imag += si * a[i]; + s->ref += co * co; + + a[i] -= (s->real * co + s->imag * si) / s->ref; + + s->real -= s->real * s->decay; + s->imag -= s->imag * s->decay; + s->ref -= s->ref * s->decay; + + s->pos += 2 * M_PI * s->freq / data->rate; + } + + mp_msg(MSGT_AFILTER, MSGL_V, "[sinesuppress] f:%8.2f: amp:%8.2f\n", s->freq, sqrt(s->real*s->real + s->imag*s->imag) / s->ref); + + return data; +} + +#if 0 +static struct mp_audio* play_float(struct af_instance* af, struct mp_audio* data) +{ + af_sinesuppress_t *s = af->setup; + register int i = 0; + float *a = (float*)data->audio; // Audio data + int len = data->len/4; // Number of samples + float avg, l, r; + + for (i = 0; i < len; i+=2) + { + avg = (a[i] + a[i + 1]) / 2; + +/* l = avg + (s->mul * (a[i] - avg)); + r = avg + (s->mul * (a[i + 1] - avg));*/ + + a[i] = af_softclip(l); + a[i + 1] = af_softclip(r); + } + + return data; +} +#endif + +// Allocate memory and set function pointers +static int af_open(struct af_instance* af){ + af->control=control; + af->uninit=uninit; + af->play=play_s16; + af->mul=1; + af->data=calloc(1,sizeof(struct mp_audio)); + af->setup=calloc(1,sizeof(af_sinesuppress_t)); + if(af->data == NULL || af->setup == NULL) + return AF_ERROR; + + ((af_sinesuppress_t*)af->setup)->freq = 50.0; + ((af_sinesuppress_t*)af->setup)->decay = 0.0001; + return AF_OK; +} + +// Description of this filter +struct af_info af_info_sinesuppress = { + "Sine Suppress", + "sinesuppress", + "Michael Niedermayer", + "", + 0, + af_open +}; diff --git a/audio/filter/af_sub.c b/audio/filter/af_sub.c new file mode 100644 index 0000000000..4af28d9141 --- /dev/null +++ b/audio/filter/af_sub.c @@ -0,0 +1,188 @@ +/* + * Copyright (C) 2002 Anders Johansson ajh@watri.uwa.edu.au + * + * This file is part of MPlayer. + * + * MPlayer 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. + * + * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* This filter adds a sub-woofer channels to the audio stream by + averaging the left and right channel and low-pass filter them. The + low-pass filter is implemented as a 4th order IIR Butterworth + filter, with a variable cutoff frequency between 10 and 300 Hz. The + filter gives 24dB/octave attenuation. There are two runtime + controls one for setting which channel to insert the sub-audio into + called AF_CONTROL_SUB_CH and one for setting the cutoff frequency + called AF_CONTROL_SUB_FC. +*/ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "af.h" +#include "dsp.h" + +// Q value for low-pass filter +#define Q 1.0 + +// Analog domain biquad section +typedef struct{ + float a[3]; // Numerator coefficients + float b[3]; // Denominator coefficients +} biquad_t; + +// S-parameters for designing 4th order Butterworth filter +static biquad_t sp[2] = {{{1.0,0.0,0.0},{1.0,0.765367,1.0}}, + {{1.0,0.0,0.0},{1.0,1.847759,1.0}}}; + +// Data for specific instances of this filter +typedef struct af_sub_s +{ + float w[2][4]; // Filter taps for low-pass filter + float q[2][2]; // Circular queues + float fc; // Cutoff frequency [Hz] for low-pass filter + float k; // Filter gain; + int ch; // Channel number which to insert the filtered data + +}af_sub_t; + +// Initialization and runtime control +static int control(struct af_instance* af, int cmd, void* arg) +{ + af_sub_t* s = af->setup; + + switch(cmd){ + case AF_CONTROL_REINIT:{ + // Sanity check + if(!arg) return AF_ERROR; + + af->data->rate = ((struct mp_audio*)arg)->rate; + af->data->nch = max(s->ch+1,((struct mp_audio*)arg)->nch); + af->data->format = AF_FORMAT_FLOAT_NE; + af->data->bps = 4; + + // Design low-pass filter + s->k = 1.0; + if((-1 == af_filter_szxform(sp[0].a, sp[0].b, Q, s->fc, + (float)af->data->rate, &s->k, s->w[0])) || + (-1 == af_filter_szxform(sp[1].a, sp[1].b, Q, s->fc, + (float)af->data->rate, &s->k, s->w[1]))) + return AF_ERROR; + return af_test_output(af,(struct mp_audio*)arg); + } + case AF_CONTROL_COMMAND_LINE:{ + int ch=5; + float fc=60.0; + sscanf(arg,"%f:%i", &fc , &ch); + if(AF_OK != control(af,AF_CONTROL_SUB_CH | AF_CONTROL_SET, &ch)) + return AF_ERROR; + return control(af,AF_CONTROL_SUB_FC | AF_CONTROL_SET, &fc); + } + case AF_CONTROL_SUB_CH | AF_CONTROL_SET: // Requires reinit + // Sanity check + if((*(int*)arg >= AF_NCH) || (*(int*)arg < 0)){ + mp_msg(MSGT_AFILTER, MSGL_ERR, "[sub] Subwoofer channel number must be between " + " 0 and %i current value is %i\n", AF_NCH-1, *(int*)arg); + return AF_ERROR; + } + s->ch = *(int*)arg; + return AF_OK; + case AF_CONTROL_SUB_CH | AF_CONTROL_GET: + *(int*)arg = s->ch; + return AF_OK; + case AF_CONTROL_SUB_FC | AF_CONTROL_SET: // Requires reinit + // Sanity check + if((*(float*)arg > 300) || (*(float*)arg < 20)){ + mp_msg(MSGT_AFILTER, MSGL_ERR, "[sub] Cutoff frequency must be between 20Hz and" + " 300Hz current value is %0.2f",*(float*)arg); + return AF_ERROR; + } + // Set cutoff frequency + s->fc = *(float*)arg; + return AF_OK; + case AF_CONTROL_SUB_FC | AF_CONTROL_GET: + *(float*)arg = s->fc; + return AF_OK; + } + return AF_UNKNOWN; +} + +// Deallocate memory +static void uninit(struct af_instance* af) +{ + free(af->data); + free(af->setup); +} + +#ifndef IIR +#define IIR(in,w,q,out) { \ + float h0 = (q)[0]; \ + float h1 = (q)[1]; \ + float hn = (in) - h0 * (w)[0] - h1 * (w)[1]; \ + out = hn + h0 * (w)[2] + h1 * (w)[3]; \ + (q)[1] = h0; \ + (q)[0] = hn; \ +} +#endif + +// Filter data through filter +static struct mp_audio* play(struct af_instance* af, struct mp_audio* data) +{ + struct mp_audio* c = data; // Current working data + af_sub_t* s = af->setup; // Setup for this instance + float* a = c->audio; // Audio data + int len = c->len/4; // Number of samples in current audio block + int nch = c->nch; // Number of channels + int ch = s->ch; // Channel in which to insert the sub audio + register int i; + + // Run filter + for(i=0;i<len;i+=nch){ + // Average left and right + register float x = 0.5 * (a[i] + a[i+1]); + IIR(x * s->k, s->w[0], s->q[0], x); + IIR(x , s->w[1], s->q[1], a[i+ch]); + } + + return c; +} + +// Allocate memory and set function pointers +static int af_open(struct af_instance* af){ + af_sub_t* s; + af->control=control; + af->uninit=uninit; + af->play=play; + af->mul=1; + af->data=calloc(1,sizeof(struct mp_audio)); + af->setup=s=calloc(1,sizeof(af_sub_t)); + if(af->data == NULL || af->setup == NULL) + return AF_ERROR; + // Set default values + s->ch = 5; // Channel nr 6 + s->fc = 60; // Cutoff frequency 60Hz + return AF_OK; +} + +// Description of this filter +struct af_info af_info_sub = { + "Audio filter for adding a sub-base channel", + "sub", + "Anders", + "", + AF_FLAGS_NOT_REENTRANT, + af_open +}; diff --git a/audio/filter/af_surround.c b/audio/filter/af_surround.c new file mode 100644 index 0000000000..57288d6ba2 --- /dev/null +++ b/audio/filter/af_surround.c @@ -0,0 +1,273 @@ +/* + * Filter to do simple decoding of matrixed surround sound. + * This will provide a (basic) surround-sound effect from + * audio encoded for Dolby Surround, Pro Logic etc. + * + * original author: Steve Davies <steve@daviesfam.org> + * + * This file is part of MPlayer. + * + * MPlayer 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. + * + * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* The principle: Make rear channels by extracting anti-phase data + from the front channels, delay by 20ms and feed to rear in anti-phase +*/ + + +/* SPLITREAR: Define to decode two distinct rear channels - this + doesn't work so well in practice because separation in a passive + matrix is not high. C (dialogue) to Ls and Rs 14dB or so - so + dialogue leaks to the rear. Still - give it a try and send + feedback. Comment this define for old behavior of a single + surround sent to rear in anti-phase */ +#define SPLITREAR 1 + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "af.h" +#include "dsp.h" + +#define L 32 // Length of fir filter +#define LD 65536 // Length of delay buffer + +// 32 Tap fir filter loop unrolled +#define FIR(x,w,y) \ + y = ( w[0] *x[0] +w[1] *x[1] +w[2] *x[2] +w[3] *x[3] \ + + w[4] *x[4] +w[5] *x[5] +w[6] *x[6] +w[7] *x[7] \ + + w[8] *x[8] +w[9] *x[9] +w[10]*x[10]+w[11]*x[11] \ + + w[12]*x[12]+w[13]*x[13]+w[14]*x[14]+w[15]*x[15] \ + + w[16]*x[16]+w[17]*x[17]+w[18]*x[18]+w[19]*x[19] \ + + w[20]*x[20]+w[21]*x[21]+w[22]*x[22]+w[23]*x[23] \ + + w[24]*x[24]+w[25]*x[25]+w[26]*x[26]+w[27]*x[27] \ + + w[28]*x[28]+w[29]*x[29]+w[30]*x[30]+w[31]*x[31]) + +// Add to circular queue macro + update index +#ifdef SPLITREAR +#define ADDQUE(qi,rq,lq,r,l)\ + lq[qi]=lq[qi+L]=(l);\ + rq[qi]=rq[qi+L]=(r);\ + qi=(qi-1)&(L-1); +#else +#define ADDQUE(qi,lq,l)\ + lq[qi]=lq[qi+L]=(l);\ + qi=(qi-1)&(L-1); +#endif + +// Macro for updating queue index in delay queues +#define UPDATEQI(qi) qi=(qi+1)&(LD-1) + +// instance data +typedef struct af_surround_s +{ + float lq[2*L]; // Circular queue for filtering left rear channel + float rq[2*L]; // Circular queue for filtering right rear channel + float w[L]; // FIR filter coefficients for surround sound 7kHz low-pass + float* dr; // Delay queue right rear channel + float* dl; // Delay queue left rear channel + float d; // Delay time + int i; // Position in circular buffer + int wi; // Write index for delay queue + int ri; // Read index for delay queue +}af_surround_t; + +// Initialization and runtime control +static int control(struct af_instance* af, int cmd, void* arg) +{ + af_surround_t *s = af->setup; + switch(cmd){ + case AF_CONTROL_REINIT:{ + float fc; + af->data->rate = ((struct mp_audio*)arg)->rate; + af->data->nch = ((struct mp_audio*)arg)->nch*2; + af->data->format = AF_FORMAT_FLOAT_NE; + af->data->bps = 4; + + if (af->data->nch != 4){ + mp_msg(MSGT_AFILTER, MSGL_ERR, "[surround] Only stereo input is supported.\n"); + return AF_DETACH; + } + // Surround filer coefficients + fc = 2.0 * 7000.0/(float)af->data->rate; + if (-1 == af_filter_design_fir(L, s->w, &fc, LP|HAMMING, 0)){ + mp_msg(MSGT_AFILTER, MSGL_ERR, "[surround] Unable to design low-pass filter.\n"); + return AF_ERROR; + } + + // Free previous delay queues + free(s->dl); + free(s->dr); + // Allocate new delay queues + s->dl = calloc(LD,af->data->bps); + s->dr = calloc(LD,af->data->bps); + if((NULL == s->dl) || (NULL == s->dr)) + mp_msg(MSGT_AFILTER, MSGL_FATAL, "[delay] Out of memory\n"); + + // Initialize delay queue index + if(AF_OK != af_from_ms(1, &s->d, &s->wi, af->data->rate, 0.0, 1000.0)) + return AF_ERROR; +// printf("%i\n",s->wi); + s->ri = 0; + + if((af->data->format != ((struct mp_audio*)arg)->format) || + (af->data->bps != ((struct mp_audio*)arg)->bps)){ + ((struct mp_audio*)arg)->format = af->data->format; + ((struct mp_audio*)arg)->bps = af->data->bps; + return AF_FALSE; + } + return AF_OK; + } + case AF_CONTROL_COMMAND_LINE:{ + float d = 0; + sscanf((char*)arg,"%f",&d); + if ((d < 0) || (d > 1000)){ + mp_msg(MSGT_AFILTER, MSGL_ERR, "[surround] Invalid delay time, valid time values" + " are 0ms to 1000ms current value is %0.3f ms\n",d); + return AF_ERROR; + } + s->d = d; + return AF_OK; + } + } + return AF_UNKNOWN; +} + +// Deallocate memory +static void uninit(struct af_instance* af) +{ + if(af->data) + free(af->data->audio); + free(af->data); + free(af->setup); +} + +// The beginnings of an active matrix... +static float steering_matrix[][12] = { +// LL RL LR RR LS RS +// LLs RLs LRs RRs LC RC + {.707, .0, .0, .707, .5, -.5, + .5878, -.3928, .3928, -.5878, .5, .5}, +}; + +// Experimental moving average dominance +//static int amp_L = 0, amp_R = 0, amp_C = 0, amp_S = 0; + +// Filter data through filter +static struct mp_audio* play(struct af_instance* af, struct mp_audio* data){ + af_surround_t* s = (af_surround_t*)af->setup; + float* m = steering_matrix[0]; + float* in = data->audio; // Input audio data + float* out = NULL; // Output audio data + float* end = in + data->len / sizeof(float); // Loop end + int i = s->i; // Filter queue index + int ri = s->ri; // Read index for delay queue + int wi = s->wi; // Write index for delay queue + + if (AF_OK != RESIZE_LOCAL_BUFFER(af, data)) + return NULL; + + out = af->data->audio; + + while(in < end){ + /* Dominance: + abs(in[0]) abs(in[1]); + abs(in[0]+in[1]) abs(in[0]-in[1]); + 10 * log( abs(in[0]) / (abs(in[1])|1) ); + 10 * log( abs(in[0]+in[1]) / (abs(in[0]-in[1])|1) ); */ + + /* About volume balancing... + Surround encoding does the following: + Lt=L+.707*C+.707*S, Rt=R+.707*C-.707*S + So S should be extracted as: + (Lt-Rt) + But we are splitting the S to two output channels, so we + must take 3dB off as we split it: + Ls=Rs=.707*(Lt-Rt) + Trouble is, Lt could be +1, Rt -1, so possibility that S will + overflow. So to avoid that, we cut L/R by 3dB (*.707), and S by + 6dB (/2). This keeps the overall balance, but guarantees no + overflow. */ + + // Output front left and right + out[0] = m[0]*in[0] + m[1]*in[1]; + out[1] = m[2]*in[0] + m[3]*in[1]; + + // Low-pass output @ 7kHz + FIR((&s->lq[i]), s->w, s->dl[wi]); + + // Delay output by d ms + out[2] = s->dl[ri]; + +#ifdef SPLITREAR + // Low-pass output @ 7kHz + FIR((&s->rq[i]), s->w, s->dr[wi]); + + // Delay output by d ms + out[3] = s->dr[ri]; +#else + out[3] = -out[2]; +#endif + + // Update delay queues indexes + UPDATEQI(ri); + UPDATEQI(wi); + + // Calculate and save surround in circular queue +#ifdef SPLITREAR + ADDQUE(i, s->rq, s->lq, m[6]*in[0]+m[7]*in[1], m[8]*in[0]+m[9]*in[1]); +#else + ADDQUE(i, s->lq, m[4]*in[0]+m[5]*in[1]); +#endif + + // Next sample... + in = &in[data->nch]; + out = &out[af->data->nch]; + } + + // Save indexes + s->i = i; s->ri = ri; s->wi = wi; + + // Set output data + data->audio = af->data->audio; + data->len *= 2; + data->nch = af->data->nch; + + return data; +} + +static int af_open(struct af_instance* af){ + af->control=control; + af->uninit=uninit; + af->play=play; + af->mul=2; + af->data=calloc(1,sizeof(struct mp_audio)); + af->setup=calloc(1,sizeof(af_surround_t)); + if(af->data == NULL || af->setup == NULL) + return AF_ERROR; + ((af_surround_t*)af->setup)->d = 20; + return AF_OK; +} + +struct af_info af_info_surround = +{ + "Surround decoder filter", + "surround", + "Steve Davies <steve@daviesfam.org>", + "", + AF_FLAGS_NOT_REENTRANT, + af_open +}; diff --git a/audio/filter/af_sweep.c b/audio/filter/af_sweep.c new file mode 100644 index 0000000000..6d1106fefc --- /dev/null +++ b/audio/filter/af_sweep.c @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2004 Michael Niedermayer <michaelni@gmx.at> + * + * This file is part of MPlayer. + * + * MPlayer 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. + * + * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <inttypes.h> +#include <math.h> + +#include "config.h" +#include "af.h" + +typedef struct af_sweep_s{ + double x; + double delta; +}af_sweept; + + +// Initialization and runtime control +static int control(struct af_instance* af, int cmd, void* arg) +{ + af_sweept* s = (af_sweept*)af->setup; + struct mp_audio *data= (struct mp_audio*)arg; + + switch(cmd){ + case AF_CONTROL_REINIT: + af->data->nch = data->nch; + af->data->format = AF_FORMAT_S16_NE; + af->data->bps = 2; + af->data->rate = data->rate; + + return AF_OK; + case AF_CONTROL_COMMAND_LINE: + sscanf((char*)arg,"%lf", &s->delta); + return AF_OK; +/* case AF_CONTROL_RESAMPLE_RATE | AF_CONTROL_SET: + af->data->rate = *(int*)arg; + return AF_OK;*/ + } + return AF_UNKNOWN; +} + +// Deallocate memory +static void uninit(struct af_instance* af) +{ + free(af->data); + free(af->setup); +} + +// Filter data through filter +static struct mp_audio* play(struct af_instance* af, struct mp_audio* data) +{ + af_sweept *s = af->setup; + int i, j; + int16_t *in = (int16_t*)data->audio; + int chans = data->nch; + int in_len = data->len/(2*chans); + + for(i=0; i<in_len; i++){ + for(j=0; j<chans; j++) + in[i*chans+j]= 32000*sin(s->x*s->x); + s->x += s->delta; + if(2*s->x*s->delta >= 3.141592) s->x=0; + } + + return data; +} + +static int af_open(struct af_instance* af){ + af->control=control; + af->uninit=uninit; + af->play=play; + af->mul=1; + af->data=calloc(1,sizeof(struct mp_audio)); + af->setup=calloc(1,sizeof(af_sweept)); + return AF_OK; +} + +struct af_info af_info_sweep = { + "sine sweep", + "sweep", + "Michael Niedermayer", + "", + AF_FLAGS_REENTRANT, + af_open +}; diff --git a/audio/filter/af_tools.c b/audio/filter/af_tools.c new file mode 100644 index 0000000000..0d5dc6c573 --- /dev/null +++ b/audio/filter/af_tools.c @@ -0,0 +1,110 @@ +/* + * This file is part of MPlayer. + * + * MPlayer 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. + * + * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <math.h> +#include <string.h> +#include "af.h" + +/* Convert to gain value from dB. Returns AF_OK if of and AF_ERROR if + fail */ +int af_from_dB(int n, float* in, float* out, float k, float mi, float ma) +{ + int i = 0; + // Sanity check + if(!in || !out) + return AF_ERROR; + + for(i=0;i<n;i++){ + if(in[i]<=-200) + out[i]=0.0; + else + out[i]=pow(10.0,clamp(in[i],mi,ma)/k); + } + return AF_OK; +} + +/* Convert from gain value to dB. Returns AF_OK if of and AF_ERROR if + fail */ +int af_to_dB(int n, float* in, float* out, float k) +{ + int i = 0; + // Sanity check + if(!in || !out) + return AF_ERROR; + + for(i=0;i<n;i++){ + if(in[i] == 0.0) + out[i]=-200.0; + else + out[i]=k*log10(in[i]); + } + return AF_OK; +} + +/* Convert from ms to sample time */ +int af_from_ms(int n, float* in, int* out, int rate, float mi, float ma) +{ + int i = 0; + // Sanity check + if(!in || !out) + return AF_ERROR; + + for(i=0;i<n;i++) + out[i]=(int)((float)rate * clamp(in[i],mi,ma)/1000.0); + + return AF_OK; +} + +/* Convert from sample time to ms */ +int af_to_ms(int n, int* in, float* out, int rate) +{ + int i = 0; + // Sanity check + if(!in || !out || !rate) + return AF_ERROR; + + for(i=0;i<n;i++) + out[i]=1000.0 * (float)in[i]/((float)rate); + + return AF_OK; +} + +/* Helper function for testing the output format */ +int af_test_output(struct af_instance* af, struct mp_audio* out) +{ + if((af->data->format != out->format) || + (af->data->bps != out->bps) || + (af->data->rate != out->rate) || + (af->data->nch != out->nch)){ + memcpy(out,af->data,sizeof(struct mp_audio)); + return AF_FALSE; + } + return AF_OK; +} + +/* Soft clipping, the sound of a dream, thanks to Jon Wattes + post to Musicdsp.org */ +float af_softclip(float a) +{ + if (a >= M_PI/2) + return 1.0; + else if (a <= -M_PI/2) + return -1.0; + else + return sin(a); +} diff --git a/audio/filter/af_volnorm.c b/audio/filter/af_volnorm.c new file mode 100644 index 0000000000..b4c204d305 --- /dev/null +++ b/audio/filter/af_volnorm.c @@ -0,0 +1,353 @@ +/* + * Copyright (C) 2004 Alex Beregszaszi & Pierre Lombard + * + * This file is part of MPlayer. + * + * MPlayer 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. + * + * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <inttypes.h> +#include <math.h> +#include <limits.h> + +#include "af.h" + +// Methods: +// 1: uses a 1 value memory and coefficients new=a*old+b*cur (with a+b=1) +// 2: uses several samples to smooth the variations (standard weighted mean +// on past samples) + +// Size of the memory array +// FIXME: should depend on the frequency of the data (should be a few seconds) +#define NSAMPLES 128 + +// If summing all the mem[].len is lower than MIN_SAMPLE_SIZE bytes, then we +// choose to ignore the computed value as it's not significant enough +// FIXME: should depend on the frequency of the data (0.5s maybe) +#define MIN_SAMPLE_SIZE 32000 + +// mul is the value by which the samples are scaled +// and has to be in [MUL_MIN, MUL_MAX] +#define MUL_INIT 1.0 +#define MUL_MIN 0.1 +#define MUL_MAX 5.0 + +// Silence level +// FIXME: should be relative to the level of the samples +#define SIL_S16 (SHRT_MAX * 0.01) +#define SIL_FLOAT (INT_MAX * 0.01) // FIXME + +// smooth must be in ]0.0, 1.0[ +#define SMOOTH_MUL 0.06 +#define SMOOTH_LASTAVG 0.06 + +#define DEFAULT_TARGET 0.25 + +// Data for specific instances of this filter +typedef struct af_volume_s +{ + int method; // method used + float mul; + // method 1 + float lastavg; // history value of the filter + // method 2 + int idx; + struct { + float avg; // average level of the sample + int len; // sample size (weight) + } mem[NSAMPLES]; + // "Ideal" level + float mid_s16; + float mid_float; +}af_volnorm_t; + +// Initialization and runtime control +static int control(struct af_instance* af, int cmd, void* arg) +{ + af_volnorm_t* s = (af_volnorm_t*)af->setup; + + switch(cmd){ + case AF_CONTROL_REINIT: + // Sanity check + if(!arg) return AF_ERROR; + + af->data->rate = ((struct mp_audio*)arg)->rate; + af->data->nch = ((struct mp_audio*)arg)->nch; + + if(((struct mp_audio*)arg)->format == (AF_FORMAT_S16_NE)){ + af->data->format = AF_FORMAT_S16_NE; + af->data->bps = 2; + }else{ + af->data->format = AF_FORMAT_FLOAT_NE; + af->data->bps = 4; + } + return af_test_output(af,(struct mp_audio*)arg); + case AF_CONTROL_COMMAND_LINE:{ + int i = 0; + float target = DEFAULT_TARGET; + sscanf((char*)arg,"%d:%f", &i, &target); + if (i != 1 && i != 2) + return AF_ERROR; + s->method = i-1; + s->mid_s16 = ((float)SHRT_MAX) * target; + s->mid_float = ((float)INT_MAX) * target; + return AF_OK; + } + } + return AF_UNKNOWN; +} + +// Deallocate memory +static void uninit(struct af_instance* af) +{ + free(af->data); + free(af->setup); +} + +static void method1_int16(af_volnorm_t *s, struct mp_audio *c) +{ + register int i = 0; + int16_t *data = (int16_t*)c->audio; // Audio data + int len = c->len/2; // Number of samples + float curavg = 0.0, newavg, neededmul; + int tmp; + + for (i = 0; i < len; i++) + { + tmp = data[i]; + curavg += tmp * tmp; + } + curavg = sqrt(curavg / (float) len); + + // Evaluate an adequate 'mul' coefficient based on previous state, current + // samples level, etc + + if (curavg > SIL_S16) + { + neededmul = s->mid_s16 / (curavg * s->mul); + s->mul = (1.0 - SMOOTH_MUL) * s->mul + SMOOTH_MUL * neededmul; + + // clamp the mul coefficient + s->mul = clamp(s->mul, MUL_MIN, MUL_MAX); + } + + // Scale & clamp the samples + for (i = 0; i < len; i++) + { + tmp = s->mul * data[i]; + tmp = clamp(tmp, SHRT_MIN, SHRT_MAX); + data[i] = tmp; + } + + // Evaulation of newavg (not 100% accurate because of values clamping) + newavg = s->mul * curavg; + + // Stores computed values for future smoothing + s->lastavg = (1.0 - SMOOTH_LASTAVG) * s->lastavg + SMOOTH_LASTAVG * newavg; +} + +static void method1_float(af_volnorm_t *s, struct mp_audio *c) +{ + register int i = 0; + float *data = (float*)c->audio; // Audio data + int len = c->len/4; // Number of samples + float curavg = 0.0, newavg, neededmul, tmp; + + for (i = 0; i < len; i++) + { + tmp = data[i]; + curavg += tmp * tmp; + } + curavg = sqrt(curavg / (float) len); + + // Evaluate an adequate 'mul' coefficient based on previous state, current + // samples level, etc + + if (curavg > SIL_FLOAT) // FIXME + { + neededmul = s->mid_float / (curavg * s->mul); + s->mul = (1.0 - SMOOTH_MUL) * s->mul + SMOOTH_MUL * neededmul; + + // clamp the mul coefficient + s->mul = clamp(s->mul, MUL_MIN, MUL_MAX); + } + + // Scale & clamp the samples + for (i = 0; i < len; i++) + data[i] *= s->mul; + + // Evaulation of newavg (not 100% accurate because of values clamping) + newavg = s->mul * curavg; + + // Stores computed values for future smoothing + s->lastavg = (1.0 - SMOOTH_LASTAVG) * s->lastavg + SMOOTH_LASTAVG * newavg; +} + +static void method2_int16(af_volnorm_t *s, struct mp_audio *c) +{ + register int i = 0; + int16_t *data = (int16_t*)c->audio; // Audio data + int len = c->len/2; // Number of samples + float curavg = 0.0, newavg, avg = 0.0; + int tmp, totallen = 0; + + for (i = 0; i < len; i++) + { + tmp = data[i]; + curavg += tmp * tmp; + } + curavg = sqrt(curavg / (float) len); + + // Evaluate an adequate 'mul' coefficient based on previous state, current + // samples level, etc + for (i = 0; i < NSAMPLES; i++) + { + avg += s->mem[i].avg * (float)s->mem[i].len; + totallen += s->mem[i].len; + } + + if (totallen > MIN_SAMPLE_SIZE) + { + avg /= (float)totallen; + if (avg >= SIL_S16) + { + s->mul = s->mid_s16 / avg; + s->mul = clamp(s->mul, MUL_MIN, MUL_MAX); + } + } + + // Scale & clamp the samples + for (i = 0; i < len; i++) + { + tmp = s->mul * data[i]; + tmp = clamp(tmp, SHRT_MIN, SHRT_MAX); + data[i] = tmp; + } + + // Evaulation of newavg (not 100% accurate because of values clamping) + newavg = s->mul * curavg; + + // Stores computed values for future smoothing + s->mem[s->idx].len = len; + s->mem[s->idx].avg = newavg; + s->idx = (s->idx + 1) % NSAMPLES; +} + +static void method2_float(af_volnorm_t *s, struct mp_audio *c) +{ + register int i = 0; + float *data = (float*)c->audio; // Audio data + int len = c->len/4; // Number of samples + float curavg = 0.0, newavg, avg = 0.0, tmp; + int totallen = 0; + + for (i = 0; i < len; i++) + { + tmp = data[i]; + curavg += tmp * tmp; + } + curavg = sqrt(curavg / (float) len); + + // Evaluate an adequate 'mul' coefficient based on previous state, current + // samples level, etc + for (i = 0; i < NSAMPLES; i++) + { + avg += s->mem[i].avg * (float)s->mem[i].len; + totallen += s->mem[i].len; + } + + if (totallen > MIN_SAMPLE_SIZE) + { + avg /= (float)totallen; + if (avg >= SIL_FLOAT) + { + s->mul = s->mid_float / avg; + s->mul = clamp(s->mul, MUL_MIN, MUL_MAX); + } + } + + // Scale & clamp the samples + for (i = 0; i < len; i++) + data[i] *= s->mul; + + // Evaulation of newavg (not 100% accurate because of values clamping) + newavg = s->mul * curavg; + + // Stores computed values for future smoothing + s->mem[s->idx].len = len; + s->mem[s->idx].avg = newavg; + s->idx = (s->idx + 1) % NSAMPLES; +} + +// Filter data through filter +static struct mp_audio* play(struct af_instance* af, struct mp_audio* data) +{ + af_volnorm_t *s = af->setup; + + if(af->data->format == (AF_FORMAT_S16_NE)) + { + if (s->method) + method2_int16(s, data); + else + method1_int16(s, data); + } + else if(af->data->format == (AF_FORMAT_FLOAT_NE)) + { + if (s->method) + method2_float(s, data); + else + method1_float(s, data); + } + return data; +} + +// Allocate memory and set function pointers +static int af_open(struct af_instance* af){ + int i = 0; + af->control=control; + af->uninit=uninit; + af->play=play; + af->mul=1; + af->data=calloc(1,sizeof(struct mp_audio)); + af->setup=calloc(1,sizeof(af_volnorm_t)); + if(af->data == NULL || af->setup == NULL) + return AF_ERROR; + + ((af_volnorm_t*)af->setup)->mul = MUL_INIT; + ((af_volnorm_t*)af->setup)->lastavg = ((float)SHRT_MAX) * DEFAULT_TARGET; + ((af_volnorm_t*)af->setup)->idx = 0; + ((af_volnorm_t*)af->setup)->mid_s16 = ((float)SHRT_MAX) * DEFAULT_TARGET; + ((af_volnorm_t*)af->setup)->mid_float = ((float)INT_MAX) * DEFAULT_TARGET; + for (i = 0; i < NSAMPLES; i++) + { + ((af_volnorm_t*)af->setup)->mem[i].len = 0; + ((af_volnorm_t*)af->setup)->mem[i].avg = 0; + } + return AF_OK; +} + +// Description of this filter +struct af_info af_info_volnorm = { + "Volume normalizer filter", + "volnorm", + "Alex Beregszaszi & Pierre Lombard", + "", + AF_FLAGS_NOT_REENTRANT, + af_open +}; diff --git a/audio/filter/af_volume.c b/audio/filter/af_volume.c new file mode 100644 index 0000000000..ecf181c8b8 --- /dev/null +++ b/audio/filter/af_volume.c @@ -0,0 +1,226 @@ +/* + * Copyright (C)2002 Anders Johansson ajh@atri.curtin.edu.au + * + * This file is part of MPlayer. + * + * MPlayer 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. + * + * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* This audio filter changes the volume of the sound, and can be used + when the mixer doesn't support the PCM channel. It can handle + between 1 and AF_NCH channels. The volume can be adjusted between -60dB + to +20dB and is set on a per channels basis. The is accessed through + AF_CONTROL_VOLUME_LEVEL. + + The filter has support for soft-clipping, it is enabled by + AF_CONTROL_VOLUME_SOFTCLIPP. It has also a probing feature which + can be used to measure the power in the audio stream, both an + instantaneous value and the maximum value can be probed. The + probing is enable by AF_CONTROL_VOLUME_PROBE_ON_OFF and is done on a + per channel basis. The result from the probing is obtained using + AF_CONTROL_VOLUME_PROBE_GET and AF_CONTROL_VOLUME_PROBE_GET_MAX. The + probed values are calculated in dB. +*/ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <inttypes.h> +#include <math.h> +#include <limits.h> + +#include "af.h" + +// Data for specific instances of this filter +typedef struct af_volume_s +{ + int enable[AF_NCH]; // Enable/disable / channel + float pow[AF_NCH]; // Estimated power level [dB] + float max[AF_NCH]; // Max Power level [dB] + float level[AF_NCH]; // Gain level for each channel + float time; // Forgetting factor for power estimate + int soft; // Enable/disable soft clipping + int fast; // Use fix-point volume control +}af_volume_t; + +// Initialization and runtime control +static int control(struct af_instance* af, int cmd, void* arg) +{ + af_volume_t* s = (af_volume_t*)af->setup; + + switch(cmd){ + case AF_CONTROL_REINIT: + // Sanity check + if(!arg) return AF_ERROR; + + af->data->rate = ((struct mp_audio*)arg)->rate; + af->data->nch = ((struct mp_audio*)arg)->nch; + + if(s->fast && (((struct mp_audio*)arg)->format != (AF_FORMAT_FLOAT_NE))){ + af->data->format = AF_FORMAT_S16_NE; + af->data->bps = 2; + } + else{ + // Cutoff set to 10Hz for forgetting factor + float x = 2.0*M_PI*15.0/(float)af->data->rate; + float t = 2.0-cos(x); + s->time = 1.0 - (t - sqrt(t*t - 1)); + mp_msg(MSGT_AFILTER, MSGL_DBG2, "[volume] Forgetting factor = %0.5f\n",s->time); + af->data->format = AF_FORMAT_FLOAT_NE; + af->data->bps = 4; + } + return af_test_output(af,(struct mp_audio*)arg); + case AF_CONTROL_COMMAND_LINE:{ + float v=0.0; + float vol[AF_NCH]; + int i; + sscanf((char*)arg,"%f:%i", &v, &s->soft); + for(i=0;i<AF_NCH;i++) vol[i]=v; + return control(af,AF_CONTROL_VOLUME_LEVEL | AF_CONTROL_SET, vol); + } + case AF_CONTROL_POST_CREATE: + s->fast = ((((struct af_cfg*)arg)->force & AF_INIT_FORMAT_MASK) == + AF_INIT_FLOAT) ? 0 : 1; + return AF_OK; + case AF_CONTROL_VOLUME_ON_OFF | AF_CONTROL_SET: + memcpy(s->enable,(int*)arg,AF_NCH*sizeof(int)); + return AF_OK; + case AF_CONTROL_VOLUME_ON_OFF | AF_CONTROL_GET: + memcpy((int*)arg,s->enable,AF_NCH*sizeof(int)); + return AF_OK; + case AF_CONTROL_VOLUME_SOFTCLIP | AF_CONTROL_SET: + s->soft = *(int*)arg; + return AF_OK; + case AF_CONTROL_VOLUME_SOFTCLIP | AF_CONTROL_GET: + *(int*)arg = s->soft; + return AF_OK; + case AF_CONTROL_VOLUME_LEVEL | AF_CONTROL_SET: + return af_from_dB(AF_NCH,(float*)arg,s->level,20.0,-200.0,60.0); + case AF_CONTROL_VOLUME_LEVEL | AF_CONTROL_GET: + return af_to_dB(AF_NCH,s->level,(float*)arg,20.0); + case AF_CONTROL_VOLUME_PROBE | AF_CONTROL_GET: + return af_to_dB(AF_NCH,s->pow,(float*)arg,10.0); + case AF_CONTROL_VOLUME_PROBE_MAX | AF_CONTROL_GET: + return af_to_dB(AF_NCH,s->max,(float*)arg,10.0); + case AF_CONTROL_PRE_DESTROY:{ + float m = 0.0; + int i; + if(!s->fast){ + for(i=0;i<AF_NCH;i++) + m=max(m,s->max[i]); + af_to_dB(1, &m, &m, 10.0); + mp_msg(MSGT_AFILTER, MSGL_INFO, "[volume] The maximum volume was %0.2fdB \n", m); + } + return AF_OK; + } + } + return AF_UNKNOWN; +} + +// Deallocate memory +static void uninit(struct af_instance* af) +{ + free(af->data); + free(af->setup); +} + +// Filter data through filter +static struct mp_audio* play(struct af_instance* af, struct mp_audio* data) +{ + struct mp_audio* c = data; // Current working data + af_volume_t* s = (af_volume_t*)af->setup; // Setup for this instance + register int nch = c->nch; // Number of channels + register int i = 0; + + // Basic operation volume control only (used on slow machines) + if(af->data->format == (AF_FORMAT_S16_NE)){ + int16_t* a = (int16_t*)c->audio; // Audio data + int len = c->len/2; // Number of samples + for (int ch = 0; ch < nch; ch++) { + int vol = 256.0 * s->level[ch]; + if (s->enable[ch] && vol != 256) { + for(i=ch;i<len;i+=nch){ + register int x = (a[i] * vol) >> 8; + a[i]=clamp(x,SHRT_MIN,SHRT_MAX); + } + } + } + } + // Machine is fast and data is floating point + else if(af->data->format == (AF_FORMAT_FLOAT_NE)){ + float* a = (float*)c->audio; // Audio data + int len = c->len/4; // Number of samples + for (int ch = 0; ch < nch; ch++) { + // Volume control (fader) + if(s->enable[ch]){ + float t = 1.0 - s->time; + for(i=ch;i<len;i+=nch){ + register float x = a[i]; + register float pow = x*x; + // Check maximum power value + if(pow > s->max[ch]) + s->max[ch] = pow; + // Set volume + x *= s->level[ch]; + // Peak meter + pow = x*x; + if(pow > s->pow[ch]) + s->pow[ch] = pow; + else + s->pow[ch] = t*s->pow[ch] + pow*s->time; // LP filter + /* Soft clipping, the sound of a dream, thanks to Jon Wattes + post to Musicdsp.org */ + if(s->soft) + x=af_softclip(x); + // Hard clipping + else + x=clamp(x,-1.0,1.0); + a[i] = x; + } + } + } + } + return c; +} + +// Allocate memory and set function pointers +static int af_open(struct af_instance* af){ + int i = 0; + af->control=control; + af->uninit=uninit; + af->play=play; + af->mul=1; + af->data=calloc(1,sizeof(struct mp_audio)); + af->setup=calloc(1,sizeof(af_volume_t)); + if(af->data == NULL || af->setup == NULL) + return AF_ERROR; + // Enable volume control and set initial volume to 0dB. + for(i=0;i<AF_NCH;i++){ + ((af_volume_t*)af->setup)->enable[i] = 1; + ((af_volume_t*)af->setup)->level[i] = 1.0; + } + return AF_OK; +} + +// Description of this filter +struct af_info af_info_volume = { + "Volume control audio filter", + "volume", + "Anders", + "", + AF_FLAGS_NOT_REENTRANT, + af_open +}; diff --git a/audio/filter/control.h b/audio/filter/control.h new file mode 100644 index 0000000000..323b9a3924 --- /dev/null +++ b/audio/filter/control.h @@ -0,0 +1,257 @@ +/* + * This file is part of MPlayer. + * + * MPlayer 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. + * + * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPLAYER_CONTROL_H +#define MPLAYER_CONTROL_H + +#include <sys/types.h> + +/********************************************* +// Control info struct. +// +// This struct is the argument in a info call to a filter. +*/ + +// Argument types +#define AF_CONTROL_TYPE_BOOL (0x0<<0) +#define AF_CONTROL_TYPE_CHAR (0x1<<0) +#define AF_CONTROL_TYPE_INT (0x2<<0) +#define AF_CONTROL_TYPE_FLOAT (0x3<<0) +#define AF_CONTROL_TYPE_STRUCT (0x4<<0) +#define AF_CONTROL_TYPE_SPECIAL (0x5<<0) // a pointer to a function for example +#define AF_CONTROL_TYPE_MASK (0x7<<0) +// Argument geometry +#define AF_CONTROL_GEOM_SCALAR (0x0<<3) +#define AF_CONTROL_GEOM_ARRAY (0x1<<3) +#define AF_CONTROL_GEOM_MATRIX (0x2<<3) +#define AF_CONTROL_GEOM_MASK (0x3<<3) +// Argument properties +#define AF_CONTROL_PROP_READ (0x0<<5) // The argument can be read +#define AF_CONTROL_PROP_WRITE (0x1<<5) // The argument can be written +#define AF_CONTROL_PROP_SAVE (0x2<<5) // Can be saved +#define AF_CONTROL_PROP_RUNTIME (0x4<<5) // Acessable during execution +#define AF_CONTROL_PROP_CHANNEL (0x8<<5) // Argument is set per channel +#define AF_CONTROL_PROP_MASK (0xF<<5) + +typedef struct af_control_info_s{ + int def; // Control enumrification + char* name; // Name of argument + char* info; // Description of what it does + int flags; // Flags as defined above + float max; // Max and min value + float min; // (only aplicable on float and int) + int xdim; // 1st dimension + int ydim; // 2nd dimension (=0 for everything except matrix) + size_t sz; // Size of argument in bytes + int ch; // Channel number (for future use) + void* arg; // Data (for future use) +}af_control_info_t; + + +/********************************************* +// Extended control used with arguments that operates on only one +// channel at the time +*/ +typedef struct af_control_ext_s{ + void* arg; // Argument + int ch; // Chanel number +}af_control_ext_t; + +/********************************************* +// Control parameters +*/ + +/* The control system is divided into 3 levels + mandatory calls - all filters must answer to all of these + optional calls - are optional + filter specific calls - applies only to some filters +*/ + +#define AF_CONTROL_MANDATORY 0x10000000 +#define AF_CONTROL_OPTIONAL 0x20000000 +#define AF_CONTROL_FILTER_SPECIFIC 0x40000000 + +// MANDATORY CALLS + +/* Reinitialize filter. The optional argument contains the new + configuration in form of a struct mp_audio struct. If the filter does not + support the new format the struct should be changed and AF_FALSE + should be returned. If the incoming and outgoing data streams are + identical the filter can return AF_DETACH. This will remove the + filter. */ +#define AF_CONTROL_REINIT 0x00000100 | AF_CONTROL_MANDATORY + +// OPTIONAL CALLS + +/* Called just after creation with the af_cfg for the stream in which + the filter resides as input parameter this call can be used by the + filter to initialize itself */ +#define AF_CONTROL_POST_CREATE 0x00000100 | AF_CONTROL_OPTIONAL + +// Called just before destruction of a filter +#define AF_CONTROL_PRE_DESTROY 0x00000200 | AF_CONTROL_OPTIONAL + +/* Commandline parameters. If there were any commandline parameters + for this specific filter, they will be given as a char* in the + argument */ +#define AF_CONTROL_COMMAND_LINE 0x00000300 | AF_CONTROL_OPTIONAL + + +// FILTER SPECIFIC CALLS + +// Basic operations: These can be ored with any of the below calls +// Set argument +#define AF_CONTROL_SET 0x00000000 +// Get argument +#define AF_CONTROL_GET 0x00000001 +// Get info about the control, i.e fill in everything except argument +#define AF_CONTROL_INFO 0x00000002 + +// Resample + +// Set output rate in resample +#define AF_CONTROL_RESAMPLE_RATE 0x00000100 | AF_CONTROL_FILTER_SPECIFIC + +// Enable sloppy resampling +#define AF_CONTROL_RESAMPLE_SLOPPY 0x00000200 | AF_CONTROL_FILTER_SPECIFIC + +// Set resampling accuracy +#define AF_CONTROL_RESAMPLE_ACCURACY 0x00000300 | AF_CONTROL_FILTER_SPECIFIC + +// Format + +#define AF_CONTROL_FORMAT_FMT 0x00000400 | AF_CONTROL_FILTER_SPECIFIC + +// Channels + +// Set number of output channels in channels +#define AF_CONTROL_CHANNELS 0x00000600 | AF_CONTROL_FILTER_SPECIFIC + +// Set number of channel routes +#define AF_CONTROL_CHANNELS_ROUTES 0x00000700 | AF_CONTROL_FILTER_SPECIFIC + +// Set channel routing pair, arg is int[2] and ch is used +#define AF_CONTROL_CHANNELS_ROUTING 0x00000800 | AF_CONTROL_FILTER_SPECIFIC + +// Set nuber of channel routing pairs, arg is int* +#define AF_CONTROL_CHANNELS_NR 0x00000900 | AF_CONTROL_FILTER_SPECIFIC + +// Set make af_channels into a router +#define AF_CONTROL_CHANNELS_ROUTER 0x00000A00 | AF_CONTROL_FILTER_SPECIFIC + +// Volume + +// Turn volume control on and off, arg is int* +#define AF_CONTROL_VOLUME_ON_OFF 0x00000B00 | AF_CONTROL_FILTER_SPECIFIC + +// Turn soft clipping of the volume on and off, arg is binary +#define AF_CONTROL_VOLUME_SOFTCLIP 0x00000C00 | AF_CONTROL_FILTER_SPECIFIC + +// Set volume level, arg is a float* with the volume for all the channels +#define AF_CONTROL_VOLUME_LEVEL 0x00000D00 | AF_CONTROL_FILTER_SPECIFIC + +// Probed power level for all channels, arg is a float* +#define AF_CONTROL_VOLUME_PROBE 0x00000E00 | AF_CONTROL_FILTER_SPECIFIC + +// Maximum probed power level for all channels, arg is a float* +#define AF_CONTROL_VOLUME_PROBE_MAX 0x00000F00 | AF_CONTROL_FILTER_SPECIFIC + +// Compressor/expander + +// Turn compressor/expander on and off +#define AF_CONTROL_COMP_ON_OFF 0x00001000 | AF_CONTROL_FILTER_SPECIFIC + +// Compression/expansion threshold [dB] +#define AF_CONTROL_COMP_THRESH 0x00001100 | AF_CONTROL_FILTER_SPECIFIC + +// Compression/expansion attack time [ms] +#define AF_CONTROL_COMP_ATTACK 0x00001200 | AF_CONTROL_FILTER_SPECIFIC + +// Compression/expansion release time [ms] +#define AF_CONTROL_COMP_RELEASE 0x00001300 | AF_CONTROL_FILTER_SPECIFIC + +// Compression/expansion gain level [dB] +#define AF_CONTROL_COMP_RATIO 0x00001400 | AF_CONTROL_FILTER_SPECIFIC + +// Noise gate + +// Turn noise gate on an off +#define AF_CONTROL_GATE_ON_OFF 0x00001500 | AF_CONTROL_FILTER_SPECIFIC + +// Noise gate threshold [dB] +#define AF_CONTROL_GATE_THRESH 0x00001600 | AF_CONTROL_FILTER_SPECIFIC + +// Noise gate attack time [ms] +#define AF_CONTROL_GATE_ATTACK 0x00001700 | AF_CONTROL_FILTER_SPECIFIC + +// Noise gate release time [ms] +#define AF_CONTROL_GATE_RELEASE 0x00001800 | AF_CONTROL_FILTER_SPECIFIC + +// Noise gate release range level [dB] +#define AF_CONTROL_GATE_RANGE 0x00001900 | AF_CONTROL_FILTER_SPECIFIC + +// Pan + +// Pan levels, arg is a control_ext with a float* +#define AF_CONTROL_PAN_LEVEL 0x00001A00 | AF_CONTROL_FILTER_SPECIFIC + +// Number of outputs from pan, arg is int* +#define AF_CONTROL_PAN_NOUT 0x00001B00 | AF_CONTROL_FILTER_SPECIFIC + +// Balance, arg is float*; range -1 (left) to 1 (right), 0 center +#define AF_CONTROL_PAN_BALANCE 0x00001C00 | AF_CONTROL_FILTER_SPECIFIC + +// Set equalizer gain, arg is a control_ext with a float* +#define AF_CONTROL_EQUALIZER_GAIN 0x00001D00 | AF_CONTROL_FILTER_SPECIFIC + + +// Delay length in ms, arg is a control_ext with a float* +#define AF_CONTROL_DELAY_LEN 0x00001E00 | AF_CONTROL_FILTER_SPECIFIC + + +// Subwoofer + +// Channel number which to insert the filtered data, arg in int* +#define AF_CONTROL_SUB_CH 0x00001F00 | AF_CONTROL_FILTER_SPECIFIC + +// Cutoff frequency [Hz] for lowpass filter, arg is float* +#define AF_CONTROL_SUB_FC 0x00002000 | AF_CONTROL_FILTER_SPECIFIC + + +// Export +#define AF_CONTROL_EXPORT_SZ 0x00003000 | AF_CONTROL_FILTER_SPECIFIC + + +// ExtraStereo Multiplier +#define AF_CONTROL_ES_MUL 0x00003100 | AF_CONTROL_FILTER_SPECIFIC + + +// Center + +// Channel number which to inster the filtered data, arg in int* +#define AF_CONTROL_CENTER_CH 0x00003200 | AF_CONTROL_FILTER_SPECIFIC + + +// SineSuppress +#define AF_CONTROL_SS_FREQ 0x00003300 | AF_CONTROL_FILTER_SPECIFIC +#define AF_CONTROL_SS_DECAY 0x00003400 | AF_CONTROL_FILTER_SPECIFIC + +#define AF_CONTROL_PLAYBACK_SPEED 0x00003500 | AF_CONTROL_FILTER_SPECIFIC +#define AF_CONTROL_SCALETEMPO_AMOUNT 0x00003600 | AF_CONTROL_FILTER_SPECIFIC + +#endif /* MPLAYER_CONTROL_H */ diff --git a/audio/filter/dsp.h b/audio/filter/dsp.h new file mode 100644 index 0000000000..561b86cfe0 --- /dev/null +++ b/audio/filter/dsp.h @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2002 Anders Johansson ajh@atri.curtin.edu.au + * + * This file is part of MPlayer. + * + * MPlayer 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. + * + * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPLAYER_DSP_H +#define MPLAYER_DSP_H + +/* Implementation of routines used for DSP */ + +/* Size of floating point type used in routines */ +#define FLOAT_TYPE float + +#include "window.h" +#include "filter.h" + +#endif /* MPLAYER_DSP_H */ diff --git a/audio/filter/equalizer.h b/audio/filter/equalizer.h new file mode 100644 index 0000000000..4935401413 --- /dev/null +++ b/audio/filter/equalizer.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2002 Anders Johansson ajh@atri.curtin.edu.au + * + * This file is part of MPlayer. + * + * MPlayer 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. + * + * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPLAYER_EQUALIZER_H +#define MPLAYER_EQUALIZER_H + +/* Equalizer plugin header file defines struct used for setting or + getting the gain of a specific channel and frequency */ + +typedef struct equalizer_s +{ + float gain; // Gain in dB -15 - 15 + int channel; // Channel number 0 - 5 + int band; // Frequency band 0 - 9 +}equalizer_t; + +/* The different frequency bands are: +nr. center frequency +0 31.25 Hz +1 62.50 Hz +2 125.0 Hz +3 250.0 Hz +4 500.0 Hz +5 1.000 kHz +6 2.000 kHz +7 4.000 kHz +8 8.000 kHz +9 16.00 kHz +*/ + +#endif /* MPLAYER_EQUALIZER_H */ diff --git a/audio/filter/filter.c b/audio/filter/filter.c new file mode 100644 index 0000000000..b272125fd8 --- /dev/null +++ b/audio/filter/filter.c @@ -0,0 +1,360 @@ +/* + * design and implementation of different types of digital filters + * + * Copyright (C) 2001 Anders Johansson ajh@atri.curtin.edu.au + * + * This file is part of MPlayer. + * + * MPlayer 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. + * + * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <string.h> +#include <math.h> +#include "dsp.h" + +/****************************************************************************** +* FIR filter implementations +******************************************************************************/ + +/* C implementation of FIR filter y=w*x + + n number of filter taps, where mod(n,4)==0 + w filter taps + x input signal must be a circular buffer which is indexed backwards +*/ +inline FLOAT_TYPE af_filter_fir(register unsigned int n, const FLOAT_TYPE* w, + const FLOAT_TYPE* x) +{ + register FLOAT_TYPE y; // Output + y = 0.0; + do{ + n--; + y+=w[n]*x[n]; + }while(n != 0); + return y; +} + +/****************************************************************************** +* FIR filter design +******************************************************************************/ + +/* Design FIR filter using the Window method + + n filter length must be odd for HP and BS filters + w buffer for the filter taps (must be n long) + fc cutoff frequencies (1 for LP and HP, 2 for BP and BS) + 0 < fc < 1 where 1 <=> Fs/2 + flags window and filter type as defined in filter.h + variables are ored together: i.e. LP|HAMMING will give a + low pass filter designed using a hamming window + opt beta constant used only when designing using kaiser windows + + returns 0 if OK, -1 if fail +*/ +int af_filter_design_fir(unsigned int n, FLOAT_TYPE* w, const FLOAT_TYPE* fc, + unsigned int flags, FLOAT_TYPE opt) +{ + unsigned int o = n & 1; // Indicator for odd filter length + unsigned int end = ((n + 1) >> 1) - o; // Loop end + unsigned int i; // Loop index + + FLOAT_TYPE k1 = 2 * M_PI; // 2*pi*fc1 + FLOAT_TYPE k2 = 0.5 * (FLOAT_TYPE)(1 - o);// Constant used if the filter has even length + FLOAT_TYPE k3; // 2*pi*fc2 Constant used in BP and BS design + FLOAT_TYPE g = 0.0; // Gain + FLOAT_TYPE t1,t2,t3; // Temporary variables + FLOAT_TYPE fc1,fc2; // Cutoff frequencies + + // Sanity check + if(!w || (n == 0)) return -1; + + // Get window coefficients + switch(flags & WINDOW_MASK){ + case(BOXCAR): + af_window_boxcar(n,w); break; + case(TRIANG): + af_window_triang(n,w); break; + case(HAMMING): + af_window_hamming(n,w); break; + case(HANNING): + af_window_hanning(n,w); break; + case(BLACKMAN): + af_window_blackman(n,w); break; + case(FLATTOP): + af_window_flattop(n,w); break; + case(KAISER): + af_window_kaiser(n,w,opt); break; + default: + return -1; + } + + if(flags & (LP | HP)){ + fc1=*fc; + // Cutoff frequency must be < 0.5 where 0.5 <=> Fs/2 + fc1 = ((fc1 <= 1.0) && (fc1 > 0.0)) ? fc1/2 : 0.25; + k1 *= fc1; + + if(flags & LP){ // Low pass filter + + // If the filter length is odd, there is one point which is exactly + // in the middle. The value at this point is 2*fCutoff*sin(x)/x, + // where x is zero. To make sure nothing strange happens, we set this + // value separately. + if (o){ + w[end] = fc1 * w[end] * 2.0; + g=w[end]; + } + + // Create filter + for (i=0 ; i<end ; i++){ + t1 = (FLOAT_TYPE)(i+1) - k2; + w[end-i-1] = w[n-end+i] = w[end-i-1] * sin(k1 * t1)/(M_PI * t1); // Sinc + g += 2*w[end-i-1]; // Total gain in filter + } + } + else{ // High pass filter + if (!o) // High pass filters must have odd length + return -1; + w[end] = 1.0 - (fc1 * w[end] * 2.0); + g= w[end]; + + // Create filter + for (i=0 ; i<end ; i++){ + t1 = (FLOAT_TYPE)(i+1); + w[end-i-1] = w[n-end+i] = -1 * w[end-i-1] * sin(k1 * t1)/(M_PI * t1); // Sinc + g += ((i&1) ? (2*w[end-i-1]) : (-2*w[end-i-1])); // Total gain in filter + } + } + } + + if(flags & (BP | BS)){ + fc1=fc[0]; + fc2=fc[1]; + // Cutoff frequencies must be < 1.0 where 1.0 <=> Fs/2 + fc1 = ((fc1 <= 1.0) && (fc1 > 0.0)) ? fc1/2 : 0.25; + fc2 = ((fc2 <= 1.0) && (fc2 > 0.0)) ? fc2/2 : 0.25; + k3 = k1 * fc2; // 2*pi*fc2 + k1 *= fc1; // 2*pi*fc1 + + if(flags & BP){ // Band pass + // Calculate center tap + if (o){ + g=w[end]*(fc1+fc2); + w[end] = (fc2 - fc1) * w[end] * 2.0; + } + + // Create filter + for (i=0 ; i<end ; i++){ + t1 = (FLOAT_TYPE)(i+1) - k2; + t2 = sin(k3 * t1)/(M_PI * t1); // Sinc fc2 + t3 = sin(k1 * t1)/(M_PI * t1); // Sinc fc1 + g += w[end-i-1] * (t3 + t2); // Total gain in filter + w[end-i-1] = w[n-end+i] = w[end-i-1] * (t2 - t3); + } + } + else{ // Band stop + if (!o) // Band stop filters must have odd length + return -1; + w[end] = 1.0 - (fc2 - fc1) * w[end] * 2.0; + g= w[end]; + + // Create filter + for (i=0 ; i<end ; i++){ + t1 = (FLOAT_TYPE)(i+1); + t2 = sin(k1 * t1)/(M_PI * t1); // Sinc fc1 + t3 = sin(k3 * t1)/(M_PI * t1); // Sinc fc2 + w[end-i-1] = w[n-end+i] = w[end-i-1] * (t2 - t3); + g += 2*w[end-i-1]; // Total gain in filter + } + } + } + + // Normalize gain + g=1/g; + for (i=0; i<n; i++) + w[i] *= g; + + return 0; +} + +/****************************************************************************** +* IIR filter design +******************************************************************************/ + +/* Helper functions for the bilinear transform */ + +/* Pre-warp the coefficients of a numerator or denominator. + Note that a0 is assumed to be 1, so there is no wrapping + of it. +*/ +static void af_filter_prewarp(FLOAT_TYPE* a, FLOAT_TYPE fc, FLOAT_TYPE fs) +{ + FLOAT_TYPE wp; + wp = 2.0 * fs * tan(M_PI * fc / fs); + a[2] = a[2]/(wp * wp); + a[1] = a[1]/wp; +} + +/* Transform the numerator and denominator coefficients of s-domain + biquad section into corresponding z-domain coefficients. + + The transfer function for z-domain is: + + 1 + alpha1 * z^(-1) + alpha2 * z^(-2) + H(z) = ------------------------------------- + 1 + beta1 * z^(-1) + beta2 * z^(-2) + + Store the 4 IIR coefficients in array pointed by coef in following + order: + beta1, beta2 (denominator) + alpha1, alpha2 (numerator) + + Arguments: + a - s-domain numerator coefficients + b - s-domain denominator coefficients + k - filter gain factor. Initially set to 1 and modified by each + biquad section in such a way, as to make it the + coefficient by which to multiply the overall filter gain + in order to achieve a desired overall filter gain, + specified in initial value of k. + fs - sampling rate (Hz) + coef - array of z-domain coefficients to be filled in. + + Return: On return, set coef z-domain coefficients and k to the gain + required to maintain overall gain = 1.0; +*/ +static void af_filter_bilinear(const FLOAT_TYPE* a, const FLOAT_TYPE* b, FLOAT_TYPE* k, + FLOAT_TYPE fs, FLOAT_TYPE *coef) +{ + FLOAT_TYPE ad, bd; + + /* alpha (Numerator in s-domain) */ + ad = 4. * a[2] * fs * fs + 2. * a[1] * fs + a[0]; + /* beta (Denominator in s-domain) */ + bd = 4. * b[2] * fs * fs + 2. * b[1] * fs + b[0]; + + /* Update gain constant for this section */ + *k *= ad/bd; + + /* Denominator */ + *coef++ = (2. * b[0] - 8. * b[2] * fs * fs)/bd; /* beta1 */ + *coef++ = (4. * b[2] * fs * fs - 2. * b[1] * fs + b[0])/bd; /* beta2 */ + + /* Numerator */ + *coef++ = (2. * a[0] - 8. * a[2] * fs * fs)/ad; /* alpha1 */ + *coef = (4. * a[2] * fs * fs - 2. * a[1] * fs + a[0])/ad; /* alpha2 */ +} + + + +/* IIR filter design using bilinear transform and prewarp. Transforms + 2nd order s domain analog filter into a digital IIR biquad link. To + create a filter fill in a, b, Q and fs and make space for coef and k. + + + Example Butterworth design: + + Below are Butterworth polynomials, arranged as a series of 2nd + order sections: + + Note: n is filter order. + + n Polynomials + ------------------------------------------------------------------- + 2 s^2 + 1.4142s + 1 + 4 (s^2 + 0.765367s + 1) * (s^2 + 1.847759s + 1) + 6 (s^2 + 0.5176387s + 1) * (s^2 + 1.414214 + 1) * (s^2 + 1.931852s + 1) + + For n=4 we have following equation for the filter transfer function: + 1 1 + T(s) = --------------------------- * ---------------------------- + s^2 + (1/Q) * 0.765367s + 1 s^2 + (1/Q) * 1.847759s + 1 + + The filter consists of two 2nd order sections since highest s power + is 2. Now we can take the coefficients, or the numbers by which s + is multiplied and plug them into a standard formula to be used by + bilinear transform. + + Our standard form for each 2nd order section is: + + a2 * s^2 + a1 * s + a0 + H(s) = ---------------------- + b2 * s^2 + b1 * s + b0 + + Note that Butterworth numerator is 1 for all filter sections, which + means s^2 = 0 and s^1 = 0 + + Let's convert standard Butterworth polynomials into this form: + + 0 + 0 + 1 0 + 0 + 1 + --------------------------- * -------------------------- + 1 + ((1/Q) * 0.765367) + 1 1 + ((1/Q) * 1.847759) + 1 + + Section 1: + a2 = 0; a1 = 0; a0 = 1; + b2 = 1; b1 = 0.765367; b0 = 1; + + Section 2: + a2 = 0; a1 = 0; a0 = 1; + b2 = 1; b1 = 1.847759; b0 = 1; + + Q is filter quality factor or resonance, in the range of 1 to + 1000. The overall filter Q is a product of all 2nd order stages. + For example, the 6th order filter (3 stages, or biquads) with + individual Q of 2 will have filter Q = 2 * 2 * 2 = 8. + + + Arguments: + a - s-domain numerator coefficients, a[1] is always assumed to be 1.0 + b - s-domain denominator coefficients + Q - Q value for the filter + k - filter gain factor. Initially set to 1 and modified by each + biquad section in such a way, as to make it the + coefficient by which to multiply the overall filter gain + in order to achieve a desired overall filter gain, + specified in initial value of k. + fs - sampling rate (Hz) + coef - array of z-domain coefficients to be filled in. + + Note: Upon return from each call, the k argument will be set to a + value, by which to multiply our actual signal in order for the gain + to be one. On second call to szxform() we provide k that was + changed by the previous section. During actual audio filtering + k can be used for gain compensation. + + return -1 if fail 0 if success. +*/ +int af_filter_szxform(const FLOAT_TYPE* a, const FLOAT_TYPE* b, FLOAT_TYPE Q, FLOAT_TYPE fc, + FLOAT_TYPE fs, FLOAT_TYPE *k, FLOAT_TYPE *coef) +{ + FLOAT_TYPE at[3]; + FLOAT_TYPE bt[3]; + + if(!a || !b || !k || !coef || (Q>1000.0 || Q< 1.0)) + return -1; + + memcpy(at,a,3*sizeof(FLOAT_TYPE)); + memcpy(bt,b,3*sizeof(FLOAT_TYPE)); + + bt[1]/=Q; + + /* Calculate a and b and overwrite the original values */ + af_filter_prewarp(at, fc, fs); + af_filter_prewarp(bt, fc, fs); + /* Execute bilinear transform */ + af_filter_bilinear(at, bt, k, fs, coef); + + return 0; +} diff --git a/audio/filter/filter.h b/audio/filter/filter.h new file mode 100644 index 0000000000..aed33352c2 --- /dev/null +++ b/audio/filter/filter.h @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2001 Anders Johansson ajh@atri.curtin.edu.au + * + * This file is part of MPlayer. + * + * MPlayer 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. + * + * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#if !defined MPLAYER_DSP_H +# error Never use filter.h directly; include dsp.h instead. +#endif + +#ifndef MPLAYER_FILTER_H +#define MPLAYER_FILTER_H + + +// Design and implementation of different types of digital filters + + +// Flags used for filter design + +// Filter characteristics +#define LP 0x00010000 // Low pass +#define HP 0x00020000 // High pass +#define BP 0x00040000 // Band pass +#define BS 0x00080000 // Band stop +#define TYPE_MASK 0x000F0000 + +// Window types +#define BOXCAR 0x00000001 +#define TRIANG 0x00000002 +#define HAMMING 0x00000004 +#define HANNING 0x00000008 +#define BLACKMAN 0x00000010 +#define FLATTOP 0x00000011 +#define KAISER 0x00000012 +#define WINDOW_MASK 0x0000001F + +// Parallel filter design +#define FWD 0x00000001 // Forward indexing of polyphase filter +#define REW 0x00000002 // Reverse indexing of polyphase filter +#define ODD 0x00000010 // Make filter HP + +// Exported functions +FLOAT_TYPE af_filter_fir(unsigned int n, const FLOAT_TYPE* w, const FLOAT_TYPE* x); + +int af_filter_design_fir(unsigned int n, FLOAT_TYPE* w, const FLOAT_TYPE* fc, + unsigned int flags, FLOAT_TYPE opt); + +int af_filter_szxform(const FLOAT_TYPE* a, const FLOAT_TYPE* b, FLOAT_TYPE Q, + FLOAT_TYPE fc, FLOAT_TYPE fs, FLOAT_TYPE *k, + FLOAT_TYPE *coef); + +/* Add new data to circular queue designed to be used with a FIR + filter. xq is the circular queue, in pointing at the new sample, xi + current index for xq and n the length of the filter. xq must be n*2 + long. +*/ +#define af_filter_updateq(n,xi,xq,in)\ + xq[xi]=(xq)[(xi)+(n)]=*(in);\ + xi=(++(xi))&((n)-1); + +#endif /* MPLAYER_FILTER_H */ diff --git a/audio/filter/window.c b/audio/filter/window.c new file mode 100644 index 0000000000..a970bdcbea --- /dev/null +++ b/audio/filter/window.c @@ -0,0 +1,213 @@ +/* + * Copyright (C) 2001 Anders Johansson ajh@atri.curtin.edu.au + * + * This file is part of MPlayer. + * + * MPlayer 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. + * + * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* Calculates a number of window functions. The following window + functions are currently implemented: Boxcar, Triang, Hanning, + Hamming, Blackman, Flattop and Kaiser. In the function call n is + the number of filter taps and w the buffer in which the filter + coefficients will be stored. +*/ + +#include <math.h> +#include "dsp.h" + +/* +// Boxcar +// +// n window length +// w buffer for the window parameters +*/ +void af_window_boxcar(int n, FLOAT_TYPE* w) +{ + int i; + // Calculate window coefficients + for (i=0 ; i<n ; i++) + w[i] = 1.0; +} + + +/* +// Triang a.k.a Bartlett +// +// | (N-1)| +// 2 * |k - -----| +// | 2 | +// w = 1.0 - --------------- +// N+1 +// n window length +// w buffer for the window parameters +*/ +void af_window_triang(int n, FLOAT_TYPE* w) +{ + FLOAT_TYPE k1 = (FLOAT_TYPE)(n & 1); + FLOAT_TYPE k2 = 1/((FLOAT_TYPE)n + k1); + int end = (n + 1) >> 1; + int i; + + // Calculate window coefficients + for (i=0 ; i<end ; i++) + w[i] = w[n-i-1] = (2.0*((FLOAT_TYPE)(i+1))-(1.0-k1))*k2; +} + + +/* +// Hanning +// 2*pi*k +// w = 0.5 - 0.5*cos(------), where 0 < k <= N +// N+1 +// n window length +// w buffer for the window parameters +*/ +void af_window_hanning(int n, FLOAT_TYPE* w) +{ + int i; + FLOAT_TYPE k = 2*M_PI/((FLOAT_TYPE)(n+1)); // 2*pi/(N+1) + + // Calculate window coefficients + for (i=0; i<n; i++) + *w++ = 0.5*(1.0 - cos(k*(FLOAT_TYPE)(i+1))); +} + +/* +// Hamming +// 2*pi*k +// w(k) = 0.54 - 0.46*cos(------), where 0 <= k < N +// N-1 +// +// n window length +// w buffer for the window parameters +*/ +void af_window_hamming(int n,FLOAT_TYPE* w) +{ + int i; + FLOAT_TYPE k = 2*M_PI/((FLOAT_TYPE)(n-1)); // 2*pi/(N-1) + + // Calculate window coefficients + for (i=0; i<n; i++) + *w++ = 0.54 - 0.46*cos(k*(FLOAT_TYPE)i); +} + +/* +// Blackman +// 2*pi*k 4*pi*k +// w(k) = 0.42 - 0.5*cos(------) + 0.08*cos(------), where 0 <= k < N +// N-1 N-1 +// +// n window length +// w buffer for the window parameters +*/ +void af_window_blackman(int n,FLOAT_TYPE* w) +{ + int i; + FLOAT_TYPE k1 = 2*M_PI/((FLOAT_TYPE)(n-1)); // 2*pi/(N-1) + FLOAT_TYPE k2 = 2*k1; // 4*pi/(N-1) + + // Calculate window coefficients + for (i=0; i<n; i++) + *w++ = 0.42 - 0.50*cos(k1*(FLOAT_TYPE)i) + 0.08*cos(k2*(FLOAT_TYPE)i); +} + +/* +// Flattop +// 2*pi*k 4*pi*k +// w(k) = 0.2810638602 - 0.5208971735*cos(------) + 0.1980389663*cos(------), where 0 <= k < N +// N-1 N-1 +// +// n window length +// w buffer for the window parameters +*/ +void af_window_flattop(int n,FLOAT_TYPE* w) +{ + int i; + FLOAT_TYPE k1 = 2*M_PI/((FLOAT_TYPE)(n-1)); // 2*pi/(N-1) + FLOAT_TYPE k2 = 2*k1; // 4*pi/(N-1) + + // Calculate window coefficients + for (i=0; i<n; i++) + *w++ = 0.2810638602 - 0.5208971735*cos(k1*(FLOAT_TYPE)i) + + 0.1980389663*cos(k2*(FLOAT_TYPE)i); +} + +/* Computes the 0th order modified Bessel function of the first kind. +// (Needed to compute Kaiser window) +// +// y = sum( (x/(2*n))^2 ) +// n +*/ +#define BIZ_EPSILON 1E-21 // Max error acceptable + +static FLOAT_TYPE besselizero(FLOAT_TYPE x) +{ + FLOAT_TYPE temp; + FLOAT_TYPE sum = 1.0; + FLOAT_TYPE u = 1.0; + FLOAT_TYPE halfx = x/2.0; + int n = 1; + + do { + temp = halfx/(FLOAT_TYPE)n; + u *=temp * temp; + sum += u; + n++; + } while (u >= BIZ_EPSILON * sum); + return sum; +} + +/* +// Kaiser +// +// n window length +// w buffer for the window parameters +// b beta parameter of Kaiser window, Beta >= 1 +// +// Beta trades the rejection of the low pass filter against the +// transition width from passband to stop band. Larger Beta means a +// slower transition and greater stop band rejection. See Rabiner and +// Gold (Theory and Application of DSP) under Kaiser windows for more +// about Beta. The following table from Rabiner and Gold gives some +// feel for the effect of Beta: +// +// All ripples in dB, width of transition band = D*N where N = window +// length +// +// BETA D PB RIP SB RIP +// 2.120 1.50 +-0.27 -30 +// 3.384 2.23 0.0864 -40 +// 4.538 2.93 0.0274 -50 +// 5.658 3.62 0.00868 -60 +// 6.764 4.32 0.00275 -70 +// 7.865 5.0 0.000868 -80 +// 8.960 5.7 0.000275 -90 +// 10.056 6.4 0.000087 -100 +*/ +void af_window_kaiser(int n, FLOAT_TYPE* w, FLOAT_TYPE b) +{ + FLOAT_TYPE tmp; + FLOAT_TYPE k1 = 1.0/besselizero(b); + int k2 = 1 - (n & 1); + int end = (n + 1) >> 1; + int i; + + // Calculate window coefficients + for (i=0 ; i<end ; i++){ + tmp = (FLOAT_TYPE)(2*i + k2) / ((FLOAT_TYPE)n - 1.0); + w[end-(1&(!k2))+i] = w[end-1-i] = k1 * besselizero(b*sqrt(1.0 - tmp*tmp)); + } +} diff --git a/audio/filter/window.h b/audio/filter/window.h new file mode 100644 index 0000000000..1c179b7902 --- /dev/null +++ b/audio/filter/window.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2001 Anders Johansson ajh@atri.curtin.edu.au + * + * This file is part of MPlayer. + * + * MPlayer 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. + * + * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* Calculates a number of window functions. The following window + functions are currently implemented: Boxcar, Triang, Hanning, + Hamming, Blackman, Flattop and Kaiser. In the function call n is + the number of filter taps and w the buffer in which the filter + coefficients will be stored. +*/ + +#if !defined MPLAYER_DSP_H +# error Never use window.h directly; include dsp.h instead. +#endif + +#ifndef MPLAYER_WINDOW_H +#define MPLAYER_WINDOW_H + +void af_window_boxcar(int n, FLOAT_TYPE* w); +void af_window_triang(int n, FLOAT_TYPE* w); +void af_window_hanning(int n, FLOAT_TYPE* w); +void af_window_hamming(int n, FLOAT_TYPE* w); +void af_window_blackman(int n, FLOAT_TYPE* w); +void af_window_flattop(int n, FLOAT_TYPE* w); +void af_window_kaiser(int n, FLOAT_TYPE* w, FLOAT_TYPE b); + +#endif /* MPLAYER_WINDOW_H */ diff --git a/audio/format.c b/audio/format.c new file mode 100644 index 0000000000..88d66522a0 --- /dev/null +++ b/audio/format.c @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2005 Alex Beregszaszi + * + * This file is part of MPlayer. + * + * MPlayer 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. + * + * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <inttypes.h> +#include <limits.h> + +#include "af.h" + +int af_fmt2bits(int format) +{ + if (AF_FORMAT_IS_AC3(format)) return 16; + return (format & AF_FORMAT_BITS_MASK)+8; +// return (((format & AF_FORMAT_BITS_MASK)>>3)+1) * 8; +#if 0 + switch(format & AF_FORMAT_BITS_MASK) + { + case AF_FORMAT_8BIT: return 8; + case AF_FORMAT_16BIT: return 16; + case AF_FORMAT_24BIT: return 24; + case AF_FORMAT_32BIT: return 32; + case AF_FORMAT_48BIT: return 48; + } +#endif + return -1; +} + +int af_bits2fmt(int bits) +{ + return (bits/8 - 1) << 3; +} + +/* Convert format to str input str is a buffer for the + converted string, size is the size of the buffer */ +char* af_fmt2str(int format, char* str, int size) +{ + const char *name = af_fmt2str_short(format); + if (name) { + snprintf(str, size, "%s", name); + } else { + snprintf(str, size, "%#x", format); + } + return str; +} + +const struct af_fmt_entry af_fmtstr_table[] = { + { "mulaw", AF_FORMAT_MU_LAW }, + { "alaw", AF_FORMAT_A_LAW }, + { "mpeg2", AF_FORMAT_MPEG2 }, + { "ac3le", AF_FORMAT_AC3_LE }, + { "ac3be", AF_FORMAT_AC3_BE }, + { "ac3ne", AF_FORMAT_AC3_NE }, + { "iec61937le", AF_FORMAT_IEC61937_LE }, + { "iec61937be", AF_FORMAT_IEC61937_BE }, + { "iec61937ne", AF_FORMAT_IEC61937_NE }, + { "imaadpcm", AF_FORMAT_IMA_ADPCM }, + + { "u8", AF_FORMAT_U8 }, + { "s8", AF_FORMAT_S8 }, + { "u16le", AF_FORMAT_U16_LE }, + { "u16be", AF_FORMAT_U16_BE }, + { "u16ne", AF_FORMAT_U16_NE }, + { "s16le", AF_FORMAT_S16_LE }, + { "s16be", AF_FORMAT_S16_BE }, + { "s16ne", AF_FORMAT_S16_NE }, + { "u24le", AF_FORMAT_U24_LE }, + { "u24be", AF_FORMAT_U24_BE }, + { "u24ne", AF_FORMAT_U24_NE }, + { "s24le", AF_FORMAT_S24_LE }, + { "s24be", AF_FORMAT_S24_BE }, + { "s24ne", AF_FORMAT_S24_NE }, + { "u32le", AF_FORMAT_U32_LE }, + { "u32be", AF_FORMAT_U32_BE }, + { "u32ne", AF_FORMAT_U32_NE }, + { "s32le", AF_FORMAT_S32_LE }, + { "s32be", AF_FORMAT_S32_BE }, + { "s32ne", AF_FORMAT_S32_NE }, + { "floatle", AF_FORMAT_FLOAT_LE }, + { "floatbe", AF_FORMAT_FLOAT_BE }, + { "floatne", AF_FORMAT_FLOAT_NE }, + + {0} +}; + +const char *af_fmt2str_short(int format) +{ + int i; + + for (i = 0; af_fmtstr_table[i].name; i++) + if (af_fmtstr_table[i].format == format) + return af_fmtstr_table[i].name; + + return "??"; +} + +static bool af_fmt_valid(int format) +{ + return (format & AF_FORMAT_MASK) == format; +} + +int af_str2fmt_short(bstr str) +{ + if (bstr_startswith0(str, "0x")) { + bstr rest; + int fmt = bstrtoll(str, &rest, 16); + if (rest.len == 0 && af_fmt_valid(fmt)) + return fmt; + } + + for (int i = 0; af_fmtstr_table[i].name; i++) + if (!bstrcasecmp0(str, af_fmtstr_table[i].name)) + return af_fmtstr_table[i].format; + + return -1; +} diff --git a/audio/format.h b/audio/format.h new file mode 100644 index 0000000000..e60c0789b9 --- /dev/null +++ b/audio/format.h @@ -0,0 +1,137 @@ +/* + * The sample format system used lin libaf is based on bitmasks. + * The format definition only refers to the storage format, + * not the resolution. + * + * This file is part of MPlayer. + * + * MPlayer 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. + * + * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPLAYER_AF_FORMAT_H +#define MPLAYER_AF_FORMAT_H + +#include <sys/types.h> +#include "config.h" +#include "bstr.h" + +// Endianness +#define AF_FORMAT_BE (0<<0) // Big Endian +#define AF_FORMAT_LE (1<<0) // Little Endian +#define AF_FORMAT_END_MASK (1<<0) + +#if BYTE_ORDER == BIG_ENDIAN +#define AF_FORMAT_NE AF_FORMAT_BE +#else +#define AF_FORMAT_NE AF_FORMAT_LE +#endif + +// Signed/unsigned +#define AF_FORMAT_SI (0<<1) // Signed +#define AF_FORMAT_US (1<<1) // Unsigned +#define AF_FORMAT_SIGN_MASK (1<<1) + +// Fixed or floating point +#define AF_FORMAT_I (0<<2) // Int +#define AF_FORMAT_F (1<<2) // Foating point +#define AF_FORMAT_POINT_MASK (1<<2) + +// Bits used +#define AF_FORMAT_8BIT (0<<3) +#define AF_FORMAT_16BIT (1<<3) +#define AF_FORMAT_24BIT (2<<3) +#define AF_FORMAT_32BIT (3<<3) +#define AF_FORMAT_40BIT (4<<3) +#define AF_FORMAT_48BIT (5<<3) +#define AF_FORMAT_BITS_MASK (7<<3) + +// Special flags refering to non pcm data +#define AF_FORMAT_MU_LAW (1<<6) +#define AF_FORMAT_A_LAW (2<<6) +#define AF_FORMAT_MPEG2 (3<<6) // MPEG(2) audio +#define AF_FORMAT_AC3 (4<<6) // Dolby Digital AC3 +#define AF_FORMAT_IMA_ADPCM (5<<6) +#define AF_FORMAT_IEC61937 (6<<6) +#define AF_FORMAT_SPECIAL_MASK (7<<6) + +#define AF_FORMAT_MASK ((1<<9)-1) + +// PREDEFINED formats + +#define AF_FORMAT_U8 (AF_FORMAT_I|AF_FORMAT_US|AF_FORMAT_8BIT|AF_FORMAT_NE) +#define AF_FORMAT_S8 (AF_FORMAT_I|AF_FORMAT_SI|AF_FORMAT_8BIT|AF_FORMAT_NE) +#define AF_FORMAT_U16_LE (AF_FORMAT_I|AF_FORMAT_US|AF_FORMAT_16BIT|AF_FORMAT_LE) +#define AF_FORMAT_U16_BE (AF_FORMAT_I|AF_FORMAT_US|AF_FORMAT_16BIT|AF_FORMAT_BE) +#define AF_FORMAT_S16_LE (AF_FORMAT_I|AF_FORMAT_SI|AF_FORMAT_16BIT|AF_FORMAT_LE) +#define AF_FORMAT_S16_BE (AF_FORMAT_I|AF_FORMAT_SI|AF_FORMAT_16BIT|AF_FORMAT_BE) +#define AF_FORMAT_U24_LE (AF_FORMAT_I|AF_FORMAT_US|AF_FORMAT_24BIT|AF_FORMAT_LE) +#define AF_FORMAT_U24_BE (AF_FORMAT_I|AF_FORMAT_US|AF_FORMAT_24BIT|AF_FORMAT_BE) +#define AF_FORMAT_S24_LE (AF_FORMAT_I|AF_FORMAT_SI|AF_FORMAT_24BIT|AF_FORMAT_LE) +#define AF_FORMAT_S24_BE (AF_FORMAT_I|AF_FORMAT_SI|AF_FORMAT_24BIT|AF_FORMAT_BE) +#define AF_FORMAT_U32_LE (AF_FORMAT_I|AF_FORMAT_US|AF_FORMAT_32BIT|AF_FORMAT_LE) +#define AF_FORMAT_U32_BE (AF_FORMAT_I|AF_FORMAT_US|AF_FORMAT_32BIT|AF_FORMAT_BE) +#define AF_FORMAT_S32_LE (AF_FORMAT_I|AF_FORMAT_SI|AF_FORMAT_32BIT|AF_FORMAT_LE) +#define AF_FORMAT_S32_BE (AF_FORMAT_I|AF_FORMAT_SI|AF_FORMAT_32BIT|AF_FORMAT_BE) + +#define AF_FORMAT_FLOAT_LE (AF_FORMAT_F|AF_FORMAT_32BIT|AF_FORMAT_LE) +#define AF_FORMAT_FLOAT_BE (AF_FORMAT_F|AF_FORMAT_32BIT|AF_FORMAT_BE) + +#define AF_FORMAT_AC3_LE (AF_FORMAT_AC3|AF_FORMAT_16BIT|AF_FORMAT_LE) +#define AF_FORMAT_AC3_BE (AF_FORMAT_AC3|AF_FORMAT_16BIT|AF_FORMAT_BE) + +#define AF_FORMAT_IEC61937_LE (AF_FORMAT_IEC61937|AF_FORMAT_16BIT|AF_FORMAT_LE) +#define AF_FORMAT_IEC61937_BE (AF_FORMAT_IEC61937|AF_FORMAT_16BIT|AF_FORMAT_BE) + +#if BYTE_ORDER == BIG_ENDIAN +#define AF_FORMAT_U16_NE AF_FORMAT_U16_BE +#define AF_FORMAT_S16_NE AF_FORMAT_S16_BE +#define AF_FORMAT_U24_NE AF_FORMAT_U24_BE +#define AF_FORMAT_S24_NE AF_FORMAT_S24_BE +#define AF_FORMAT_U32_NE AF_FORMAT_U32_BE +#define AF_FORMAT_S32_NE AF_FORMAT_S32_BE +#define AF_FORMAT_FLOAT_NE AF_FORMAT_FLOAT_BE +#define AF_FORMAT_AC3_NE AF_FORMAT_AC3_BE +#define AF_FORMAT_IEC61937_NE AF_FORMAT_IEC61937_BE +#else +#define AF_FORMAT_U16_NE AF_FORMAT_U16_LE +#define AF_FORMAT_S16_NE AF_FORMAT_S16_LE +#define AF_FORMAT_U24_NE AF_FORMAT_U24_LE +#define AF_FORMAT_S24_NE AF_FORMAT_S24_LE +#define AF_FORMAT_U32_NE AF_FORMAT_U32_LE +#define AF_FORMAT_S32_NE AF_FORMAT_S32_LE +#define AF_FORMAT_FLOAT_NE AF_FORMAT_FLOAT_LE +#define AF_FORMAT_AC3_NE AF_FORMAT_AC3_LE +#define AF_FORMAT_IEC61937_NE AF_FORMAT_IEC61937_LE +#endif + +#define AF_FORMAT_UNKNOWN (-1) + +#define AF_FORMAT_IS_AC3(fmt) (((fmt) & AF_FORMAT_SPECIAL_MASK) == AF_FORMAT_AC3) +#define AF_FORMAT_IS_IEC61937(fmt) (((fmt) & AF_FORMAT_SPECIAL_MASK) == AF_FORMAT_IEC61937) + +struct af_fmt_entry { + const char *name; + int format; +}; + +extern const struct af_fmt_entry af_fmtstr_table[]; + +int af_str2fmt_short(bstr str); +int af_fmt2bits(int format); +int af_bits2fmt(int bits); +char* af_fmt2str(int format, char* str, int size); +const char* af_fmt2str_short(int format); + +#endif /* MPLAYER_AF_FORMAT_H */ diff --git a/audio/mixer.c b/audio/mixer.c new file mode 100644 index 0000000000..2f9505a1ae --- /dev/null +++ b/audio/mixer.c @@ -0,0 +1,292 @@ +/* + * This file is part of MPlayer. + * + * MPlayer 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. + * + * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <string.h> + +#include <libavutil/common.h> + +#include "config.h" +#include "libao2/audio_out.h" +#include "libaf/af.h" +#include "mp_msg.h" +#include "mixer.h" + + +static void checkvolume(struct mixer *mixer) +{ + if (!mixer->ao) + return; + + if (mixer->softvol == SOFTVOL_AUTO) { + mixer->softvol = mixer->ao->per_application_mixer + ? SOFTVOL_NO : SOFTVOL_YES; + } + + ao_control_vol_t vol; + if (mixer->softvol || CONTROL_OK != ao_control(mixer->ao, + AOCONTROL_GET_VOLUME, &vol)) { + mixer->softvol = SOFTVOL_YES; + if (!mixer->afilter) + return; + float db_vals[AF_NCH]; + if (!af_control_any_rev(mixer->afilter, + AF_CONTROL_VOLUME_LEVEL | AF_CONTROL_GET, db_vals)) + db_vals[0] = db_vals[1] = 1.0; + else + af_from_dB(2, db_vals, db_vals, 20.0, -200.0, 60.0); + vol.left = (db_vals[0] / (mixer->softvol_max / 100.0)) * 100.0; + vol.right = (db_vals[1] / (mixer->softvol_max / 100.0)) * 100.0; + } + float l = mixer->vol_l; + float r = mixer->vol_r; + if (mixer->muted_using_volume) + l = r = 0; + /* Try to detect cases where the volume has been changed by some external + * action (such as something else changing a shared system-wide volume). + * We don't test for exact equality, as some AOs may round the value + * we last set to some nearby supported value. 3 has been the default + * volume step for increase/decrease keys, and is apparently big enough + * to step to the next possible value in most setups. + */ + if (FFABS(vol.left - l) >= 3 || FFABS(vol.right - r) >= 3) { + mixer->vol_l = vol.left; + mixer->vol_r = vol.right; + if (mixer->muted_using_volume) + mixer->muted = false; + } + if (!mixer->softvol) + // Rely on the value not changing if the query is not supported + ao_control(mixer->ao, AOCONTROL_GET_MUTE, &mixer->muted); + mixer->muted_by_us &= mixer->muted; + mixer->muted_using_volume &= mixer->muted; +} + +void mixer_getvolume(mixer_t *mixer, float *l, float *r) +{ + checkvolume(mixer); + *l = mixer->vol_l; + *r = mixer->vol_r; +} + +static void setvolume_internal(mixer_t *mixer, float l, float r) +{ + struct ao_control_vol vol = {.left = l, .right = r}; + if (!mixer->softvol) { + // relies on the driver data being permanent (so ptr stays valid) + mixer->restore_volume = mixer->ao->no_persistent_volume ? + mixer->ao->driver->info->short_name : NULL; + if (ao_control(mixer->ao, AOCONTROL_SET_VOLUME, &vol) != CONTROL_OK) + mp_tmsg(MSGT_GLOBAL, MSGL_ERR, + "[Mixer] Failed to change audio output volume.\n"); + return; + } + mixer->restore_volume = "softvol"; + if (!mixer->afilter) + return; + // af_volume uses values in dB + float db_vals[AF_NCH]; + int i; + db_vals[0] = (l / 100.0) * (mixer->softvol_max / 100.0); + db_vals[1] = (r / 100.0) * (mixer->softvol_max / 100.0); + for (i = 2; i < AF_NCH; i++) + db_vals[i] = ((l + r) / 100.0) * (mixer->softvol_max / 100.0) / 2.0; + af_to_dB(AF_NCH, db_vals, db_vals, 20.0); + if (!af_control_any_rev(mixer->afilter, + AF_CONTROL_VOLUME_LEVEL | AF_CONTROL_SET, + db_vals)) + { + mp_tmsg(MSGT_GLOBAL, mixer->softvol ? MSGL_V : MSGL_WARN, + "[Mixer] No hardware mixing, inserting volume filter.\n"); + if (!(af_add(mixer->afilter, "volume") + && af_control_any_rev(mixer->afilter, + AF_CONTROL_VOLUME_LEVEL | AF_CONTROL_SET, + db_vals))) + mp_tmsg(MSGT_GLOBAL, MSGL_ERR, + "[Mixer] No volume control available.\n"); + } +} + +void mixer_setvolume(mixer_t *mixer, float l, float r) +{ + checkvolume(mixer); // to check mute status and AO support for volume + mixer->vol_l = av_clip(l, 0, 100); + mixer->vol_r = av_clip(r, 0, 100); + if (!mixer->ao || mixer->muted) + return; + setvolume_internal(mixer, mixer->vol_l, mixer->vol_r); +} + +void mixer_getbothvolume(mixer_t *mixer, float *b) +{ + float mixer_l, mixer_r; + mixer_getvolume(mixer, &mixer_l, &mixer_r); + *b = (mixer_l + mixer_r) / 2; +} + +void mixer_setmute(struct mixer *mixer, bool mute) +{ + checkvolume(mixer); + if (mute != mixer->muted) { + if (!mixer->softvol && !mixer->muted_using_volume && ao_control( + mixer->ao, AOCONTROL_SET_MUTE, &mute) == CONTROL_OK) { + mixer->muted_using_volume = false; + } else { + setvolume_internal(mixer, mixer->vol_l*!mute, mixer->vol_r*!mute); + mixer->muted_using_volume = mute; + } + mixer->muted = mute; + mixer->muted_by_us = mute; + } +} + +bool mixer_getmute(struct mixer *mixer) +{ + checkvolume(mixer); + return mixer->muted; +} + +static void addvolume(struct mixer *mixer, float d) +{ + checkvolume(mixer); + mixer_setvolume(mixer, mixer->vol_l + d, mixer->vol_r + d); + if (d > 0) + mixer_setmute(mixer, false); +} + +void mixer_incvolume(mixer_t *mixer) +{ + addvolume(mixer, mixer->volstep); +} + +void mixer_decvolume(mixer_t *mixer) +{ + addvolume(mixer, -mixer->volstep); +} + +void mixer_getbalance(mixer_t *mixer, float *val) +{ + if (mixer->afilter) + af_control_any_rev(mixer->afilter, + AF_CONTROL_PAN_BALANCE | AF_CONTROL_GET, + &mixer->balance); + *val = mixer->balance; +} + +/* NOTE: Currently the balance code is seriously buggy: it always changes + * the af_pan mapping between the first two input channels and first two + * output channels to particular values. These values make sense for an + * af_pan instance that was automatically inserted for balance control + * only and is otherwise an identity transform, but if the filter was + * there for another reason, then ignoring and overriding the original + * values is completely wrong. In particular, this will break + * automatically inserted downmix filters; the original coefficients that + * are significantly below 1 will be overwritten with much higher values. + */ + +void mixer_setbalance(mixer_t *mixer, float val) +{ + float level[AF_NCH]; + int i; + af_control_ext_t arg_ext = { .arg = level }; + struct af_instance *af_pan_balance; + + mixer->balance = val; + + if (!mixer->afilter) + return; + + if (af_control_any_rev(mixer->afilter, + AF_CONTROL_PAN_BALANCE | AF_CONTROL_SET, &val)) + return; + + if (val == 0 || mixer->ao->channels < 2) + return; + + if (!(af_pan_balance = af_add(mixer->afilter, "pan"))) { + mp_tmsg(MSGT_GLOBAL, MSGL_ERR, + "[Mixer] No balance control available.\n"); + return; + } + + af_init(mixer->afilter); + /* make all other channels pass thru since by default pan blocks all */ + memset(level, 0, sizeof(level)); + for (i = 2; i < AF_NCH; i++) { + arg_ext.ch = i; + level[i] = 1.f; + af_pan_balance->control(af_pan_balance, + AF_CONTROL_PAN_LEVEL | AF_CONTROL_SET, + &arg_ext); + level[i] = 0.f; + } + + af_pan_balance->control(af_pan_balance, + AF_CONTROL_PAN_BALANCE | AF_CONTROL_SET, &val); +} + +// Called after the audio filter chain is built or rebuilt. +void mixer_reinit(struct mixer *mixer, struct ao *ao) +{ + mixer->ao = ao; + /* Use checkvolume() to see if softvol needs to be enabled because of + * lacking AO support, but first store values it could overwrite. */ + float left = mixer->vol_l, right = mixer->vol_r; + bool muted = mixer->muted_by_us; + checkvolume(mixer); + /* Try to avoid restoring volume stored from one control method with + * another. Especially, restoring softvol volume (typically high) on + * system mixer could have very nasty effects. */ + const char *restore_reason = mixer->softvol ? "softvol" : + mixer->ao->driver->info->short_name; + if (mixer->restore_volume && !strcmp(mixer->restore_volume, + restore_reason)) + mixer_setvolume(mixer, left, right); + /* We turn mute off at AO uninit, so it has to be restored (unless + * we're reinitializing filter chain while keeping AO); but we only + * enable mute, not turn external mute off. */ + if (muted) + mixer_setmute(mixer, true); + if (mixer->balance != 0) + mixer_setbalance(mixer, mixer->balance); +} + +/* Called before uninitializing the audio output. The main purpose is to + * turn off mute, in case it's a global/persistent setting which might + * otherwise be left enabled even after this player instance exits. + */ +void mixer_uninit(struct mixer *mixer) +{ + if (!mixer->ao) + return; + + checkvolume(mixer); + if (mixer->muted_by_us) { + /* Current audio output API combines playing the remaining buffered + * audio and uninitializing the AO into one operation, even though + * ideally unmute would happen between those two steps. We can't do + * volume changes after uninitialization, but we don't want the + * remaining audio to play at full volume either. Thus this + * workaround to drop remaining audio first. */ + ao_reset(mixer->ao); + mixer_setmute(mixer, false); + /* We remember mute status and re-enable it if we play more audio + * in the same process. */ + mixer->muted_by_us = true; + } + mixer->ao = NULL; +} diff --git a/audio/mixer.h b/audio/mixer.h new file mode 100644 index 0000000000..ba90d0881c --- /dev/null +++ b/audio/mixer.h @@ -0,0 +1,61 @@ +/* + * This file is part of MPlayer. + * + * MPlayer 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. + * + * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPLAYER_MIXER_H +#define MPLAYER_MIXER_H + +#include <stdbool.h> + +#include "libaf/af.h" +#include "libao2/audio_out.h" + +enum { + SOFTVOL_NO = 0, + SOFTVOL_YES = 1, + SOFTVOL_AUTO = 2, +}; + +typedef struct mixer { + struct ao *ao; + struct af_stream *afilter; + int volstep; + int softvol; + float softvol_max; + bool muted; + bool muted_by_us; + bool muted_using_volume; + float vol_l, vol_r; + /* Contains ao driver name or "softvol" if volume is not persistent + * and needs to be restored after the driver is reinitialized. */ + const char *restore_volume; + float balance; +} mixer_t; + +void mixer_reinit(struct mixer *mixer, struct ao *ao); +void mixer_uninit(struct mixer *mixer); +void mixer_getvolume(mixer_t *mixer, float *l, float *r); +void mixer_setvolume(mixer_t *mixer, float l, float r); +void mixer_incvolume(mixer_t *mixer); +void mixer_decvolume(mixer_t *mixer); +void mixer_getbothvolume(mixer_t *mixer, float *b); +void mixer_setmute(mixer_t *mixer, bool mute); +bool mixer_getmute(mixer_t *mixer); +void mixer_getbalance(mixer_t *mixer, float *bal); +void mixer_setbalance(mixer_t *mixer, float bal); + +#endif /* MPLAYER_MIXER_H */ diff --git a/audio/out/ao.c b/audio/out/ao.c new file mode 100644 index 0000000000..ab8e60b753 --- /dev/null +++ b/audio/out/ao.c @@ -0,0 +1,294 @@ +/* + * This file is part of MPlayer. + * + * MPlayer 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. + * + * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> + +#include "talloc.h" + +#include "config.h" +#include "audio_out.h" + +#include "mp_msg.h" + +// there are some globals: +struct ao *global_ao; +char *ao_subdevice = NULL; + +extern const struct ao_driver audio_out_oss; +extern const struct ao_driver audio_out_coreaudio; +extern const struct ao_driver audio_out_rsound; +extern const struct ao_driver audio_out_pulse; +extern const struct ao_driver audio_out_jack; +extern const struct ao_driver audio_out_openal; +extern const struct ao_driver audio_out_null; +extern const struct ao_driver audio_out_alsa; +extern const struct ao_driver audio_out_dsound; +extern const struct ao_driver audio_out_pcm; +extern const struct ao_driver audio_out_pss; +extern const struct ao_driver audio_out_lavc; +extern const struct ao_driver audio_out_portaudio; + +static const struct ao_driver * const audio_out_drivers[] = { +// native: +#ifdef CONFIG_COREAUDIO + &audio_out_coreaudio, +#endif +#ifdef CONFIG_PULSE + &audio_out_pulse, +#endif +#ifdef CONFIG_ALSA + &audio_out_alsa, +#endif +#ifdef CONFIG_OSS_AUDIO + &audio_out_oss, +#endif +#ifdef CONFIG_PORTAUDIO + &audio_out_portaudio, +#endif +#ifdef CONFIG_DSOUND + &audio_out_dsound, +#endif + // wrappers: +#ifdef CONFIG_JACK + &audio_out_jack, +#endif +#ifdef CONFIG_OPENAL + &audio_out_openal, +#endif + &audio_out_null, + // should not be auto-selected: + &audio_out_pcm, +#ifdef CONFIG_ENCODING + &audio_out_lavc, +#endif +#ifdef CONFIG_RSOUND + &audio_out_rsound, +#endif + NULL +}; + +void list_audio_out(void) +{ + int i=0; + mp_tmsg(MSGT_AO, MSGL_INFO, "Available audio output drivers:\n"); + mp_msg(MSGT_IDENTIFY, MSGL_INFO, "ID_AUDIO_OUTPUTS\n"); + while (audio_out_drivers[i]) { + const ao_info_t *info = audio_out_drivers[i++]->info; + mp_msg(MSGT_GLOBAL, MSGL_INFO, "\t%s\t%s\n", info->short_name, + info->name); + } + mp_msg(MSGT_GLOBAL, MSGL_INFO,"\n"); +} + +struct ao *ao_create(struct MPOpts *opts, struct input_ctx *input) +{ + struct ao *r = talloc(NULL, struct ao); + *r = (struct ao){.outburst = 512, .buffersize = -1, + .opts = opts, .input_ctx = input }; + return r; +} + +void ao_init(struct ao *ao, char **ao_list) +{ + /* Caller adding child blocks is not supported as we may call + * talloc_free_children() to clean up after failed open attempts. + */ + assert(talloc_total_blocks(ao) == 1); + struct ao backup = *ao; + + if (!ao_list) + goto try_defaults; + + // first try the preferred drivers, with their optional subdevice param: + while (*ao_list) { + char *ao_name = *ao_list; + if (!*ao_name) + goto try_defaults; // empty entry means try defaults + int ao_len; + char *params = strchr(ao_name, ':'); + if (params) { + ao_len = params - ao_name; + params++; + } else + ao_len = strlen(ao_name); + + mp_tmsg(MSGT_AO, MSGL_V, + "Trying preferred audio driver '%.*s', options '%s'\n", + ao_len, ao_name, params ? params : "[none]"); + + const struct ao_driver *audio_out = NULL; + for (int i = 0; audio_out_drivers[i]; i++) { + audio_out = audio_out_drivers[i]; + if (!strncmp(audio_out->info->short_name, ao_name, ao_len)) + break; + audio_out = NULL; + } + if (audio_out) { + // name matches, try it + ao->driver = audio_out; + if (audio_out->init(ao, params) >= 0) { + ao->driver = audio_out; + ao->initialized = true; + return; + } + mp_tmsg(MSGT_AO, MSGL_WARN, + "Failed to initialize audio driver '%s'\n", ao_name); + talloc_free_children(ao); + *ao = backup; + } else + mp_tmsg(MSGT_AO, MSGL_WARN, "No such audio driver '%.*s'\n", + ao_len, ao_name); + ++ao_list; + } + return; + + try_defaults: + mp_tmsg(MSGT_AO, MSGL_V, "Trying every known audio driver...\n"); + + // now try the rest... + for (int i = 0; audio_out_drivers[i]; i++) { + const struct ao_driver *audio_out = audio_out_drivers[i]; + ao->driver = audio_out; + ao->probing = true; + if (audio_out->init(ao, NULL) >= 0) { + ao->probing = false; + ao->initialized = true; + ao->driver = audio_out; + return; + } + talloc_free_children(ao); + *ao = backup; + } + return; +} + +void ao_uninit(struct ao *ao, bool cut_audio) +{ + assert(ao->buffer.len >= ao->buffer_playable_size); + ao->buffer.len = ao->buffer_playable_size; + if (ao->initialized) + ao->driver->uninit(ao, cut_audio); + if (!cut_audio && ao->buffer.len) + mp_msg(MSGT_AO, MSGL_WARN, "Audio output truncated at end.\n"); + talloc_free(ao); +} + +int ao_play(struct ao *ao, void *data, int len, int flags) +{ + return ao->driver->play(ao, data, len, flags); +} + +int ao_control(struct ao *ao, enum aocontrol cmd, void *arg) +{ + if (ao->driver->control) + return ao->driver->control(ao, cmd, arg); + return CONTROL_UNKNOWN; +} + +double ao_get_delay(struct ao *ao) +{ + if (!ao->driver->get_delay) { + assert(ao->untimed); + return 0; + } + return ao->driver->get_delay(ao); +} + +int ao_get_space(struct ao *ao) +{ + return ao->driver->get_space(ao); +} + +void ao_reset(struct ao *ao) +{ + ao->buffer.len = 0; + ao->buffer_playable_size = 0; + if (ao->driver->reset) + ao->driver->reset(ao); +} + +void ao_pause(struct ao *ao) +{ + if (ao->driver->pause) + ao->driver->pause(ao); +} + +void ao_resume(struct ao *ao) +{ + if (ao->driver->resume) + ao->driver->resume(ao); +} + + + +int old_ao_init(struct ao *ao, char *params) +{ + assert(!global_ao); + global_ao = ao; + ao_subdevice = params ? talloc_strdup(ao, params) : NULL; + if (ao->driver->old_functions->init(ao->samplerate, ao->channels, + ao->format, 0) == 0) { + global_ao = NULL; + return -1; + } + return 0; +} + +void old_ao_uninit(struct ao *ao, bool cut_audio) +{ + ao->driver->old_functions->uninit(cut_audio); + global_ao = NULL; +} + +int old_ao_play(struct ao *ao, void *data, int len, int flags) +{ + return ao->driver->old_functions->play(data, len, flags); +} + +int old_ao_control(struct ao *ao, enum aocontrol cmd, void *arg) +{ + return ao->driver->old_functions->control(cmd, arg); +} + +float old_ao_get_delay(struct ao *ao) +{ + return ao->driver->old_functions->get_delay(); +} + +int old_ao_get_space(struct ao *ao) +{ + return ao->driver->old_functions->get_space(); +} + +void old_ao_reset(struct ao *ao) +{ + ao->driver->old_functions->reset(); +} + +void old_ao_pause(struct ao *ao) +{ + ao->driver->old_functions->pause(); +} + +void old_ao_resume(struct ao *ao) +{ + ao->driver->old_functions->resume(); +} diff --git a/audio/out/ao.h b/audio/out/ao.h new file mode 100644 index 0000000000..9e172fd06c --- /dev/null +++ b/audio/out/ao.h @@ -0,0 +1,140 @@ +/* + * This file is part of MPlayer. + * + * MPlayer 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. + * + * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPLAYER_AUDIO_OUT_H +#define MPLAYER_AUDIO_OUT_H + +#include <stdbool.h> + +#include "bstr.h" + +#define CONTROL_OK 1 +#define CONTROL_TRUE 1 +#define CONTROL_FALSE 0 +#define CONTROL_UNKNOWN -1 +#define CONTROL_ERROR -2 +#define CONTROL_NA -3 + +enum aocontrol { + // _VOLUME commands take struct ao_control_vol pointer for input/output. + // If there's only one volume, SET should use average of left/right. + AOCONTROL_GET_VOLUME, + AOCONTROL_SET_VOLUME, + // _MUTE commands take a pointer to bool + AOCONTROL_GET_MUTE, + AOCONTROL_SET_MUTE, +}; + +#define AOPLAY_FINAL_CHUNK 1 + +typedef struct ao_control_vol { + float left; + float right; +} ao_control_vol_t; + +typedef struct ao_info { + /* driver name ("Matrox Millennium G200/G400" */ + const char *name; + /* short name (for config strings) ("mga") */ + const char *short_name; + /* author ("Aaron Holtzman <aholtzma@ess.engr.uvic.ca>") */ + const char *author; + /* any additional comments */ + const char *comment; +} ao_info_t; + +/* interface towards mplayer and */ +typedef struct ao_old_functions { + int (*control)(int cmd, void *arg); + int (*init)(int rate, int channels, int format, int flags); + void (*uninit)(int immed); + void (*reset)(void); + int (*get_space)(void); + int (*play)(void *data, int len, int flags); + float (*get_delay)(void); + void (*pause)(void); + void (*resume)(void); +} ao_functions_t; + +struct ao; + +struct ao_driver { + bool is_new; + const struct ao_info *info; + const struct ao_old_functions *old_functions; + int (*control)(struct ao *ao, enum aocontrol cmd, void *arg); + int (*init)(struct ao *ao, char *params); + void (*uninit)(struct ao *ao, bool cut_audio); + void (*reset)(struct ao*ao); + int (*get_space)(struct ao *ao); + int (*play)(struct ao *ao, void *data, int len, int flags); + float (*get_delay)(struct ao *ao); + void (*pause)(struct ao *ao); + void (*resume)(struct ao *ao); +}; + +/* global data used by mplayer and plugins */ +struct ao { + int samplerate; + int channels; + int format; + int bps; + int outburst; + int buffersize; + int brokenpts; + double pts; + struct bstr buffer; + int buffer_playable_size; + bool probing; + bool initialized; + bool untimed; + bool no_persistent_volume; + bool per_application_mixer; + const struct ao_driver *driver; + void *priv; + struct encode_lavc_context *encode_lavc_ctx; + struct MPOpts *opts; + struct input_ctx *input_ctx; +}; + +extern char *ao_subdevice; + +void list_audio_out(void); + +struct ao *ao_create(struct MPOpts *opts, struct input_ctx *input); +void ao_init(struct ao *ao, char **ao_list); +void ao_uninit(struct ao *ao, bool cut_audio); +int ao_play(struct ao *ao, void *data, int len, int flags); +int ao_control(struct ao *ao, enum aocontrol cmd, void *arg); +double ao_get_delay(struct ao *ao); +int ao_get_space(struct ao *ao); +void ao_reset(struct ao *ao); +void ao_pause(struct ao *ao); +void ao_resume(struct ao *ao); + +int old_ao_control(struct ao *ao, enum aocontrol cmd, void *arg); +int old_ao_init(struct ao *ao, char *params); +void old_ao_uninit(struct ao *ao, bool cut_audio); +void old_ao_reset(struct ao*ao); +int old_ao_get_space(struct ao *ao); +int old_ao_play(struct ao *ao, void *data, int len, int flags); +float old_ao_get_delay(struct ao *ao); +void old_ao_pause(struct ao *ao); +void old_ao_resume(struct ao *ao); + +#endif /* MPLAYER_AUDIO_OUT_H */ diff --git a/audio/out/ao_alsa.c b/audio/out/ao_alsa.c new file mode 100644 index 0000000000..27119112cb --- /dev/null +++ b/audio/out/ao_alsa.c @@ -0,0 +1,868 @@ +/* + * ALSA 0.9.x-1.x audio output driver + * + * Copyright (C) 2004 Alex Beregszaszi + * + * modified for real ALSA 0.9.0 support by Zsolt Barat <joy@streamminister.de> + * additional AC-3 passthrough support by Andy Lo A Foe <andy@alsaplayer.org> + * 08/22/2002 iec958-init rewritten and merged with common init, zsolt + * 04/13/2004 merged with ao_alsa1.x, fixes provided by Jindrich Makovicka + * 04/25/2004 printfs converted to mp_msg, Zsolt. + * + * This file is part of MPlayer. + * + * MPlayer 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. + * + * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <errno.h> +#include <sys/time.h> +#include <stdlib.h> +#include <stdarg.h> +#include <ctype.h> +#include <math.h> +#include <string.h> +#include <alloca.h> + +#include "config.h" +#include "subopt-helper.h" +#include "mixer.h" +#include "mp_msg.h" + +#define ALSA_PCM_NEW_HW_PARAMS_API +#define ALSA_PCM_NEW_SW_PARAMS_API + +#include <alsa/asoundlib.h> + +#include "audio_out.h" +#include "audio_out_internal.h" +#include "libaf/format.h" + +static const ao_info_t info = +{ + "ALSA-0.9.x-1.x audio output", + "alsa", + "Alex Beregszaszi, Zsolt Barat <joy@streamminister.de>", + "under development" +}; + +LIBAO_EXTERN(alsa) + +static snd_pcm_t *alsa_handler; +static snd_pcm_format_t alsa_format; + +#define BUFFER_TIME 500000 // 0.5 s +#define FRAGCOUNT 16 + +static size_t bytes_per_sample; + +static int alsa_can_pause; +static snd_pcm_sframes_t prepause_frames; + +#define ALSA_DEVICE_SIZE 256 + +static void alsa_error_handler(const char *file, int line, const char *function, + int err, const char *format, ...) +{ + char tmp[0xc00]; + va_list va; + + va_start(va, format); + vsnprintf(tmp, sizeof tmp, format, va); + va_end(va); + + if (err) + mp_msg(MSGT_AO, MSGL_ERR, "[AO_ALSA] alsa-lib: %s:%i:(%s) %s: %s\n", + file, line, function, tmp, snd_strerror(err)); + else + mp_msg(MSGT_AO, MSGL_ERR, "[AO_ALSA] alsa-lib: %s:%i:(%s) %s\n", + file, line, function, tmp); +} + +/* to set/get/query special features/parameters */ +static int control(int cmd, void *arg) +{ + switch(cmd) { + case AOCONTROL_GET_MUTE: + case AOCONTROL_SET_MUTE: + case AOCONTROL_GET_VOLUME: + case AOCONTROL_SET_VOLUME: + { + int err; + snd_mixer_t *handle; + snd_mixer_elem_t *elem; + snd_mixer_selem_id_t *sid; + + char *mix_name = "Master"; + char *card = "default"; + int mix_index = 0; + + long pmin, pmax; + long get_vol, set_vol; + float f_multi; + + if(AF_FORMAT_IS_AC3(ao_data.format) || AF_FORMAT_IS_IEC61937(ao_data.format)) + return CONTROL_TRUE; + + if(mixer_channel) { + char *test_mix_index; + + mix_name = strdup(mixer_channel); + if ((test_mix_index = strchr(mix_name, ','))){ + *test_mix_index = 0; + test_mix_index++; + mix_index = strtol(test_mix_index, &test_mix_index, 0); + + if (*test_mix_index){ + mp_tmsg(MSGT_AO,MSGL_ERR, + "[AO_ALSA] Invalid mixer index. Defaulting to 0.\n"); + mix_index = 0 ; + } + } + } + if(mixer_device) card = mixer_device; + + //allocate simple id + snd_mixer_selem_id_alloca(&sid); + + //sets simple-mixer index and name + snd_mixer_selem_id_set_index(sid, mix_index); + snd_mixer_selem_id_set_name(sid, mix_name); + + if (mixer_channel) { + free(mix_name); + mix_name = NULL; + } + + if ((err = snd_mixer_open(&handle, 0)) < 0) { + mp_tmsg(MSGT_AO,MSGL_ERR,"[AO_ALSA] Mixer open error: %s\n", snd_strerror(err)); + return CONTROL_ERROR; + } + + if ((err = snd_mixer_attach(handle, card)) < 0) { + mp_tmsg(MSGT_AO,MSGL_ERR,"[AO_ALSA] Mixer attach %s error: %s\n", + card, snd_strerror(err)); + snd_mixer_close(handle); + return CONTROL_ERROR; + } + + if ((err = snd_mixer_selem_register(handle, NULL, NULL)) < 0) { + mp_tmsg(MSGT_AO,MSGL_ERR,"[AO_ALSA] Mixer register error: %s\n", snd_strerror(err)); + snd_mixer_close(handle); + return CONTROL_ERROR; + } + err = snd_mixer_load(handle); + if (err < 0) { + mp_tmsg(MSGT_AO,MSGL_ERR,"[AO_ALSA] Mixer load error: %s\n", snd_strerror(err)); + snd_mixer_close(handle); + return CONTROL_ERROR; + } + + elem = snd_mixer_find_selem(handle, sid); + if (!elem) { + mp_tmsg(MSGT_AO,MSGL_ERR,"[AO_ALSA] Unable to find simple control '%s',%i.\n", + snd_mixer_selem_id_get_name(sid), snd_mixer_selem_id_get_index(sid)); + snd_mixer_close(handle); + return CONTROL_ERROR; + } + + snd_mixer_selem_get_playback_volume_range(elem,&pmin,&pmax); + f_multi = (100 / (float)(pmax - pmin)); + + switch (cmd) { + case AOCONTROL_SET_VOLUME: { + ao_control_vol_t *vol = arg; + set_vol = vol->left / f_multi + pmin + 0.5; + + //setting channels + if ((err = snd_mixer_selem_set_playback_volume(elem, SND_MIXER_SCHN_FRONT_LEFT, set_vol)) < 0) { + mp_tmsg(MSGT_AO,MSGL_ERR,"[AO_ALSA] Error setting left channel, %s\n", + snd_strerror(err)); + goto mixer_error; + } + mp_msg(MSGT_AO,MSGL_DBG2,"left=%li, ", set_vol); + + set_vol = vol->right / f_multi + pmin + 0.5; + + if ((err = snd_mixer_selem_set_playback_volume(elem, SND_MIXER_SCHN_FRONT_RIGHT, set_vol)) < 0) { + mp_tmsg(MSGT_AO,MSGL_ERR,"[AO_ALSA] Error setting right channel, %s\n", + snd_strerror(err)); + goto mixer_error; + } + mp_msg(MSGT_AO,MSGL_DBG2,"right=%li, pmin=%li, pmax=%li, mult=%f\n", + set_vol, pmin, pmax, f_multi); + break; + } + case AOCONTROL_GET_VOLUME: { + ao_control_vol_t *vol = arg; + snd_mixer_selem_get_playback_volume(elem, SND_MIXER_SCHN_FRONT_LEFT, &get_vol); + vol->left = (get_vol - pmin) * f_multi; + snd_mixer_selem_get_playback_volume(elem, SND_MIXER_SCHN_FRONT_RIGHT, &get_vol); + vol->right = (get_vol - pmin) * f_multi; + mp_msg(MSGT_AO,MSGL_DBG2,"left=%f, right=%f\n",vol->left,vol->right); + break; + } + case AOCONTROL_SET_MUTE: { + bool *mute = arg; + if (!snd_mixer_selem_has_playback_switch(elem)) + goto mixer_error; + if (!snd_mixer_selem_has_playback_switch_joined(elem)) { + snd_mixer_selem_set_playback_switch( + elem, SND_MIXER_SCHN_FRONT_RIGHT, !*mute); + } + snd_mixer_selem_set_playback_switch(elem, SND_MIXER_SCHN_FRONT_LEFT, + !*mute); + break; + } + case AOCONTROL_GET_MUTE: { + bool *mute = arg; + if (!snd_mixer_selem_has_playback_switch(elem)) + goto mixer_error; + int tmp = 1; + snd_mixer_selem_get_playback_switch(elem, SND_MIXER_SCHN_FRONT_LEFT, + &tmp); + *mute = !tmp; + if (!snd_mixer_selem_has_playback_switch_joined(elem)) { + snd_mixer_selem_get_playback_switch( + elem, SND_MIXER_SCHN_FRONT_RIGHT, &tmp); + *mute &= !tmp; + } + break; + } + } + snd_mixer_close(handle); + return CONTROL_OK; + mixer_error: + snd_mixer_close(handle); + return CONTROL_ERROR; + } + + } //end switch + return CONTROL_UNKNOWN; +} + +static void parse_device (char *dest, const char *src, int len) +{ + char *tmp; + memmove(dest, src, len); + dest[len] = 0; + while ((tmp = strrchr(dest, '.'))) + tmp[0] = ','; + while ((tmp = strrchr(dest, '='))) + tmp[0] = ':'; +} + +static void print_help (void) +{ + mp_tmsg (MSGT_AO, MSGL_FATAL, + "\n[AO_ALSA] -ao alsa commandline help:\n"\ + "[AO_ALSA] Example: mpv -ao alsa:device=hw=0.3\n"\ + "[AO_ALSA] Sets first card fourth hardware device.\n\n"\ + "[AO_ALSA] Options:\n"\ + "[AO_ALSA] noblock\n"\ + "[AO_ALSA] Opens device in non-blocking mode.\n"\ + "[AO_ALSA] device=<device-name>\n"\ + "[AO_ALSA] Sets device (change , to . and : to =)\n"); +} + +static int str_maxlen(void *strp) { + strarg_t *str = strp; + return str->len <= ALSA_DEVICE_SIZE; +} + +static int try_open_device(const char *device, int open_mode, int try_ac3) +{ + int err, len; + char *ac3_device, *args; + + if (try_ac3) { + /* to set the non-audio bit, use AES0=6 */ + len = strlen(device); + ac3_device = malloc(len + 7 + 1); + if (!ac3_device) + return -ENOMEM; + strcpy(ac3_device, device); + args = strchr(ac3_device, ':'); + if (!args) { + /* no existing parameters: add it behind device name */ + strcat(ac3_device, ":AES0=6"); + } else { + do + ++args; + while (isspace(*args)); + if (*args == '\0') { + /* ":" but no parameters */ + strcat(ac3_device, "AES0=6"); + } else if (*args != '{') { + /* a simple list of parameters: add it at the end of the list */ + strcat(ac3_device, ",AES0=6"); + } else { + /* parameters in config syntax: add it inside the { } block */ + do + --len; + while (len > 0 && isspace(ac3_device[len])); + if (ac3_device[len] == '}') + strcpy(ac3_device + len, " AES0=6}"); + } + } + err = snd_pcm_open(&alsa_handler, ac3_device, SND_PCM_STREAM_PLAYBACK, + open_mode); + free(ac3_device); + if (!err) + return 0; + } + return snd_pcm_open(&alsa_handler, device, SND_PCM_STREAM_PLAYBACK, + open_mode); +} + +/* + open & setup audio device + return: 1=success 0=fail +*/ +static int init(int rate_hz, int channels, int format, int flags) +{ + int err; + int block; + strarg_t device; + snd_pcm_uframes_t chunk_size; + snd_pcm_uframes_t bufsize; + snd_pcm_uframes_t boundary; + const opt_t subopts[] = { + {"block", OPT_ARG_BOOL, &block, NULL}, + {"device", OPT_ARG_STR, &device, str_maxlen}, + {NULL} + }; + + char alsa_device[ALSA_DEVICE_SIZE + 1]; + // make sure alsa_device is null-terminated even when using strncpy etc. + memset(alsa_device, 0, ALSA_DEVICE_SIZE + 1); + + mp_msg(MSGT_AO,MSGL_V,"alsa-init: requested format: %d Hz, %d channels, %x\n", rate_hz, + channels, format); + alsa_handler = NULL; + mp_msg(MSGT_AO,MSGL_V,"alsa-init: using ALSA %s\n", snd_asoundlib_version()); + + prepause_frames = 0; + + snd_lib_error_set_handler(alsa_error_handler); + + ao_data.samplerate = rate_hz; + ao_data.format = format; + ao_data.channels = channels; + + switch (format) + { + case AF_FORMAT_S8: + alsa_format = SND_PCM_FORMAT_S8; + break; + case AF_FORMAT_U8: + alsa_format = SND_PCM_FORMAT_U8; + break; + case AF_FORMAT_U16_LE: + alsa_format = SND_PCM_FORMAT_U16_LE; + break; + case AF_FORMAT_U16_BE: + alsa_format = SND_PCM_FORMAT_U16_BE; + break; + case AF_FORMAT_AC3_LE: + case AF_FORMAT_S16_LE: + case AF_FORMAT_IEC61937_LE: + alsa_format = SND_PCM_FORMAT_S16_LE; + break; + case AF_FORMAT_AC3_BE: + case AF_FORMAT_S16_BE: + case AF_FORMAT_IEC61937_BE: + alsa_format = SND_PCM_FORMAT_S16_BE; + break; + case AF_FORMAT_U32_LE: + alsa_format = SND_PCM_FORMAT_U32_LE; + break; + case AF_FORMAT_U32_BE: + alsa_format = SND_PCM_FORMAT_U32_BE; + break; + case AF_FORMAT_S32_LE: + alsa_format = SND_PCM_FORMAT_S32_LE; + break; + case AF_FORMAT_S32_BE: + alsa_format = SND_PCM_FORMAT_S32_BE; + break; + case AF_FORMAT_U24_LE: + alsa_format = SND_PCM_FORMAT_U24_3LE; + break; + case AF_FORMAT_U24_BE: + alsa_format = SND_PCM_FORMAT_U24_3BE; + break; + case AF_FORMAT_S24_LE: + alsa_format = SND_PCM_FORMAT_S24_3LE; + break; + case AF_FORMAT_S24_BE: + alsa_format = SND_PCM_FORMAT_S24_3BE; + break; + case AF_FORMAT_FLOAT_LE: + alsa_format = SND_PCM_FORMAT_FLOAT_LE; + break; + case AF_FORMAT_FLOAT_BE: + alsa_format = SND_PCM_FORMAT_FLOAT_BE; + break; + case AF_FORMAT_MU_LAW: + alsa_format = SND_PCM_FORMAT_MU_LAW; + break; + case AF_FORMAT_A_LAW: + alsa_format = SND_PCM_FORMAT_A_LAW; + break; + + default: + alsa_format = SND_PCM_FORMAT_MPEG; //? default should be -1 + break; + } + + //subdevice parsing + // set defaults + block = 1; + /* switch for spdif + * sets opening sequence for SPDIF + * sets also the playback and other switches 'on the fly' + * while opening the abstract alias for the spdif subdevice + * 'iec958' + */ + if (AF_FORMAT_IS_AC3(format) || AF_FORMAT_IS_IEC61937(format)) { + device.str = "iec958"; + mp_msg(MSGT_AO,MSGL_V,"alsa-spdif-init: playing AC3/iec61937/iec958, %i channels\n", channels); + } + else + /* in any case for multichannel playback we should select + * appropriate device + */ + switch (channels) { + case 1: + case 2: + device.str = "default"; + mp_msg(MSGT_AO,MSGL_V,"alsa-init: setup for 1/2 channel(s)\n"); + break; + case 4: + if (alsa_format == SND_PCM_FORMAT_FLOAT_LE) + // hack - use the converter plugin + device.str = "plug:surround40"; + else + device.str = "surround40"; + mp_msg(MSGT_AO,MSGL_V,"alsa-init: device set to surround40\n"); + break; + case 6: + if (alsa_format == SND_PCM_FORMAT_FLOAT_LE) + device.str = "plug:surround51"; + else + device.str = "surround51"; + mp_msg(MSGT_AO,MSGL_V,"alsa-init: device set to surround51\n"); + break; + case 8: + if (alsa_format == SND_PCM_FORMAT_FLOAT_LE) + device.str = "plug:surround71"; + else + device.str = "surround71"; + mp_msg(MSGT_AO,MSGL_V,"alsa-init: device set to surround71\n"); + break; + default: + device.str = "default"; + mp_tmsg(MSGT_AO,MSGL_ERR,"[AO_ALSA] %d channels are not supported.\n",channels); + } + device.len = strlen(device.str); + if (subopt_parse(ao_subdevice, subopts) != 0) { + print_help(); + return 0; + } + parse_device(alsa_device, device.str, device.len); + + mp_msg(MSGT_AO,MSGL_V,"alsa-init: using device %s\n", alsa_device); + + alsa_can_pause = 1; + + if (!alsa_handler) { + int open_mode = block ? 0 : SND_PCM_NONBLOCK; + int isac3 = AF_FORMAT_IS_AC3(format) || AF_FORMAT_IS_IEC61937(format); + //modes = 0, SND_PCM_NONBLOCK, SND_PCM_ASYNC + if ((err = try_open_device(alsa_device, open_mode, isac3)) < 0) + { + if (err != -EBUSY && !block) { + mp_tmsg(MSGT_AO,MSGL_INFO,"[AO_ALSA] Open in nonblock-mode failed, trying to open in block-mode.\n"); + if ((err = try_open_device(alsa_device, 0, isac3)) < 0) { + mp_tmsg(MSGT_AO,MSGL_ERR,"[AO_ALSA] Playback open error: %s\n", snd_strerror(err)); + return 0; + } + } else { + mp_tmsg(MSGT_AO,MSGL_ERR,"[AO_ALSA] Playback open error: %s\n", snd_strerror(err)); + return 0; + } + } + + if ((err = snd_pcm_nonblock(alsa_handler, 0)) < 0) { + mp_tmsg(MSGT_AO,MSGL_ERR,"[AL_ALSA] Error setting block-mode %s.\n", snd_strerror(err)); + } else { + mp_msg(MSGT_AO,MSGL_V,"alsa-init: pcm opened in blocking mode\n"); + } + + snd_pcm_hw_params_t *alsa_hwparams; + snd_pcm_sw_params_t *alsa_swparams; + + snd_pcm_hw_params_alloca(&alsa_hwparams); + snd_pcm_sw_params_alloca(&alsa_swparams); + + // setting hw-parameters + if ((err = snd_pcm_hw_params_any(alsa_handler, alsa_hwparams)) < 0) + { + mp_tmsg(MSGT_AO,MSGL_ERR,"[AO_ALSA] Unable to get initial parameters: %s\n", + snd_strerror(err)); + return 0; + } + + err = snd_pcm_hw_params_set_access(alsa_handler, alsa_hwparams, + SND_PCM_ACCESS_RW_INTERLEAVED); + if (err < 0) { + mp_tmsg(MSGT_AO,MSGL_ERR,"[AO_ALSA] Unable to set access type: %s\n", + snd_strerror(err)); + return 0; + } + + /* workaround for nonsupported formats + sets default format to S16_LE if the given formats aren't supported */ + if ((err = snd_pcm_hw_params_test_format(alsa_handler, alsa_hwparams, + alsa_format)) < 0) + { + mp_tmsg(MSGT_AO,MSGL_INFO, + "[AO_ALSA] Format %s is not supported by hardware, trying default.\n", af_fmt2str_short(format)); + alsa_format = SND_PCM_FORMAT_S16_LE; + if (AF_FORMAT_IS_AC3(ao_data.format)) + ao_data.format = AF_FORMAT_AC3_LE; + else if (AF_FORMAT_IS_IEC61937(ao_data.format)) + ao_data.format = AF_FORMAT_IEC61937_LE; + else + ao_data.format = AF_FORMAT_S16_LE; + } + + if ((err = snd_pcm_hw_params_set_format(alsa_handler, alsa_hwparams, + alsa_format)) < 0) + { + mp_tmsg(MSGT_AO,MSGL_ERR,"[AO_ALSA] Unable to set format: %s\n", + snd_strerror(err)); + return 0; + } + + if ((err = snd_pcm_hw_params_set_channels_near(alsa_handler, alsa_hwparams, + &ao_data.channels)) < 0) + { + mp_tmsg(MSGT_AO,MSGL_ERR,"[AO_ALSA] Unable to set channels: %s\n", + snd_strerror(err)); + return 0; + } + + /* workaround for buggy rate plugin (should be fixed in ALSA 1.0.11) + prefer our own resampler, since that allows users to choose the resampler, + even per file if desired */ + if ((err = snd_pcm_hw_params_set_rate_resample(alsa_handler, alsa_hwparams, + 0)) < 0) + { + mp_tmsg(MSGT_AO,MSGL_ERR,"[AO_ALSA] Unable to disable resampling: %s\n", + snd_strerror(err)); + return 0; + } + + if ((err = snd_pcm_hw_params_set_rate_near(alsa_handler, alsa_hwparams, + &ao_data.samplerate, NULL)) < 0) + { + mp_tmsg(MSGT_AO,MSGL_ERR,"[AO_ALSA] Unable to set samplerate-2: %s\n", + snd_strerror(err)); + return 0; + } + + bytes_per_sample = af_fmt2bits(ao_data.format) / 8; + bytes_per_sample *= ao_data.channels; + ao_data.bps = ao_data.samplerate * bytes_per_sample; + + if ((err = snd_pcm_hw_params_set_buffer_time_near(alsa_handler, alsa_hwparams, + &(unsigned int){BUFFER_TIME}, NULL)) < 0) + { + mp_tmsg(MSGT_AO,MSGL_ERR,"[AO_ALSA] Unable to set buffer time near: %s\n", + snd_strerror(err)); + return 0; + } + + if ((err = snd_pcm_hw_params_set_periods_near(alsa_handler, alsa_hwparams, + &(unsigned int){FRAGCOUNT}, NULL)) < 0) { + mp_tmsg(MSGT_AO,MSGL_ERR,"[AO_ALSA] Unable to set periods: %s\n", + snd_strerror(err)); + return 0; + } + + /* finally install hardware parameters */ + if ((err = snd_pcm_hw_params(alsa_handler, alsa_hwparams)) < 0) + { + mp_tmsg(MSGT_AO,MSGL_ERR,"[AO_ALSA] Unable to set hw-parameters: %s\n", + snd_strerror(err)); + return 0; + } + // end setting hw-params + + + // gets buffersize for control + if ((err = snd_pcm_hw_params_get_buffer_size(alsa_hwparams, &bufsize)) < 0) + { + mp_tmsg(MSGT_AO,MSGL_ERR,"[AO_ALSA] Unable to get buffersize: %s\n", snd_strerror(err)); + return 0; + } + else { + ao_data.buffersize = bufsize * bytes_per_sample; + mp_msg(MSGT_AO,MSGL_V,"alsa-init: got buffersize=%i\n", ao_data.buffersize); + } + + if ((err = snd_pcm_hw_params_get_period_size(alsa_hwparams, &chunk_size, NULL)) < 0) { + mp_tmsg(MSGT_AO,MSGL_ERR,"[AO ALSA] Unable to get period size: %s\n", snd_strerror(err)); + return 0; + } else { + mp_msg(MSGT_AO,MSGL_V,"alsa-init: got period size %li\n", chunk_size); + } + ao_data.outburst = chunk_size * bytes_per_sample; + + /* setting software parameters */ + if ((err = snd_pcm_sw_params_current(alsa_handler, alsa_swparams)) < 0) { + mp_tmsg(MSGT_AO,MSGL_ERR,"[AO_ALSA] Unable to get sw-parameters: %s\n", + snd_strerror(err)); + return 0; + } + if ((err = snd_pcm_sw_params_get_boundary(alsa_swparams, &boundary)) < 0) { + mp_tmsg(MSGT_AO,MSGL_ERR,"[AO_ALSA] Unable to get boundary: %s\n", + snd_strerror(err)); + return 0; + } + /* start playing when one period has been written */ + if ((err = snd_pcm_sw_params_set_start_threshold(alsa_handler, alsa_swparams, chunk_size)) < 0) { + mp_tmsg(MSGT_AO,MSGL_ERR,"[AO_ALSA] Unable to set start threshold: %s\n", + snd_strerror(err)); + return 0; + } + /* disable underrun reporting */ + if ((err = snd_pcm_sw_params_set_stop_threshold(alsa_handler, alsa_swparams, boundary)) < 0) { + mp_tmsg(MSGT_AO,MSGL_ERR,"[AO_ALSA] Unable to set stop threshold: %s\n", + snd_strerror(err)); + return 0; + } + /* play silence when there is an underrun */ + if ((err = snd_pcm_sw_params_set_silence_size(alsa_handler, alsa_swparams, boundary)) < 0) { + mp_tmsg(MSGT_AO,MSGL_ERR,"[AO_ALSA] Unable to set silence size: %s\n", + snd_strerror(err)); + return 0; + } + if ((err = snd_pcm_sw_params(alsa_handler, alsa_swparams)) < 0) { + mp_tmsg(MSGT_AO,MSGL_ERR,"[AO_ALSA] Unable to get sw-parameters: %s\n", + snd_strerror(err)); + return 0; + } + /* end setting sw-params */ + + alsa_can_pause = snd_pcm_hw_params_can_pause(alsa_hwparams); + + mp_msg(MSGT_AO,MSGL_V,"alsa: %d Hz/%d channels/%d bpf/%d bytes buffer/%s\n", + ao_data.samplerate, ao_data.channels, (int)bytes_per_sample, ao_data.buffersize, + snd_pcm_format_description(alsa_format)); + + } // end switch alsa_handler (spdif) + return 1; +} // end init + + +/* close audio device */ +static void uninit(int immed) +{ + + if (alsa_handler) { + int err; + + if (!immed) + snd_pcm_drain(alsa_handler); + + if ((err = snd_pcm_close(alsa_handler)) < 0) + { + mp_tmsg(MSGT_AO,MSGL_ERR,"[AO_ALSA] pcm close error: %s\n", snd_strerror(err)); + return; + } + else { + alsa_handler = NULL; + mp_msg(MSGT_AO,MSGL_V,"alsa-uninit: pcm closed\n"); + } + } + else { + mp_tmsg(MSGT_AO,MSGL_ERR,"[AO_ALSA] No handler defined!\n"); + } +} + +static void audio_pause(void) +{ + int err; + + if (alsa_can_pause) { + if ((err = snd_pcm_pause(alsa_handler, 1)) < 0) + { + mp_tmsg(MSGT_AO,MSGL_ERR,"[AO_ALSA] pcm pause error: %s\n", snd_strerror(err)); + return; + } + mp_msg(MSGT_AO,MSGL_V,"alsa-pause: pause supported by hardware\n"); + } else { + if (snd_pcm_delay(alsa_handler, &prepause_frames) < 0 + || prepause_frames < 0) + prepause_frames = 0; + + if ((err = snd_pcm_drop(alsa_handler)) < 0) + { + mp_tmsg(MSGT_AO,MSGL_ERR,"[AO_ALSA] pcm drop error: %s\n", snd_strerror(err)); + return; + } + } +} + +static void audio_resume(void) +{ + int err; + + if (snd_pcm_state(alsa_handler) == SND_PCM_STATE_SUSPENDED) { + mp_tmsg(MSGT_AO,MSGL_INFO,"[AO_ALSA] Pcm in suspend mode, trying to resume.\n"); + while ((err = snd_pcm_resume(alsa_handler)) == -EAGAIN) sleep(1); + } + if (alsa_can_pause) { + if ((err = snd_pcm_pause(alsa_handler, 0)) < 0) + { + mp_tmsg(MSGT_AO,MSGL_ERR,"[AO_ALSA] pcm resume error: %s\n", snd_strerror(err)); + return; + } + mp_msg(MSGT_AO,MSGL_V,"alsa-resume: resume supported by hardware\n"); + } else { + if ((err = snd_pcm_prepare(alsa_handler)) < 0) + { + mp_tmsg(MSGT_AO,MSGL_ERR,"[AO_ALSA] pcm prepare error: %s\n", snd_strerror(err)); + return; + } + if (prepause_frames) { + void *silence = calloc(prepause_frames, bytes_per_sample); + play(silence, prepause_frames * bytes_per_sample, 0); + free(silence); + } + } +} + +/* stop playing and empty buffers (for seeking/pause) */ +static void reset(void) +{ + int err; + + prepause_frames = 0; + if ((err = snd_pcm_drop(alsa_handler)) < 0) + { + mp_tmsg(MSGT_AO,MSGL_ERR,"[AO_ALSA] pcm prepare error: %s\n", snd_strerror(err)); + return; + } + if ((err = snd_pcm_prepare(alsa_handler)) < 0) + { + mp_tmsg(MSGT_AO,MSGL_ERR,"[AO_ALSA] pcm prepare error: %s\n", snd_strerror(err)); + return; + } + return; +} + +/* + plays 'len' bytes of 'data' + returns: number of bytes played + modified last at 29.06.02 by jp + thanxs for marius <marius@rospot.com> for giving us the light ;) +*/ + +static int play(void* data, int len, int flags) +{ + int num_frames; + snd_pcm_sframes_t res = 0; + if (!(flags & AOPLAY_FINAL_CHUNK)) + len = len / ao_data.outburst * ao_data.outburst; + num_frames = len / bytes_per_sample; + + //mp_msg(MSGT_AO,MSGL_ERR,"alsa-play: frames=%i, len=%i\n",num_frames,len); + + if (!alsa_handler) { + mp_tmsg(MSGT_AO,MSGL_ERR,"[AO_ALSA] Device configuration error."); + return 0; + } + + if (num_frames == 0) + return 0; + + do { + res = snd_pcm_writei(alsa_handler, data, num_frames); + + if (res == -EINTR) { + /* nothing to do */ + res = 0; + } + else if (res == -ESTRPIPE) { /* suspend */ + mp_tmsg(MSGT_AO,MSGL_INFO,"[AO_ALSA] Pcm in suspend mode, trying to resume.\n"); + while ((res = snd_pcm_resume(alsa_handler)) == -EAGAIN) + sleep(1); + } + if (res < 0) { + mp_tmsg(MSGT_AO,MSGL_ERR,"[AO_ALSA] Write error: %s\n", snd_strerror(res)); + mp_tmsg(MSGT_AO,MSGL_INFO,"[AO_ALSA] Trying to reset soundcard.\n"); + if ((res = snd_pcm_prepare(alsa_handler)) < 0) { + mp_tmsg(MSGT_AO,MSGL_ERR,"[AO_ALSA] pcm prepare error: %s\n", snd_strerror(res)); + return 0; + break; + } + } + } while (res == 0); + + return res < 0 ? res : res * bytes_per_sample; +} + +/* how many byes are free in the buffer */ +static int get_space(void) +{ + snd_pcm_status_t *status; + int ret; + + snd_pcm_status_alloca(&status); + + if ((ret = snd_pcm_status(alsa_handler, status)) < 0) + { + mp_tmsg(MSGT_AO,MSGL_ERR,"[AO_ALSA] Cannot get pcm status: %s\n", snd_strerror(ret)); + return 0; + } + + unsigned space = snd_pcm_status_get_avail(status) * bytes_per_sample; + if (space > ao_data.buffersize) // Buffer underrun? + space = ao_data.buffersize; + return space; +} + +/* delay in seconds between first and last sample in buffer */ +static float get_delay(void) +{ + if (alsa_handler) { + snd_pcm_sframes_t delay; + + if (snd_pcm_delay(alsa_handler, &delay) < 0) + return 0; + + if (delay < 0) { + /* underrun - move the application pointer forward to catch up */ + snd_pcm_forward(alsa_handler, -delay); + delay = 0; + } + return (float)delay / (float)ao_data.samplerate; + } else { + return 0; + } +} diff --git a/audio/out/ao_coreaudio.c b/audio/out/ao_coreaudio.c new file mode 100644 index 0000000000..146cfd2a22 --- /dev/null +++ b/audio/out/ao_coreaudio.c @@ -0,0 +1,1283 @@ +/* + * CoreAudio audio output driver for Mac OS X + * + * original copyright (C) Timothy J. Wood - Aug 2000 + * ported to MPlayer libao2 by Dan Christiansen + * + * The S/PDIF part of the code is based on the auhal audio output + * module from VideoLAN: + * Copyright (c) 2006 Derk-Jan Hartman <hartman at videolan dot org> + * + * This file is part of MPlayer. + * + * MPlayer 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. + * + * MPlayer 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 + * along with MPlayer; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * The MacOS X CoreAudio framework doesn't mesh as simply as some + * simpler frameworks do. This is due to the fact that CoreAudio pulls + * audio samples rather than having them pushed at it (which is nice + * when you are wanting to do good buffering of audio). + * + * AC-3 and MPEG audio passthrough is possible, but has never been tested + * due to lack of a soundcard that supports it. + */ + +#include <CoreServices/CoreServices.h> +#include <AudioUnit/AudioUnit.h> +#include <AudioToolbox/AudioToolbox.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <inttypes.h> +#include <sys/types.h> +#include <unistd.h> + +#include "config.h" +#include "mp_msg.h" + +#include "audio_out.h" +#include "audio_out_internal.h" +#include "libaf/format.h" +#include "osdep/timer.h" +#include "libavutil/fifo.h" +#include "subopt-helper.h" + +static const ao_info_t info = + { + "Darwin/Mac OS X native audio output", + "coreaudio", + "Timothy J. Wood & Dan Christiansen & Chris Roccati", + "" + }; + +LIBAO_EXTERN(coreaudio) + +/* Prefix for all mp_msg() calls */ +#define ao_msg(a, b, c...) mp_msg(a, b, "AO: [coreaudio] " c) + +#if MAC_OS_X_VERSION_MAX_ALLOWED <= 1040 +/* AudioDeviceIOProcID does not exist in Mac OS X 10.4. We can emulate + * this by using AudioDeviceAddIOProc() and AudioDeviceRemoveIOProc(). */ +#define AudioDeviceIOProcID AudioDeviceIOProc +#define AudioDeviceDestroyIOProcID AudioDeviceRemoveIOProc +static OSStatus AudioDeviceCreateIOProcID(AudioDeviceID dev, + AudioDeviceIOProc proc, + void *data, + AudioDeviceIOProcID *procid) +{ + *procid = proc; + return AudioDeviceAddIOProc(dev, proc, data); +} +#endif + +typedef struct ao_coreaudio_s +{ + AudioDeviceID i_selected_dev; /* Keeps DeviceID of the selected device. */ + int b_supports_digital; /* Does the currently selected device support digital mode? */ + int b_digital; /* Are we running in digital mode? */ + int b_muted; /* Are we muted in digital mode? */ + + AudioDeviceIOProcID renderCallback; /* Render callback used for SPDIF */ + + /* AudioUnit */ + AudioUnit theOutputUnit; + + /* CoreAudio SPDIF mode specific */ + pid_t i_hog_pid; /* Keeps the pid of our hog status. */ + AudioStreamID i_stream_id; /* The StreamID that has a cac3 streamformat */ + int i_stream_index; /* The index of i_stream_id in an AudioBufferList */ + AudioStreamBasicDescription stream_format;/* The format we changed the stream to */ + AudioStreamBasicDescription sfmt_revert; /* The original format of the stream */ + int b_revert; /* Whether we need to revert the stream format */ + int b_changed_mixing; /* Whether we need to set the mixing mode back */ + int b_stream_format_changed; /* Flag for main thread to reset stream's format to digital and reset buffer */ + + /* Original common part */ + int packetSize; + int paused; + + /* Ring-buffer */ + AVFifoBuffer *buffer; + unsigned int buffer_len; ///< must always be num_chunks * chunk_size + unsigned int num_chunks; + unsigned int chunk_size; +} ao_coreaudio_t; + +static ao_coreaudio_t *ao = NULL; + +/** + * \brief add data to ringbuffer + */ +static int write_buffer(unsigned char* data, int len){ + int free = ao->buffer_len - av_fifo_size(ao->buffer); + if (len > free) len = free; + return av_fifo_generic_write(ao->buffer, data, len, NULL); +} + +/** + * \brief remove data from ringbuffer + */ +static int read_buffer(unsigned char* data,int len){ + int buffered = av_fifo_size(ao->buffer); + if (len > buffered) len = buffered; + if (data) + av_fifo_generic_read(ao->buffer, data, len, NULL); + else + av_fifo_drain(ao->buffer, len); + return len; +} + +static OSStatus theRenderProc(void *inRefCon, + AudioUnitRenderActionFlags *inActionFlags, + const AudioTimeStamp *inTimeStamp, + UInt32 inBusNumber, UInt32 inNumFrames, + AudioBufferList *ioData) +{ +int amt=av_fifo_size(ao->buffer); +int req=(inNumFrames)*ao->packetSize; + + if(amt>req) + amt=req; + + if(amt) + read_buffer((unsigned char *)ioData->mBuffers[0].mData, amt); + else audio_pause(); + ioData->mBuffers[0].mDataByteSize = amt; + + return noErr; +} + +static int control(int cmd,void *arg){ +ao_control_vol_t *control_vol; +OSStatus err; +Float32 vol; + switch (cmd) { + case AOCONTROL_GET_VOLUME: + control_vol = (ao_control_vol_t*)arg; + if (ao->b_digital) { + // Digital output has no volume adjust. + int vol = ao->b_muted ? 0 : 100; + *control_vol = (ao_control_vol_t) { + .left = vol, .right = vol, + }; + return CONTROL_TRUE; + } + err = AudioUnitGetParameter(ao->theOutputUnit, kHALOutputParam_Volume, kAudioUnitScope_Global, 0, &vol); + + if(err==0) { + // printf("GET VOL=%f\n", vol); + control_vol->left=control_vol->right=vol*100.0/4.0; + return CONTROL_TRUE; + } + else { + ao_msg(MSGT_AO, MSGL_WARN, "could not get HAL output volume: [%4.4s]\n", (char *)&err); + return CONTROL_FALSE; + } + + case AOCONTROL_SET_VOLUME: + control_vol = (ao_control_vol_t*)arg; + + if (ao->b_digital) { + // Digital output can not set volume. Here we have to return true + // to make mixer forget it. Else mixer will add a soft filter, + // that's not we expected and the filter not support ac3 stream + // will cause mplayer die. + + // Although not support set volume, but at least we support mute. + // MPlayer set mute by set volume to zero, we handle it. + if (control_vol->left == 0 && control_vol->right == 0) + ao->b_muted = 1; + else + ao->b_muted = 0; + return CONTROL_TRUE; + } + + vol=(control_vol->left+control_vol->right)*4.0/200.0; + err = AudioUnitSetParameter(ao->theOutputUnit, kHALOutputParam_Volume, kAudioUnitScope_Global, 0, vol, 0); + if(err==0) { + // printf("SET VOL=%f\n", vol); + return CONTROL_TRUE; + } + else { + ao_msg(MSGT_AO, MSGL_WARN, "could not set HAL output volume: [%4.4s]\n", (char *)&err); + return CONTROL_FALSE; + } + /* Everything is currently unimplemented */ + default: + return CONTROL_FALSE; + } + +} + + +static void print_format(int lev, const char* str, const AudioStreamBasicDescription *f){ + uint32_t flags=(uint32_t) f->mFormatFlags; + ao_msg(MSGT_AO,lev, "%s %7.1fHz %"PRIu32"bit [%c%c%c%c][%"PRIu32"][%"PRIu32"][%"PRIu32"][%"PRIu32"][%"PRIu32"] %s %s %s%s%s%s\n", + str, f->mSampleRate, f->mBitsPerChannel, + (int)(f->mFormatID & 0xff000000) >> 24, + (int)(f->mFormatID & 0x00ff0000) >> 16, + (int)(f->mFormatID & 0x0000ff00) >> 8, + (int)(f->mFormatID & 0x000000ff) >> 0, + f->mFormatFlags, f->mBytesPerPacket, + f->mFramesPerPacket, f->mBytesPerFrame, + f->mChannelsPerFrame, + (flags&kAudioFormatFlagIsFloat) ? "float" : "int", + (flags&kAudioFormatFlagIsBigEndian) ? "BE" : "LE", + (flags&kAudioFormatFlagIsSignedInteger) ? "S" : "U", + (flags&kAudioFormatFlagIsPacked) ? " packed" : "", + (flags&kAudioFormatFlagIsAlignedHigh) ? " aligned" : "", + (flags&kAudioFormatFlagIsNonInterleaved) ? " ni" : "" ); +} + +static OSStatus GetAudioProperty(AudioObjectID id, + AudioObjectPropertySelector selector, + UInt32 outSize, void *outData) +{ + AudioObjectPropertyAddress property_address; + + property_address.mSelector = selector; + property_address.mScope = kAudioObjectPropertyScopeGlobal; + property_address.mElement = kAudioObjectPropertyElementMaster; + + return AudioObjectGetPropertyData(id, &property_address, 0, NULL, &outSize, outData); +} + +static UInt32 GetAudioPropertyArray(AudioObjectID id, + AudioObjectPropertySelector selector, + AudioObjectPropertyScope scope, + void **outData) +{ + OSStatus err; + AudioObjectPropertyAddress property_address; + UInt32 i_param_size; + + property_address.mSelector = selector; + property_address.mScope = scope; + property_address.mElement = kAudioObjectPropertyElementMaster; + + err = AudioObjectGetPropertyDataSize(id, &property_address, 0, NULL, &i_param_size); + + if (err != noErr) + return 0; + + *outData = malloc(i_param_size); + + + err = AudioObjectGetPropertyData(id, &property_address, 0, NULL, &i_param_size, *outData); + + if (err != noErr) { + free(*outData); + return 0; + } + + return i_param_size; +} + +static UInt32 GetGlobalAudioPropertyArray(AudioObjectID id, + AudioObjectPropertySelector selector, + void **outData) +{ + return GetAudioPropertyArray(id, selector, kAudioObjectPropertyScopeGlobal, outData); +} + +static OSStatus GetAudioPropertyString(AudioObjectID id, + AudioObjectPropertySelector selector, + char **outData) +{ + OSStatus err; + AudioObjectPropertyAddress property_address; + UInt32 i_param_size; + CFStringRef string; + CFIndex string_length; + + property_address.mSelector = selector; + property_address.mScope = kAudioObjectPropertyScopeGlobal; + property_address.mElement = kAudioObjectPropertyElementMaster; + + i_param_size = sizeof(CFStringRef); + err = AudioObjectGetPropertyData(id, &property_address, 0, NULL, &i_param_size, &string); + if (err != noErr) + return err; + + string_length = CFStringGetMaximumSizeForEncoding(CFStringGetLength(string), + kCFStringEncodingASCII); + *outData = malloc(string_length + 1); + CFStringGetCString(string, *outData, string_length + 1, kCFStringEncodingASCII); + + CFRelease(string); + + return err; +} + +static OSStatus SetAudioProperty(AudioObjectID id, + AudioObjectPropertySelector selector, + UInt32 inDataSize, void *inData) +{ + AudioObjectPropertyAddress property_address; + + property_address.mSelector = selector; + property_address.mScope = kAudioObjectPropertyScopeGlobal; + property_address.mElement = kAudioObjectPropertyElementMaster; + + return AudioObjectSetPropertyData(id, &property_address, 0, NULL, inDataSize, inData); +} + +static Boolean IsAudioPropertySettable(AudioObjectID id, + AudioObjectPropertySelector selector, + Boolean *outData) +{ + AudioObjectPropertyAddress property_address; + + property_address.mSelector = selector; + property_address.mScope = kAudioObjectPropertyScopeGlobal; + property_address.mElement = kAudioObjectPropertyElementMaster; + + return AudioObjectIsPropertySettable(id, &property_address, outData); +} + +static int AudioDeviceSupportsDigital( AudioDeviceID i_dev_id ); +static int AudioStreamSupportsDigital( AudioStreamID i_stream_id ); +static int OpenSPDIF(void); +static int AudioStreamChangeFormat( AudioStreamID i_stream_id, AudioStreamBasicDescription change_format ); +static OSStatus RenderCallbackSPDIF( AudioDeviceID inDevice, + const AudioTimeStamp * inNow, + const void * inInputData, + const AudioTimeStamp * inInputTime, + AudioBufferList * outOutputData, + const AudioTimeStamp * inOutputTime, + void * threadGlobals ); +static OSStatus StreamListener( AudioObjectID inObjectID, + UInt32 inNumberAddresses, + const AudioObjectPropertyAddress inAddresses[], + void *inClientData ); +static OSStatus DeviceListener( AudioObjectID inObjectID, + UInt32 inNumberAddresses, + const AudioObjectPropertyAddress inAddresses[], + void *inClientData ); + +static void print_help(void) +{ + OSStatus err; + UInt32 i_param_size; + int num_devices; + AudioDeviceID *devids; + char *device_name; + + mp_msg(MSGT_AO, MSGL_FATAL, + "\n-ao coreaudio commandline help:\n" + "Example: mpv -ao coreaudio:device_id=266\n" + " open Core Audio with output device ID 266.\n" + "\nOptions:\n" + " device_id\n" + " ID of output device to use (0 = default device)\n" + " help\n" + " This help including list of available devices.\n" + "\n" + "Available output devices:\n"); + + i_param_size = GetGlobalAudioPropertyArray(kAudioObjectSystemObject, kAudioHardwarePropertyDevices, (void **)&devids); + + if (!i_param_size) { + mp_msg(MSGT_AO, MSGL_FATAL, "Failed to get list of output devices.\n"); + return; + } + + num_devices = i_param_size / sizeof(AudioDeviceID); + + for (int i = 0; i < num_devices; ++i) { + err = GetAudioPropertyString(devids[i], kAudioObjectPropertyName, &device_name); + + if (err == noErr) { + mp_msg(MSGT_AO, MSGL_FATAL, "%s (id: %"PRIu32")\n", device_name, devids[i]); + free(device_name); + } else + mp_msg(MSGT_AO, MSGL_FATAL, "Unknown (id: %"PRIu32")\n", devids[i]); + } + + mp_msg(MSGT_AO, MSGL_FATAL, "\n"); + + free(devids); +} + +static int init(int rate,int channels,int format,int flags) +{ +AudioStreamBasicDescription inDesc; +ComponentDescription desc; +Component comp; +AURenderCallbackStruct renderCallback; +OSStatus err; +UInt32 size, maxFrames, b_alive; +char *psz_name; +AudioDeviceID devid_def = 0; +int device_id, display_help = 0; + + const opt_t subopts[] = { + {"device_id", OPT_ARG_INT, &device_id, NULL}, + {"help", OPT_ARG_BOOL, &display_help, NULL}, + {NULL} + }; + + // set defaults + device_id = 0; + + if (subopt_parse(ao_subdevice, subopts) != 0 || display_help) { + print_help(); + if (!display_help) + return 0; + } + + ao_msg(MSGT_AO,MSGL_V, "init([%dHz][%dch][%s][%d])\n", rate, channels, af_fmt2str_short(format), flags); + + ao = calloc(1, sizeof(ao_coreaudio_t)); + + ao->i_selected_dev = 0; + ao->b_supports_digital = 0; + ao->b_digital = 0; + ao->b_muted = 0; + ao->b_stream_format_changed = 0; + ao->i_hog_pid = -1; + ao->i_stream_id = 0; + ao->i_stream_index = -1; + ao->b_revert = 0; + ao->b_changed_mixing = 0; + + global_ao->per_application_mixer = true; + global_ao->no_persistent_volume = true; + + if (device_id == 0) { + /* Find the ID of the default Device. */ + err = GetAudioProperty(kAudioObjectSystemObject, + kAudioHardwarePropertyDefaultOutputDevice, + sizeof(UInt32), &devid_def); + if (err != noErr) + { + ao_msg(MSGT_AO, MSGL_WARN, "could not get default audio device: [%4.4s]\n", (char *)&err); + goto err_out; + } + } else { + devid_def = device_id; + } + + /* Retrieve the name of the device. */ + err = GetAudioPropertyString(devid_def, + kAudioObjectPropertyName, + &psz_name); + if (err != noErr) + { + ao_msg(MSGT_AO, MSGL_WARN, "could not get default audio device name: [%4.4s]\n", (char *)&err); + goto err_out; + } + + ao_msg(MSGT_AO,MSGL_V, "got audio output device ID: %"PRIu32" Name: %s\n", devid_def, psz_name ); + + /* Probe whether device support S/PDIF stream output if input is AC3 stream. */ + if (AF_FORMAT_IS_AC3(format)) { + if (AudioDeviceSupportsDigital(devid_def)) + { + ao->b_supports_digital = 1; + } + ao_msg(MSGT_AO, MSGL_V, + "probe default audio output device about support for digital s/pdif output: %d\n", + ao->b_supports_digital ); + } + + free(psz_name); + + // Save selected device id + ao->i_selected_dev = devid_def; + + // Build Description for the input format + inDesc.mSampleRate=rate; + inDesc.mFormatID=ao->b_supports_digital ? kAudioFormat60958AC3 : kAudioFormatLinearPCM; + inDesc.mChannelsPerFrame=channels; + inDesc.mBitsPerChannel=af_fmt2bits(format); + + if((format&AF_FORMAT_POINT_MASK)==AF_FORMAT_F) { + // float + inDesc.mFormatFlags = kAudioFormatFlagIsFloat|kAudioFormatFlagIsPacked; + } + else if((format&AF_FORMAT_SIGN_MASK)==AF_FORMAT_SI) { + // signed int + inDesc.mFormatFlags = kAudioFormatFlagIsSignedInteger|kAudioFormatFlagIsPacked; + } + else { + // unsigned int + inDesc.mFormatFlags = kAudioFormatFlagIsPacked; + } + if ((format & AF_FORMAT_END_MASK) == AF_FORMAT_BE) + inDesc.mFormatFlags |= kAudioFormatFlagIsBigEndian; + + inDesc.mFramesPerPacket = 1; + ao->packetSize = inDesc.mBytesPerPacket = inDesc.mBytesPerFrame = inDesc.mFramesPerPacket*channels*(inDesc.mBitsPerChannel/8); + print_format(MSGL_V, "source:",&inDesc); + + if (ao->b_supports_digital) + { + b_alive = 1; + err = GetAudioProperty(ao->i_selected_dev, + kAudioDevicePropertyDeviceIsAlive, + sizeof(UInt32), &b_alive); + if (err != noErr) + ao_msg(MSGT_AO, MSGL_WARN, "could not check whether device is alive: [%4.4s]\n", (char *)&err); + if (!b_alive) + ao_msg(MSGT_AO, MSGL_WARN, "device is not alive\n" ); + + /* S/PDIF output need device in HogMode. */ + err = GetAudioProperty(ao->i_selected_dev, + kAudioDevicePropertyHogMode, + sizeof(pid_t), &ao->i_hog_pid); + if (err != noErr) + { + /* This is not a fatal error. Some drivers simply don't support this property. */ + ao_msg(MSGT_AO, MSGL_WARN, "could not check whether device is hogged: [%4.4s]\n", + (char *)&err); + ao->i_hog_pid = -1; + } + + if (ao->i_hog_pid != -1 && ao->i_hog_pid != getpid()) + { + ao_msg(MSGT_AO, MSGL_WARN, "Selected audio device is exclusively in use by another program.\n" ); + goto err_out; + } + ao->stream_format = inDesc; + return OpenSPDIF(); + } + + /* original analog output code */ + desc.componentType = kAudioUnitType_Output; + desc.componentSubType = (device_id == 0) ? kAudioUnitSubType_DefaultOutput : kAudioUnitSubType_HALOutput; + desc.componentManufacturer = kAudioUnitManufacturer_Apple; + desc.componentFlags = 0; + desc.componentFlagsMask = 0; + + comp = FindNextComponent(NULL, &desc); //Finds an component that meets the desc spec's + if (comp == NULL) { + ao_msg(MSGT_AO, MSGL_WARN, "Unable to find Output Unit component\n"); + goto err_out; + } + + err = OpenAComponent(comp, &(ao->theOutputUnit)); //gains access to the services provided by the component + if (err) { + ao_msg(MSGT_AO, MSGL_WARN, "Unable to open Output Unit component: [%4.4s]\n", (char *)&err); + goto err_out; + } + + // Initialize AudioUnit + err = AudioUnitInitialize(ao->theOutputUnit); + if (err) { + ao_msg(MSGT_AO, MSGL_WARN, "Unable to initialize Output Unit component: [%4.4s]\n", (char *)&err); + goto err_out1; + } + + size = sizeof(AudioStreamBasicDescription); + err = AudioUnitSetProperty(ao->theOutputUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &inDesc, size); + + if (err) { + ao_msg(MSGT_AO, MSGL_WARN, "Unable to set the input format: [%4.4s]\n", (char *)&err); + goto err_out2; + } + + size = sizeof(UInt32); + err = AudioUnitGetProperty(ao->theOutputUnit, kAudioDevicePropertyBufferSize, kAudioUnitScope_Input, 0, &maxFrames, &size); + + if (err) + { + ao_msg(MSGT_AO,MSGL_WARN, "AudioUnitGetProperty returned [%4.4s] when getting kAudioDevicePropertyBufferSize\n", (char *)&err); + goto err_out2; + } + + //Set the Current Device to the Default Output Unit. + err = AudioUnitSetProperty(ao->theOutputUnit, kAudioOutputUnitProperty_CurrentDevice, kAudioUnitScope_Global, 0, &ao->i_selected_dev, sizeof(ao->i_selected_dev)); + + ao->chunk_size = maxFrames;//*inDesc.mBytesPerFrame; + + ao_data.samplerate = inDesc.mSampleRate; + ao_data.channels = inDesc.mChannelsPerFrame; + ao_data.bps = ao_data.samplerate * inDesc.mBytesPerFrame; + ao_data.outburst = ao->chunk_size; + ao_data.buffersize = ao_data.bps; + + ao->num_chunks = (ao_data.bps+ao->chunk_size-1)/ao->chunk_size; + ao->buffer_len = ao->num_chunks * ao->chunk_size; + ao->buffer = av_fifo_alloc(ao->buffer_len); + + ao_msg(MSGT_AO,MSGL_V, "using %5d chunks of %d bytes (buffer len %d bytes)\n", (int)ao->num_chunks, (int)ao->chunk_size, (int)ao->buffer_len); + + renderCallback.inputProc = theRenderProc; + renderCallback.inputProcRefCon = 0; + err = AudioUnitSetProperty(ao->theOutputUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, 0, &renderCallback, sizeof(AURenderCallbackStruct)); + if (err) { + ao_msg(MSGT_AO, MSGL_WARN, "Unable to set the render callback: [%4.4s]\n", (char *)&err); + goto err_out2; + } + + reset(); + + return CONTROL_OK; + +err_out2: + AudioUnitUninitialize(ao->theOutputUnit); +err_out1: + CloseComponent(ao->theOutputUnit); +err_out: + av_fifo_free(ao->buffer); + free(ao); + ao = NULL; + return CONTROL_FALSE; +} + +/***************************************************************************** + * Setup a encoded digital stream (SPDIF) + *****************************************************************************/ +static int OpenSPDIF(void) +{ + OSStatus err = noErr; + UInt32 i_param_size, b_mix = 0; + Boolean b_writeable = 0; + AudioStreamID *p_streams = NULL; + int i, i_streams = 0; + AudioObjectPropertyAddress property_address; + + /* Start doing the SPDIF setup process. */ + ao->b_digital = 1; + + /* Hog the device. */ + ao->i_hog_pid = getpid() ; + + err = SetAudioProperty(ao->i_selected_dev, + kAudioDevicePropertyHogMode, + sizeof(ao->i_hog_pid), &ao->i_hog_pid); + if (err != noErr) + { + ao_msg(MSGT_AO, MSGL_WARN, "failed to set hogmode: [%4.4s]\n", (char *)&err); + ao->i_hog_pid = -1; + goto err_out; + } + + property_address.mSelector = kAudioDevicePropertySupportsMixing; + property_address.mScope = kAudioObjectPropertyScopeGlobal; + property_address.mElement = kAudioObjectPropertyElementMaster; + + /* Set mixable to false if we are allowed to. */ + if (AudioObjectHasProperty(ao->i_selected_dev, &property_address)) { + /* Set mixable to false if we are allowed to. */ + err = IsAudioPropertySettable(ao->i_selected_dev, + kAudioDevicePropertySupportsMixing, + &b_writeable); + err = GetAudioProperty(ao->i_selected_dev, + kAudioDevicePropertySupportsMixing, + sizeof(UInt32), &b_mix); + if (err == noErr && b_writeable) + { + b_mix = 0; + err = SetAudioProperty(ao->i_selected_dev, + kAudioDevicePropertySupportsMixing, + sizeof(UInt32), &b_mix); + ao->b_changed_mixing = 1; + } + if (err != noErr) + { + ao_msg(MSGT_AO, MSGL_WARN, "failed to set mixmode: [%4.4s]\n", (char *)&err); + goto err_out; + } + } + + /* Get a list of all the streams on this device. */ + i_param_size = GetAudioPropertyArray(ao->i_selected_dev, + kAudioDevicePropertyStreams, + kAudioDevicePropertyScopeOutput, + (void **)&p_streams); + + if (!i_param_size) { + ao_msg(MSGT_AO, MSGL_WARN, "could not get number of streams.\n"); + goto err_out; + } + + i_streams = i_param_size / sizeof(AudioStreamID); + + ao_msg(MSGT_AO, MSGL_V, "current device stream number: %d\n", i_streams); + + for (i = 0; i < i_streams && ao->i_stream_index < 0; ++i) + { + /* Find a stream with a cac3 stream. */ + AudioStreamRangedDescription *p_format_list = NULL; + int i_formats = 0, j = 0, b_digital = 0; + + i_param_size = GetGlobalAudioPropertyArray(p_streams[i], + kAudioStreamPropertyAvailablePhysicalFormats, + (void **)&p_format_list); + + if (!i_param_size) { + ao_msg(MSGT_AO, MSGL_WARN, + "Could not get number of stream formats.\n"); + continue; + } + + i_formats = i_param_size / sizeof(AudioStreamRangedDescription); + + /* Check if one of the supported formats is a digital format. */ + for (j = 0; j < i_formats; ++j) + { + if (p_format_list[j].mFormat.mFormatID == 'IAC3' || + p_format_list[j].mFormat.mFormatID == 'iac3' || + p_format_list[j].mFormat.mFormatID == kAudioFormat60958AC3 || + p_format_list[j].mFormat.mFormatID == kAudioFormatAC3) + { + b_digital = 1; + break; + } + } + + if (b_digital) + { + /* If this stream supports a digital (cac3) format, then set it. */ + int i_requested_rate_format = -1; + int i_current_rate_format = -1; + int i_backup_rate_format = -1; + + ao->i_stream_id = p_streams[i]; + ao->i_stream_index = i; + + if (ao->b_revert == 0) + { + /* Retrieve the original format of this stream first if not done so already. */ + err = GetAudioProperty(ao->i_stream_id, + kAudioStreamPropertyPhysicalFormat, + sizeof(ao->sfmt_revert), &ao->sfmt_revert); + if (err != noErr) + { + ao_msg(MSGT_AO, MSGL_WARN, + "Could not retrieve the original stream format: [%4.4s]\n", + (char *)&err); + free(p_format_list); + continue; + } + ao->b_revert = 1; + } + + for (j = 0; j < i_formats; ++j) + if (p_format_list[j].mFormat.mFormatID == 'IAC3' || + p_format_list[j].mFormat.mFormatID == 'iac3' || + p_format_list[j].mFormat.mFormatID == kAudioFormat60958AC3 || + p_format_list[j].mFormat.mFormatID == kAudioFormatAC3) + { + if (p_format_list[j].mFormat.mSampleRate == ao->stream_format.mSampleRate) + { + i_requested_rate_format = j; + break; + } + if (p_format_list[j].mFormat.mSampleRate == ao->sfmt_revert.mSampleRate) + i_current_rate_format = j; + else if (i_backup_rate_format < 0 || p_format_list[j].mFormat.mSampleRate > p_format_list[i_backup_rate_format].mFormat.mSampleRate) + i_backup_rate_format = j; + } + + if (i_requested_rate_format >= 0) /* We prefer to output at the samplerate of the original audio. */ + ao->stream_format = p_format_list[i_requested_rate_format].mFormat; + else if (i_current_rate_format >= 0) /* If not possible, we will try to use the current samplerate of the device. */ + ao->stream_format = p_format_list[i_current_rate_format].mFormat; + else ao->stream_format = p_format_list[i_backup_rate_format].mFormat; /* And if we have to, any digital format will be just fine (highest rate possible). */ + } + free(p_format_list); + } + free(p_streams); + + if (ao->i_stream_index < 0) + { + ao_msg(MSGT_AO, MSGL_WARN, + "Cannot find any digital output stream format when OpenSPDIF().\n"); + goto err_out; + } + + print_format(MSGL_V, "original stream format:", &ao->sfmt_revert); + + if (!AudioStreamChangeFormat(ao->i_stream_id, ao->stream_format)) + goto err_out; + + property_address.mSelector = kAudioDevicePropertyDeviceHasChanged; + property_address.mScope = kAudioObjectPropertyScopeGlobal; + property_address.mElement = kAudioObjectPropertyElementMaster; + + err = AudioObjectAddPropertyListener(ao->i_selected_dev, + &property_address, + DeviceListener, + NULL); + if (err != noErr) + ao_msg(MSGT_AO, MSGL_WARN, "AudioDeviceAddPropertyListener for kAudioDevicePropertyDeviceHasChanged failed: [%4.4s]\n", (char *)&err); + + + /* FIXME: If output stream is not native byte-order, we need change endian somewhere. */ + /* Although there's no such case reported. */ +#if BYTE_ORDER == BIG_ENDIAN + if (!(ao->stream_format.mFormatFlags & kAudioFormatFlagIsBigEndian)) +#else + /* tell mplayer that we need a byteswap on AC3 streams, */ + if (ao->stream_format.mFormatID & kAudioFormat60958AC3) + ao_data.format = AF_FORMAT_AC3_LE; + + if (ao->stream_format.mFormatFlags & kAudioFormatFlagIsBigEndian) +#endif + ao_msg(MSGT_AO, MSGL_WARN, + "Output stream has non-native byte order, digital output may fail.\n"); + + /* For ac3/dts, just use packet size 6144 bytes as chunk size. */ + ao->chunk_size = ao->stream_format.mBytesPerPacket; + + ao_data.samplerate = ao->stream_format.mSampleRate; + ao_data.channels = ao->stream_format.mChannelsPerFrame; + ao_data.bps = ao_data.samplerate * (ao->stream_format.mBytesPerPacket/ao->stream_format.mFramesPerPacket); + ao_data.outburst = ao->chunk_size; + ao_data.buffersize = ao_data.bps; + + ao->num_chunks = (ao_data.bps+ao->chunk_size-1)/ao->chunk_size; + ao->buffer_len = ao->num_chunks * ao->chunk_size; + ao->buffer = av_fifo_alloc(ao->buffer_len); + + ao_msg(MSGT_AO,MSGL_V, "using %5d chunks of %d bytes (buffer len %d bytes)\n", (int)ao->num_chunks, (int)ao->chunk_size, (int)ao->buffer_len); + + + /* Create IOProc callback. */ + err = AudioDeviceCreateIOProcID(ao->i_selected_dev, + (AudioDeviceIOProc)RenderCallbackSPDIF, + (void *)ao, + &ao->renderCallback); + + if (err != noErr || ao->renderCallback == NULL) + { + ao_msg(MSGT_AO, MSGL_WARN, "AudioDeviceAddIOProc failed: [%4.4s]\n", (char *)&err); + goto err_out1; + } + + reset(); + + return CONTROL_TRUE; + +err_out1: + if (ao->b_revert) + AudioStreamChangeFormat(ao->i_stream_id, ao->sfmt_revert); +err_out: + if (ao->b_changed_mixing && ao->sfmt_revert.mFormatID != kAudioFormat60958AC3) + { + int b_mix = 1; + err = SetAudioProperty(ao->i_selected_dev, + kAudioDevicePropertySupportsMixing, + sizeof(int), &b_mix); + if (err != noErr) + ao_msg(MSGT_AO, MSGL_WARN, "failed to set mixmode: [%4.4s]\n", + (char *)&err); + } + if (ao->i_hog_pid == getpid()) + { + ao->i_hog_pid = -1; + err = SetAudioProperty(ao->i_selected_dev, + kAudioDevicePropertyHogMode, + sizeof(ao->i_hog_pid), &ao->i_hog_pid); + if (err != noErr) + ao_msg(MSGT_AO, MSGL_WARN, "Could not release hogmode: [%4.4s]\n", + (char *)&err); + } + av_fifo_free(ao->buffer); + free(ao); + ao = NULL; + return CONTROL_FALSE; +} + +/***************************************************************************** + * AudioDeviceSupportsDigital: Check i_dev_id for digital stream support. + *****************************************************************************/ +static int AudioDeviceSupportsDigital( AudioDeviceID i_dev_id ) +{ + UInt32 i_param_size = 0; + AudioStreamID *p_streams = NULL; + int i = 0, i_streams = 0; + int b_return = CONTROL_FALSE; + + /* Retrieve all the output streams. */ + i_param_size = GetAudioPropertyArray(i_dev_id, + kAudioDevicePropertyStreams, + kAudioDevicePropertyScopeOutput, + (void **)&p_streams); + + if (!i_param_size) { + ao_msg(MSGT_AO, MSGL_WARN, "could not get number of streams.\n"); + return CONTROL_FALSE; + } + + i_streams = i_param_size / sizeof(AudioStreamID); + + for (i = 0; i < i_streams; ++i) + { + if (AudioStreamSupportsDigital(p_streams[i])) + b_return = CONTROL_OK; + } + + free(p_streams); + return b_return; +} + +/***************************************************************************** + * AudioStreamSupportsDigital: Check i_stream_id for digital stream support. + *****************************************************************************/ +static int AudioStreamSupportsDigital( AudioStreamID i_stream_id ) +{ + UInt32 i_param_size; + AudioStreamRangedDescription *p_format_list = NULL; + int i, i_formats, b_return = CONTROL_FALSE; + + /* Retrieve all the stream formats supported by each output stream. */ + i_param_size = GetGlobalAudioPropertyArray(i_stream_id, + kAudioStreamPropertyAvailablePhysicalFormats, + (void **)&p_format_list); + + if (!i_param_size) { + ao_msg(MSGT_AO, MSGL_WARN, "Could not get number of stream formats.\n"); + return CONTROL_FALSE; + } + + i_formats = i_param_size / sizeof(AudioStreamRangedDescription); + + for (i = 0; i < i_formats; ++i) + { + print_format(MSGL_V, "supported format:", &(p_format_list[i].mFormat)); + + if (p_format_list[i].mFormat.mFormatID == 'IAC3' || + p_format_list[i].mFormat.mFormatID == 'iac3' || + p_format_list[i].mFormat.mFormatID == kAudioFormat60958AC3 || + p_format_list[i].mFormat.mFormatID == kAudioFormatAC3) + b_return = CONTROL_OK; + } + + free(p_format_list); + return b_return; +} + +/***************************************************************************** + * AudioStreamChangeFormat: Change i_stream_id to change_format + *****************************************************************************/ +static int AudioStreamChangeFormat( AudioStreamID i_stream_id, AudioStreamBasicDescription change_format ) +{ + OSStatus err = noErr; + int i; + AudioObjectPropertyAddress property_address; + + static volatile int stream_format_changed; + stream_format_changed = 0; + + print_format(MSGL_V, "setting stream format:", &change_format); + + /* Install the callback. */ + property_address.mSelector = kAudioStreamPropertyPhysicalFormat; + property_address.mScope = kAudioObjectPropertyScopeGlobal; + property_address.mElement = kAudioObjectPropertyElementMaster; + + err = AudioObjectAddPropertyListener(i_stream_id, + &property_address, + StreamListener, + (void *)&stream_format_changed); + if (err != noErr) + { + ao_msg(MSGT_AO, MSGL_WARN, "AudioStreamAddPropertyListener failed: [%4.4s]\n", (char *)&err); + return CONTROL_FALSE; + } + + /* Change the format. */ + err = SetAudioProperty(i_stream_id, + kAudioStreamPropertyPhysicalFormat, + sizeof(AudioStreamBasicDescription), &change_format); + if (err != noErr) + { + ao_msg(MSGT_AO, MSGL_WARN, "could not set the stream format: [%4.4s]\n", (char *)&err); + return CONTROL_FALSE; + } + + /* The AudioStreamSetProperty is not only asynchronious, + * it is also not Atomic, in its behaviour. + * Therefore we check 5 times before we really give up. + * FIXME: failing isn't actually implemented yet. */ + for (i = 0; i < 5; ++i) + { + AudioStreamBasicDescription actual_format; + int j; + for (j = 0; !stream_format_changed && j < 50; ++j) + usec_sleep(10000); + if (stream_format_changed) + stream_format_changed = 0; + else + ao_msg(MSGT_AO, MSGL_V, "reached timeout\n" ); + + err = GetAudioProperty(i_stream_id, + kAudioStreamPropertyPhysicalFormat, + sizeof(AudioStreamBasicDescription), &actual_format); + + print_format(MSGL_V, "actual format in use:", &actual_format); + if (actual_format.mSampleRate == change_format.mSampleRate && + actual_format.mFormatID == change_format.mFormatID && + actual_format.mFramesPerPacket == change_format.mFramesPerPacket) + { + /* The right format is now active. */ + break; + } + /* We need to check again. */ + } + + /* Removing the property listener. */ + err = AudioObjectRemovePropertyListener(i_stream_id, + &property_address, + StreamListener, + (void *)&stream_format_changed); + if (err != noErr) + { + ao_msg(MSGT_AO, MSGL_WARN, "AudioStreamRemovePropertyListener failed: [%4.4s]\n", (char *)&err); + return CONTROL_FALSE; + } + + return CONTROL_TRUE; +} + +/***************************************************************************** + * RenderCallbackSPDIF: callback for SPDIF audio output + *****************************************************************************/ +static OSStatus RenderCallbackSPDIF( AudioDeviceID inDevice, + const AudioTimeStamp * inNow, + const void * inInputData, + const AudioTimeStamp * inInputTime, + AudioBufferList * outOutputData, + const AudioTimeStamp * inOutputTime, + void * threadGlobals ) +{ + int amt = av_fifo_size(ao->buffer); + int req = outOutputData->mBuffers[ao->i_stream_index].mDataByteSize; + + if (amt > req) + amt = req; + if (amt) + read_buffer(ao->b_muted ? NULL : (unsigned char *)outOutputData->mBuffers[ao->i_stream_index].mData, amt); + + return noErr; +} + + +static int play(void* output_samples,int num_bytes,int flags) +{ + int wrote, b_digital; + SInt32 exit_reason; + + // Check whether we need to reset the digital output stream. + if (ao->b_digital && ao->b_stream_format_changed) + { + ao->b_stream_format_changed = 0; + b_digital = AudioStreamSupportsDigital(ao->i_stream_id); + if (b_digital) + { + /* Current stream supports digital format output, let's set it. */ + ao_msg(MSGT_AO, MSGL_V, + "Detected current stream supports digital, try to restore digital output...\n"); + + if (!AudioStreamChangeFormat(ao->i_stream_id, ao->stream_format)) + { + ao_msg(MSGT_AO, MSGL_WARN, "Restoring digital output failed.\n"); + } + else + { + ao_msg(MSGT_AO, MSGL_WARN, "Restoring digital output succeeded.\n"); + reset(); + } + } + else + ao_msg(MSGT_AO, MSGL_V, "Detected current stream does not support digital.\n"); + } + + wrote=write_buffer(output_samples, num_bytes); + audio_resume(); + + do { + exit_reason = CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.01, true); + } while (exit_reason == kCFRunLoopRunHandledSource); + + return wrote; +} + +/* set variables and buffer to initial state */ +static void reset(void) +{ + audio_pause(); + av_fifo_reset(ao->buffer); +} + + +/* return available space */ +static int get_space(void) +{ + return ao->buffer_len - av_fifo_size(ao->buffer); +} + + +/* return delay until audio is played */ +static float get_delay(void) +{ + // inaccurate, should also contain the data buffered e.g. by the OS + return (float)av_fifo_size(ao->buffer)/(float)ao_data.bps; +} + + +/* unload plugin and deregister from coreaudio */ +static void uninit(int immed) +{ + OSStatus err = noErr; + + if (!immed) { + long long timeleft=(1000000LL*av_fifo_size(ao->buffer))/ao_data.bps; + ao_msg(MSGT_AO,MSGL_DBG2, "%d bytes left @%d bps (%d usec)\n", av_fifo_size(ao->buffer), ao_data.bps, (int)timeleft); + usec_sleep((int)timeleft); + } + + if (!ao->b_digital) { + AudioOutputUnitStop(ao->theOutputUnit); + AudioUnitUninitialize(ao->theOutputUnit); + CloseComponent(ao->theOutputUnit); + } + else { + /* Stop device. */ + err = AudioDeviceStop(ao->i_selected_dev, ao->renderCallback); + if (err != noErr) + ao_msg(MSGT_AO, MSGL_WARN, "AudioDeviceStop failed: [%4.4s]\n", (char *)&err); + + /* Remove IOProc callback. */ + err = AudioDeviceDestroyIOProcID(ao->i_selected_dev, ao->renderCallback); + if (err != noErr) + ao_msg(MSGT_AO, MSGL_WARN, "AudioDeviceRemoveIOProc failed: [%4.4s]\n", (char *)&err); + + if (ao->b_revert) + AudioStreamChangeFormat(ao->i_stream_id, ao->sfmt_revert); + + if (ao->b_changed_mixing && ao->sfmt_revert.mFormatID != kAudioFormat60958AC3) + { + UInt32 b_mix; + Boolean b_writeable = 0; + /* Revert mixable to true if we are allowed to. */ + err = IsAudioPropertySettable(ao->i_selected_dev, + kAudioDevicePropertySupportsMixing, + &b_writeable); + err = GetAudioProperty(ao->i_selected_dev, + kAudioDevicePropertySupportsMixing, + sizeof(UInt32), &b_mix); + if (err == noErr && b_writeable) + { + b_mix = 1; + err = SetAudioProperty(ao->i_selected_dev, + kAudioDevicePropertySupportsMixing, + sizeof(UInt32), &b_mix); + } + if (err != noErr) + ao_msg(MSGT_AO, MSGL_WARN, "failed to set mixmode: [%4.4s]\n", (char *)&err); + } + if (ao->i_hog_pid == getpid()) + { + ao->i_hog_pid = -1; + err = SetAudioProperty(ao->i_selected_dev, + kAudioDevicePropertyHogMode, + sizeof(ao->i_hog_pid), &ao->i_hog_pid); + if (err != noErr) ao_msg(MSGT_AO, MSGL_WARN, "Could not release hogmode: [%4.4s]\n", (char *)&err); + } + } + + av_fifo_free(ao->buffer); + free(ao); + ao = NULL; +} + + +/* stop playing, keep buffers (for pause) */ +static void audio_pause(void) +{ + OSErr err=noErr; + + /* Stop callback. */ + if (!ao->b_digital) + { + err=AudioOutputUnitStop(ao->theOutputUnit); + if (err != noErr) + ao_msg(MSGT_AO,MSGL_WARN, "AudioOutputUnitStop returned [%4.4s]\n", (char *)&err); + } + else + { + err = AudioDeviceStop(ao->i_selected_dev, ao->renderCallback); + if (err != noErr) + ao_msg(MSGT_AO, MSGL_WARN, "AudioDeviceStop failed: [%4.4s]\n", (char *)&err); + } + ao->paused = 1; +} + + +/* resume playing, after audio_pause() */ +static void audio_resume(void) +{ + OSErr err=noErr; + + if (!ao->paused) + return; + + /* Start callback. */ + if (!ao->b_digital) + { + err = AudioOutputUnitStart(ao->theOutputUnit); + if (err != noErr) + ao_msg(MSGT_AO,MSGL_WARN, "AudioOutputUnitStart returned [%4.4s]\n", (char *)&err); + } + else + { + err = AudioDeviceStart(ao->i_selected_dev, ao->renderCallback); + if (err != noErr) + ao_msg(MSGT_AO, MSGL_WARN, "AudioDeviceStart failed: [%4.4s]\n", (char *)&err); + } + ao->paused = 0; +} + +/***************************************************************************** + * StreamListener + *****************************************************************************/ +static OSStatus StreamListener( AudioObjectID inObjectID, + UInt32 inNumberAddresses, + const AudioObjectPropertyAddress inAddresses[], + void *inClientData ) +{ + for (int i=0; i < inNumberAddresses; ++i) + { + if (inAddresses[i].mSelector == kAudioStreamPropertyPhysicalFormat) { + ao_msg(MSGT_AO, MSGL_WARN, "got notify kAudioStreamPropertyPhysicalFormat changed.\n"); + if (inClientData) + *(volatile int *)inClientData = 1; + break; + } + } + return noErr; +} + +static OSStatus DeviceListener( AudioObjectID inObjectID, + UInt32 inNumberAddresses, + const AudioObjectPropertyAddress inAddresses[], + void *inClientData ) +{ + for (int i=0; i < inNumberAddresses; ++i) + { + if (inAddresses[i].mSelector == kAudioDevicePropertyDeviceHasChanged) { + ao_msg(MSGT_AO, MSGL_WARN, "got notify kAudioDevicePropertyDeviceHasChanged.\n"); + ao->b_stream_format_changed = 1; + break; + } + } + return noErr; +} diff --git a/audio/out/ao_dsound.c b/audio/out/ao_dsound.c new file mode 100644 index 0000000000..f2f44dd401 --- /dev/null +++ b/audio/out/ao_dsound.c @@ -0,0 +1,648 @@ +/* + * Windows DirectSound interface + * + * Copyright (c) 2004 Gabor Szecsi <deje@miki.hu> + * + * This file is part of MPlayer. + * + * MPlayer 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. + * + * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/** +\todo verify/extend multichannel support +*/ + + +#include <stdio.h> +#include <stdlib.h> +#include <windows.h> +#define DIRECTSOUND_VERSION 0x0600 +#include <dsound.h> +#include <math.h> + +#include "config.h" +#include "libaf/format.h" +#include "audio_out.h" +#include "audio_out_internal.h" +#include "mp_msg.h" +#include "osdep/timer.h" +#include "subopt-helper.h" + + +static const ao_info_t info = +{ + "Windows DirectSound audio output", + "dsound", + "Gabor Szecsi <deje@miki.hu>", + "" +}; + +LIBAO_EXTERN(dsound) + +/** +\todo use the definitions from the win32 api headers when they define these +*/ +#define WAVE_FORMAT_IEEE_FLOAT 0x0003 +#define WAVE_FORMAT_DOLBY_AC3_SPDIF 0x0092 +#define WAVE_FORMAT_EXTENSIBLE 0xFFFE + +static const GUID KSDATAFORMAT_SUBTYPE_PCM = {0x1,0x0000,0x0010, {0x80,0x00,0x00,0xaa,0x00,0x38,0x9b,0x71}}; + +#define SPEAKER_FRONT_LEFT 0x1 +#define SPEAKER_FRONT_RIGHT 0x2 +#define SPEAKER_FRONT_CENTER 0x4 +#define SPEAKER_LOW_FREQUENCY 0x8 +#define SPEAKER_BACK_LEFT 0x10 +#define SPEAKER_BACK_RIGHT 0x20 +#define SPEAKER_FRONT_LEFT_OF_CENTER 0x40 +#define SPEAKER_FRONT_RIGHT_OF_CENTER 0x80 +#define SPEAKER_BACK_CENTER 0x100 +#define SPEAKER_SIDE_LEFT 0x200 +#define SPEAKER_SIDE_RIGHT 0x400 +#define SPEAKER_TOP_CENTER 0x800 +#define SPEAKER_TOP_FRONT_LEFT 0x1000 +#define SPEAKER_TOP_FRONT_CENTER 0x2000 +#define SPEAKER_TOP_FRONT_RIGHT 0x4000 +#define SPEAKER_TOP_BACK_LEFT 0x8000 +#define SPEAKER_TOP_BACK_CENTER 0x10000 +#define SPEAKER_TOP_BACK_RIGHT 0x20000 +#define SPEAKER_RESERVED 0x80000000 + +#if 0 +#define DSSPEAKER_HEADPHONE 0x00000001 +#define DSSPEAKER_MONO 0x00000002 +#define DSSPEAKER_QUAD 0x00000003 +#define DSSPEAKER_STEREO 0x00000004 +#define DSSPEAKER_SURROUND 0x00000005 +#define DSSPEAKER_5POINT1 0x00000006 +#endif + +#ifndef _WAVEFORMATEXTENSIBLE_ +typedef struct { + WAVEFORMATEX Format; + union { + WORD wValidBitsPerSample; /* bits of precision */ + WORD wSamplesPerBlock; /* valid if wBitsPerSample==0 */ + WORD wReserved; /* If neither applies, set to zero. */ + } Samples; + DWORD dwChannelMask; /* which channels are */ + /* present in stream */ + GUID SubFormat; +} WAVEFORMATEXTENSIBLE, *PWAVEFORMATEXTENSIBLE; +#endif + +static const int channel_mask[] = { + SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_LOW_FREQUENCY, + SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT, + SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT | SPEAKER_LOW_FREQUENCY, + SPEAKER_FRONT_LEFT | SPEAKER_FRONT_CENTER | SPEAKER_FRONT_RIGHT | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT | SPEAKER_LOW_FREQUENCY +}; + +static HINSTANCE hdsound_dll = NULL; ///handle to the dll +static LPDIRECTSOUND hds = NULL; ///direct sound object +static LPDIRECTSOUNDBUFFER hdspribuf = NULL; ///primary direct sound buffer +static LPDIRECTSOUNDBUFFER hdsbuf = NULL; ///secondary direct sound buffer (stream buffer) +static int buffer_size = 0; ///size in bytes of the direct sound buffer +static int write_offset = 0; ///offset of the write cursor in the direct sound buffer +static int min_free_space = 0; ///if the free space is below this value get_space() will return 0 + ///there will always be at least this amout of free space to prevent + ///get_space() from returning wrong values when buffer is 100% full. + ///will be replaced with nBlockAlign in init() +static int underrun_check = 0; ///0 or last reported free space (underrun detection) +static int device_num = 0; ///wanted device number +static GUID device; ///guid of the device +static int audio_volume; + +/***************************************************************************************/ + +/** +\brief output error message +\param err error code +\return string with the error message +*/ +static char * dserr2str(int err) +{ + switch (err) { + case DS_OK: return "DS_OK"; + case DS_NO_VIRTUALIZATION: return "DS_NO_VIRTUALIZATION"; + case DSERR_ALLOCATED: return "DS_NO_VIRTUALIZATION"; + case DSERR_CONTROLUNAVAIL: return "DSERR_CONTROLUNAVAIL"; + case DSERR_INVALIDPARAM: return "DSERR_INVALIDPARAM"; + case DSERR_INVALIDCALL: return "DSERR_INVALIDCALL"; + case DSERR_GENERIC: return "DSERR_GENERIC"; + case DSERR_PRIOLEVELNEEDED: return "DSERR_PRIOLEVELNEEDED"; + case DSERR_OUTOFMEMORY: return "DSERR_OUTOFMEMORY"; + case DSERR_BADFORMAT: return "DSERR_BADFORMAT"; + case DSERR_UNSUPPORTED: return "DSERR_UNSUPPORTED"; + case DSERR_NODRIVER: return "DSERR_NODRIVER"; + case DSERR_ALREADYINITIALIZED: return "DSERR_ALREADYINITIALIZED"; + case DSERR_NOAGGREGATION: return "DSERR_NOAGGREGATION"; + case DSERR_BUFFERLOST: return "DSERR_BUFFERLOST"; + case DSERR_OTHERAPPHASPRIO: return "DSERR_OTHERAPPHASPRIO"; + case DSERR_UNINITIALIZED: return "DSERR_UNINITIALIZED"; + case DSERR_NOINTERFACE: return "DSERR_NOINTERFACE"; + case DSERR_ACCESSDENIED: return "DSERR_ACCESSDENIED"; + default: return "unknown"; + } +} + +/** +\brief uninitialize direct sound +*/ +static void UninitDirectSound(void) +{ + // finally release the DirectSound object + if (hds) { + IDirectSound_Release(hds); + hds = NULL; + } + // free DSOUND.DLL + if (hdsound_dll) { + FreeLibrary(hdsound_dll); + hdsound_dll = NULL; + } + mp_msg(MSGT_AO, MSGL_V, "ao_dsound: DirectSound uninitialized\n"); +} + +/** +\brief print the commandline help +*/ +static void print_help(void) +{ + mp_msg(MSGT_AO, MSGL_FATAL, + "\n-ao dsound commandline help:\n" + "Example: mpv -ao dsound:device=1\n" + " sets 1st device\n" + "\nOptions:\n" + " device=<device-number>\n" + " Sets device number, use -v to get a list\n"); +} + + +/** +\brief enumerate direct sound devices +\return TRUE to continue with the enumeration +*/ +static BOOL CALLBACK DirectSoundEnum(LPGUID guid,LPCSTR desc,LPCSTR module,LPVOID context) +{ + int* device_index=context; + mp_msg(MSGT_AO, MSGL_V,"%i %s ",*device_index,desc); + if(device_num==*device_index){ + mp_msg(MSGT_AO, MSGL_V,"<--"); + if(guid){ + memcpy(&device,guid,sizeof(GUID)); + } + } + mp_msg(MSGT_AO, MSGL_V,"\n"); + (*device_index)++; + return TRUE; +} + + +/** +\brief initilize direct sound +\return 0 if error, 1 if ok +*/ +static int InitDirectSound(void) +{ + DSCAPS dscaps; + + // initialize directsound + HRESULT (WINAPI *OurDirectSoundCreate)(LPGUID, LPDIRECTSOUND *, LPUNKNOWN); + HRESULT (WINAPI *OurDirectSoundEnumerate)(LPDSENUMCALLBACKA, LPVOID); + int device_index=0; + const opt_t subopts[] = { + {"device", OPT_ARG_INT, &device_num,NULL}, + {NULL} + }; + if (subopt_parse(ao_subdevice, subopts) != 0) { + print_help(); + return 0; + } + + hdsound_dll = LoadLibrary("DSOUND.DLL"); + if (hdsound_dll == NULL) { + mp_msg(MSGT_AO, MSGL_ERR, "ao_dsound: cannot load DSOUND.DLL\n"); + return 0; + } + OurDirectSoundCreate = (void*)GetProcAddress(hdsound_dll, "DirectSoundCreate"); + OurDirectSoundEnumerate = (void*)GetProcAddress(hdsound_dll, "DirectSoundEnumerateA"); + + if (OurDirectSoundCreate == NULL || OurDirectSoundEnumerate == NULL) { + mp_msg(MSGT_AO, MSGL_ERR, "ao_dsound: GetProcAddress FAILED\n"); + FreeLibrary(hdsound_dll); + return 0; + } + + // Enumerate all directsound devices + mp_msg(MSGT_AO, MSGL_V,"ao_dsound: Output Devices:\n"); + OurDirectSoundEnumerate(DirectSoundEnum,&device_index); + + // Create the direct sound object + if FAILED(OurDirectSoundCreate((device_num)?&device:NULL, &hds, NULL )) { + mp_msg(MSGT_AO, MSGL_ERR, "ao_dsound: cannot create a DirectSound device\n"); + FreeLibrary(hdsound_dll); + return 0; + } + + /* Set DirectSound Cooperative level, ie what control we want over Windows + * sound device. In our case, DSSCL_EXCLUSIVE means that we can modify the + * settings of the primary buffer, but also that only the sound of our + * application will be hearable when it will have the focus. + * !!! (this is not really working as intended yet because to set the + * cooperative level you need the window handle of your application, and + * I don't know of any easy way to get it. Especially since we might play + * sound without any video, and so what window handle should we use ??? + * The hack for now is to use the Desktop window handle - it seems to be + * working */ + if (IDirectSound_SetCooperativeLevel(hds, GetDesktopWindow(), DSSCL_EXCLUSIVE)) { + mp_msg(MSGT_AO, MSGL_ERR, "ao_dsound: cannot set direct sound cooperative level\n"); + IDirectSound_Release(hds); + FreeLibrary(hdsound_dll); + return 0; + } + mp_msg(MSGT_AO, MSGL_V, "ao_dsound: DirectSound initialized\n"); + + memset(&dscaps, 0, sizeof(DSCAPS)); + dscaps.dwSize = sizeof(DSCAPS); + if (DS_OK == IDirectSound_GetCaps(hds, &dscaps)) { + if (dscaps.dwFlags & DSCAPS_EMULDRIVER) mp_msg(MSGT_AO, MSGL_V, "ao_dsound: DirectSound is emulated, waveOut may give better performance\n"); + } else { + mp_msg(MSGT_AO, MSGL_V, "ao_dsound: cannot get device capabilities\n"); + } + + return 1; +} + +/** +\brief destroy the direct sound buffer +*/ +static void DestroyBuffer(void) +{ + if (hdsbuf) { + IDirectSoundBuffer_Release(hdsbuf); + hdsbuf = NULL; + } + if (hdspribuf) { + IDirectSoundBuffer_Release(hdspribuf); + hdspribuf = NULL; + } +} + +/** +\brief fill sound buffer +\param data pointer to the sound data to copy +\param len length of the data to copy in bytes +\return number of copyed bytes +*/ +static int write_buffer(unsigned char *data, int len) +{ + HRESULT res; + LPVOID lpvPtr1; + DWORD dwBytes1; + LPVOID lpvPtr2; + DWORD dwBytes2; + + underrun_check = 0; + + // Lock the buffer + res = IDirectSoundBuffer_Lock(hdsbuf,write_offset, len, &lpvPtr1, &dwBytes1, &lpvPtr2, &dwBytes2, 0); + // If the buffer was lost, restore and retry lock. + if (DSERR_BUFFERLOST == res) + { + IDirectSoundBuffer_Restore(hdsbuf); + res = IDirectSoundBuffer_Lock(hdsbuf,write_offset, len, &lpvPtr1, &dwBytes1, &lpvPtr2, &dwBytes2, 0); + } + + + if (SUCCEEDED(res)) + { + if( (ao_data.channels == 6) && !AF_FORMAT_IS_AC3(ao_data.format) ) { + // reorder channels while writing to pointers. + // it's this easy because buffer size and len are always + // aligned to multiples of channels*bytespersample + // there's probably some room for speed improvements here + const int chantable[6] = {0, 1, 4, 5, 2, 3}; // reorder "matrix" + int i, j; + int numsamp,sampsize; + + sampsize = af_fmt2bits(ao_data.format)>>3; // bytes per sample + numsamp = dwBytes1 / (ao_data.channels * sampsize); // number of samples for each channel in this buffer + + for( i = 0; i < numsamp; i++ ) for( j = 0; j < ao_data.channels; j++ ) { + memcpy((char*)lpvPtr1+(i*ao_data.channels*sampsize)+(chantable[j]*sampsize),data+(i*ao_data.channels*sampsize)+(j*sampsize),sampsize); + } + + if (NULL != lpvPtr2 ) + { + numsamp = dwBytes2 / (ao_data.channels * sampsize); + for( i = 0; i < numsamp; i++ ) for( j = 0; j < ao_data.channels; j++ ) { + memcpy((char*)lpvPtr2+(i*ao_data.channels*sampsize)+(chantable[j]*sampsize),data+dwBytes1+(i*ao_data.channels*sampsize)+(j*sampsize),sampsize); + } + } + + write_offset+=dwBytes1+dwBytes2; + if(write_offset>=buffer_size)write_offset=dwBytes2; + } else { + // Write to pointers without reordering. + memcpy(lpvPtr1,data,dwBytes1); + if (NULL != lpvPtr2 )memcpy(lpvPtr2,data+dwBytes1,dwBytes2); + write_offset+=dwBytes1+dwBytes2; + if(write_offset>=buffer_size)write_offset=dwBytes2; + } + + // Release the data back to DirectSound. + res = IDirectSoundBuffer_Unlock(hdsbuf,lpvPtr1,dwBytes1,lpvPtr2,dwBytes2); + if (SUCCEEDED(res)) + { + // Success. + DWORD status; + IDirectSoundBuffer_GetStatus(hdsbuf, &status); + if (!(status & DSBSTATUS_PLAYING)){ + res = IDirectSoundBuffer_Play(hdsbuf, 0, 0, DSBPLAY_LOOPING); + } + return dwBytes1+dwBytes2; + } + } + // Lock, Unlock, or Restore failed. + return 0; +} + +/***************************************************************************************/ + +/** +\brief handle control commands +\param cmd command +\param arg argument +\return CONTROL_OK or -1 in case the command can't be handled +*/ +static int control(int cmd, void *arg) +{ + DWORD volume; + switch (cmd) { + case AOCONTROL_GET_VOLUME: { + ao_control_vol_t* vol = (ao_control_vol_t*)arg; + vol->left = vol->right = audio_volume; + return CONTROL_OK; + } + case AOCONTROL_SET_VOLUME: { + ao_control_vol_t* vol = (ao_control_vol_t*)arg; + volume = audio_volume = vol->right; + if (volume < 1) + volume = 1; + volume = (DWORD)(log10(volume) * 5000.0) - 10000; + IDirectSoundBuffer_SetVolume(hdsbuf, volume); + return CONTROL_OK; + } + } + return -1; +} + +/** +\brief setup sound device +\param rate samplerate +\param channels number of channels +\param format format +\param flags unused +\return 1=success 0=fail +*/ +static int init(int rate, int channels, int format, int flags) +{ + int res; + if (!InitDirectSound()) return 0; + + global_ao->no_persistent_volume = true; + audio_volume = 100; + + // ok, now create the buffers + WAVEFORMATEXTENSIBLE wformat; + DSBUFFERDESC dsbpridesc; + DSBUFFERDESC dsbdesc; + + //check if the channel count and format is supported in general + if (channels > 6) { + UninitDirectSound(); + mp_msg(MSGT_AO, MSGL_ERR, "ao_dsound: 8 channel audio not yet supported\n"); + return 0; + } + + if (AF_FORMAT_IS_AC3(format)) + format = AF_FORMAT_AC3_NE; + switch(format){ + case AF_FORMAT_AC3_NE: + case AF_FORMAT_S24_LE: + case AF_FORMAT_S16_LE: + case AF_FORMAT_U8: + break; + default: + mp_msg(MSGT_AO, MSGL_V,"ao_dsound: format %s not supported defaulting to Signed 16-bit Little-Endian\n",af_fmt2str_short(format)); + format=AF_FORMAT_S16_LE; + } + //fill global ao_data + ao_data.channels = channels; + ao_data.samplerate = rate; + ao_data.format = format; + ao_data.bps = channels * rate * (af_fmt2bits(format)>>3); + if(ao_data.buffersize==-1) ao_data.buffersize = ao_data.bps; // space for 1 sec + mp_msg(MSGT_AO, MSGL_V,"ao_dsound: Samplerate:%iHz Channels:%i Format:%s\n", rate, channels, af_fmt2str_short(format)); + mp_msg(MSGT_AO, MSGL_V,"ao_dsound: Buffersize:%d bytes (%d msec)\n", ao_data.buffersize, ao_data.buffersize / ao_data.bps * 1000); + + //fill waveformatex + ZeroMemory(&wformat, sizeof(WAVEFORMATEXTENSIBLE)); + wformat.Format.cbSize = (channels > 2) ? sizeof(WAVEFORMATEXTENSIBLE)-sizeof(WAVEFORMATEX) : 0; + wformat.Format.nChannels = channels; + wformat.Format.nSamplesPerSec = rate; + if (AF_FORMAT_IS_AC3(format)) { + wformat.Format.wFormatTag = WAVE_FORMAT_DOLBY_AC3_SPDIF; + wformat.Format.wBitsPerSample = 16; + wformat.Format.nBlockAlign = 4; + } else { + wformat.Format.wFormatTag = (channels > 2) ? WAVE_FORMAT_EXTENSIBLE : WAVE_FORMAT_PCM; + wformat.Format.wBitsPerSample = af_fmt2bits(format); + wformat.Format.nBlockAlign = wformat.Format.nChannels * (wformat.Format.wBitsPerSample >> 3); + } + + // fill in primary sound buffer descriptor + memset(&dsbpridesc, 0, sizeof(DSBUFFERDESC)); + dsbpridesc.dwSize = sizeof(DSBUFFERDESC); + dsbpridesc.dwFlags = DSBCAPS_PRIMARYBUFFER; + dsbpridesc.dwBufferBytes = 0; + dsbpridesc.lpwfxFormat = NULL; + + + // fill in the secondary sound buffer (=stream buffer) descriptor + memset(&dsbdesc, 0, sizeof(DSBUFFERDESC)); + dsbdesc.dwSize = sizeof(DSBUFFERDESC); + dsbdesc.dwFlags = DSBCAPS_GETCURRENTPOSITION2 /** Better position accuracy */ + | DSBCAPS_GLOBALFOCUS /** Allows background playing */ + | DSBCAPS_CTRLVOLUME; /** volume control enabled */ + + if (channels > 2) { + wformat.dwChannelMask = channel_mask[channels - 3]; + wformat.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; + wformat.Samples.wValidBitsPerSample = wformat.Format.wBitsPerSample; + // Needed for 5.1 on emu101k - shit soundblaster + dsbdesc.dwFlags |= DSBCAPS_LOCHARDWARE; + } + wformat.Format.nAvgBytesPerSec = wformat.Format.nSamplesPerSec * wformat.Format.nBlockAlign; + + dsbdesc.dwBufferBytes = ao_data.buffersize; + dsbdesc.lpwfxFormat = (WAVEFORMATEX *)&wformat; + buffer_size = dsbdesc.dwBufferBytes; + write_offset = 0; + min_free_space = wformat.Format.nBlockAlign; + ao_data.outburst = wformat.Format.nBlockAlign * 512; + + // create primary buffer and set its format + + res = IDirectSound_CreateSoundBuffer( hds, &dsbpridesc, &hdspribuf, NULL ); + if ( res != DS_OK ) { + UninitDirectSound(); + mp_msg(MSGT_AO, MSGL_ERR,"ao_dsound: cannot create primary buffer (%s)\n", dserr2str(res)); + return 0; + } + res = IDirectSoundBuffer_SetFormat( hdspribuf, (WAVEFORMATEX *)&wformat ); + if ( res != DS_OK ) mp_msg(MSGT_AO, MSGL_WARN,"ao_dsound: cannot set primary buffer format (%s), using standard setting (bad quality)", dserr2str(res)); + + mp_msg(MSGT_AO, MSGL_V, "ao_dsound: primary buffer created\n"); + + // now create the stream buffer + + res = IDirectSound_CreateSoundBuffer(hds, &dsbdesc, &hdsbuf, NULL); + if (res != DS_OK) { + if (dsbdesc.dwFlags & DSBCAPS_LOCHARDWARE) { + // Try without DSBCAPS_LOCHARDWARE + dsbdesc.dwFlags &= ~DSBCAPS_LOCHARDWARE; + res = IDirectSound_CreateSoundBuffer(hds, &dsbdesc, &hdsbuf, NULL); + } + if (res != DS_OK) { + UninitDirectSound(); + mp_msg(MSGT_AO, MSGL_ERR, "ao_dsound: cannot create secondary (stream)buffer (%s)\n", dserr2str(res)); + return 0; + } + } + mp_msg(MSGT_AO, MSGL_V, "ao_dsound: secondary (stream)buffer created\n"); + return 1; +} + + + +/** +\brief stop playing and empty buffers (for seeking/pause) +*/ +static void reset(void) +{ + IDirectSoundBuffer_Stop(hdsbuf); + // reset directsound buffer + IDirectSoundBuffer_SetCurrentPosition(hdsbuf, 0); + write_offset=0; + underrun_check=0; +} + +/** +\brief stop playing, keep buffers (for pause) +*/ +static void audio_pause(void) +{ + IDirectSoundBuffer_Stop(hdsbuf); +} + +/** +\brief resume playing, after audio_pause() +*/ +static void audio_resume(void) +{ + IDirectSoundBuffer_Play(hdsbuf, 0, 0, DSBPLAY_LOOPING); +} + +/** +\brief close audio device +\param immed stop playback immediately +*/ +static void uninit(int immed) +{ + if (!immed) + usec_sleep(get_delay() * 1000000); + reset(); + + DestroyBuffer(); + UninitDirectSound(); +} + +// return exact number of free (safe to write) bytes +static int check_free_buffer_size(void) +{ + int space; + DWORD play_offset; + IDirectSoundBuffer_GetCurrentPosition(hdsbuf,&play_offset,NULL); + space=buffer_size-(write_offset-play_offset); + // | | <-- const --> | | | + // buffer start play_cursor write_cursor write_offset buffer end + // play_cursor is the actual postion of the play cursor + // write_cursor is the position after which it is assumed to be save to write data + // write_offset is the postion where we actually write the data to + if(space > buffer_size)space -= buffer_size; // write_offset < play_offset + // Check for buffer underruns. An underrun happens if DirectSound + // started to play old data beyond the current write_offset. Detect this + // by checking whether the free space shrinks, even though no data was + // written (i.e. no write_buffer). Doesn't always work, but the only + // reason we need this is to deal with the situation when playback ends, + // and the buffer is only half-filled. + if (space < underrun_check) { + // there's no useful data in the buffers + space = buffer_size; + reset(); + } + underrun_check = space; + return space; +} + +/** +\brief find out how many bytes can be written into the audio buffer without +\return free space in bytes, has to return 0 if the buffer is almost full +*/ +static int get_space(void) +{ + int space = check_free_buffer_size(); + if(space < min_free_space)return 0; + return space-min_free_space; +} + +/** +\brief play 'len' bytes of 'data' +\param data pointer to the data to play +\param len size in bytes of the data buffer, gets rounded down to outburst*n +\param flags currently unused +\return number of played bytes +*/ +static int play(void* data, int len, int flags) +{ + int space = check_free_buffer_size(); + if(space < len) len = space; + + if (!(flags & AOPLAY_FINAL_CHUNK)) + len = (len / ao_data.outburst) * ao_data.outburst; + return write_buffer(data, len); +} + +/** +\brief get the delay between the first and last sample in the buffer +\return delay in seconds +*/ +static float get_delay(void) +{ + int space = check_free_buffer_size(); + return (float)(buffer_size - space) / (float)ao_data.bps; +} diff --git a/audio/out/ao_jack.c b/audio/out/ao_jack.c new file mode 100644 index 0000000000..b30f99a14e --- /dev/null +++ b/audio/out/ao_jack.c @@ -0,0 +1,361 @@ +/* + * JACK audio output driver for MPlayer + * + * Copyleft 2001 by Felix Bünemann (atmosfear@users.sf.net) + * and Reimar Döffinger (Reimar.Doeffinger@stud.uni-karlsruhe.de) + * + * This file is part of MPlayer. + * + * MPlayer 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. + * + * MPlayer 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 + * along with MPlayer; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "config.h" +#include "mp_msg.h" + +#include "audio_out.h" +#include "audio_out_internal.h" +#include "libaf/format.h" +#include "osdep/timer.h" +#include "subopt-helper.h" + +#include "libavutil/fifo.h" + +#include <jack/jack.h> + +static const ao_info_t info = +{ + "JACK audio output", + "jack", + "Reimar Döffinger <Reimar.Doeffinger@stud.uni-karlsruhe.de>", + "based on ao_sdl.c" +}; + +LIBAO_EXTERN(jack) + +//! maximum number of channels supported, avoids lots of mallocs +#define MAX_CHANS 8 +static jack_port_t *ports[MAX_CHANS]; +static int num_ports; ///< Number of used ports == number of channels +static jack_client_t *client; +static float jack_latency; +static int estimate; +static volatile int paused = 0; ///< set if paused +static volatile int underrun = 0; ///< signals if an underrun occured + +static volatile float callback_interval = 0; +static volatile float callback_time = 0; + +//! size of one chunk, if this is too small MPlayer will start to "stutter" +//! after a short time of playback +#define CHUNK_SIZE (16 * 1024) +//! number of "virtual" chunks the buffer consists of +#define NUM_CHUNKS 8 +#define BUFFSIZE (NUM_CHUNKS * CHUNK_SIZE) + +//! buffer for audio data +static AVFifoBuffer *buffer; + +/** + * \brief insert len bytes into buffer + * \param data data to insert + * \param len length of data + * \return number of bytes inserted into buffer + * + * If there is not enough room, the buffer is filled up + */ +static int write_buffer(unsigned char* data, int len) { + int free = av_fifo_space(buffer); + if (len > free) len = free; + return av_fifo_generic_write(buffer, data, len, NULL); +} + +static void silence(float **bufs, int cnt, int num_bufs); + +struct deinterleave { + float **bufs; + int num_bufs; + int cur_buf; + int pos; +}; + +static void deinterleave(void *info, void *src, int len) { + struct deinterleave *di = info; + float *s = src; + int i; + len /= sizeof(float); + for (i = 0; i < len; i++) { + di->bufs[di->cur_buf++][di->pos] = s[i]; + if (di->cur_buf >= di->num_bufs) { + di->cur_buf = 0; + di->pos++; + } + } +} + +/** + * \brief read data from buffer and splitting it into channels + * \param bufs num_bufs float buffers, each will contain the data of one channel + * \param cnt number of samples to read per channel + * \param num_bufs number of channels to split the data into + * \return number of samples read per channel, equals cnt unless there was too + * little data in the buffer + * + * Assumes the data in the buffer is of type float, the number of bytes + * read is res * num_bufs * sizeof(float), where res is the return value. + * If there is not enough data in the buffer remaining parts will be filled + * with silence. + */ +static int read_buffer(float **bufs, int cnt, int num_bufs) { + struct deinterleave di = {bufs, num_bufs, 0, 0}; + int buffered = av_fifo_size(buffer); + if (cnt * sizeof(float) * num_bufs > buffered) { + silence(bufs, cnt, num_bufs); + cnt = buffered / sizeof(float) / num_bufs; + } + av_fifo_generic_read(buffer, &di, cnt * num_bufs * sizeof(float), deinterleave); + return cnt; +} + +// end ring buffer stuff + +static int control(int cmd, void *arg) { + return CONTROL_UNKNOWN; +} + +/** + * \brief fill the buffers with silence + * \param bufs num_bufs float buffers, each will contain the data of one channel + * \param cnt number of samples in each buffer + * \param num_bufs number of buffers + */ +static void silence(float **bufs, int cnt, int num_bufs) { + int i; + for (i = 0; i < num_bufs; i++) + memset(bufs[i], 0, cnt * sizeof(float)); +} + +/** + * \brief JACK Callback function + * \param nframes number of frames to fill into buffers + * \param arg unused + * \return currently always 0 + * + * Write silence into buffers if paused or an underrun occured + */ +static int outputaudio(jack_nframes_t nframes, void *arg) { + float *bufs[MAX_CHANS]; + int i; + for (i = 0; i < num_ports; i++) + bufs[i] = jack_port_get_buffer(ports[i], nframes); + if (paused || underrun) + silence(bufs, nframes, num_ports); + else + if (read_buffer(bufs, nframes, num_ports) < nframes) + underrun = 1; + if (estimate) { + float now = (float)GetTimer() / 1000000.0; + float diff = callback_time + callback_interval - now; + if ((diff > -0.002) && (diff < 0.002)) + callback_time += callback_interval; + else + callback_time = now; + callback_interval = (float)nframes / (float)ao_data.samplerate; + } + return 0; +} + +/** + * \brief print suboption usage help + */ +static void print_help (void) +{ + mp_msg (MSGT_AO, MSGL_FATAL, + "\n-ao jack commandline help:\n" + "Example: mpv -ao jack:port=myout\n" + " connects mpv to the jack ports named myout\n" + "\nOptions:\n" + " port=<port name>\n" + " Connects to the given ports instead of the default physical ones\n" + " name=<client name>\n" + " Client name to pass to JACK\n" + " estimate\n" + " Estimates the amount of data in buffers (experimental)\n" + " autostart\n" + " Automatically start JACK server if necessary\n" + ); +} + +static int init(int rate, int channels, int format, int flags) { + const char **matching_ports = NULL; + char *port_name = NULL; + char *client_name = NULL; + int autostart = 0; + const opt_t subopts[] = { + {"port", OPT_ARG_MSTRZ, &port_name, NULL}, + {"name", OPT_ARG_MSTRZ, &client_name, NULL}, + {"estimate", OPT_ARG_BOOL, &estimate, NULL}, + {"autostart", OPT_ARG_BOOL, &autostart, NULL}, + {NULL} + }; + jack_options_t open_options = JackUseExactName; + int port_flags = JackPortIsInput; + int i; + estimate = 1; + if (subopt_parse(ao_subdevice, subopts) != 0) { + print_help(); + return 0; + } + if (channels > MAX_CHANS) { + mp_msg(MSGT_AO, MSGL_FATAL, "[JACK] Invalid number of channels: %i\n", channels); + goto err_out; + } + if (!client_name) { + client_name = malloc(40); + sprintf(client_name, "mpv [%d]", getpid()); + } + if (!autostart) + open_options |= JackNoStartServer; + client = jack_client_open(client_name, open_options, NULL); + if (!client) { + mp_msg(MSGT_AO, MSGL_FATAL, "[JACK] cannot open server\n"); + goto err_out; + } + buffer = av_fifo_alloc(BUFFSIZE); + jack_set_process_callback(client, outputaudio, 0); + + // list matching ports + if (!port_name) + port_flags |= JackPortIsPhysical; + matching_ports = jack_get_ports(client, port_name, NULL, port_flags); + if (!matching_ports || !matching_ports[0]) { + mp_msg(MSGT_AO, MSGL_FATAL, "[JACK] no physical ports available\n"); + goto err_out; + } + i = 1; + while (matching_ports[i]) i++; + if (channels > i) channels = i; + num_ports = channels; + + // create out output ports + for (i = 0; i < num_ports; i++) { + char pname[30]; + snprintf(pname, 30, "out_%d", i); + ports[i] = jack_port_register(client, pname, JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0); + if (!ports[i]) { + mp_msg(MSGT_AO, MSGL_FATAL, "[JACK] not enough ports available\n"); + goto err_out; + } + } + if (jack_activate(client)) { + mp_msg(MSGT_AO, MSGL_FATAL, "[JACK] activate failed\n"); + goto err_out; + } + for (i = 0; i < num_ports; i++) { + if (jack_connect(client, jack_port_name(ports[i]), matching_ports[i])) { + mp_msg(MSGT_AO, MSGL_FATAL, "[JACK] connecting failed\n"); + goto err_out; + } + } + rate = jack_get_sample_rate(client); + jack_latency = (float)(jack_port_get_total_latency(client, ports[0]) + + jack_get_buffer_size(client)) / (float)rate; + callback_interval = 0; + + ao_data.channels = channels; + ao_data.samplerate = rate; + ao_data.format = AF_FORMAT_FLOAT_NE; + ao_data.bps = channels * rate * sizeof(float); + ao_data.buffersize = CHUNK_SIZE * NUM_CHUNKS; + ao_data.outburst = CHUNK_SIZE; + free(matching_ports); + free(port_name); + free(client_name); + return 1; + +err_out: + free(matching_ports); + free(port_name); + free(client_name); + if (client) + jack_client_close(client); + av_fifo_free(buffer); + buffer = NULL; + return 0; +} + +// close audio device +static void uninit(int immed) { + if (!immed) + usec_sleep(get_delay() * 1000 * 1000); + // HACK, make sure jack doesn't loop-output dirty buffers + reset(); + usec_sleep(100 * 1000); + jack_client_close(client); + av_fifo_free(buffer); + buffer = NULL; +} + +/** + * \brief stop playing and empty buffers (for seeking/pause) + */ +static void reset(void) { + paused = 1; + av_fifo_reset(buffer); + paused = 0; +} + +/** + * \brief stop playing, keep buffers (for pause) + */ +static void audio_pause(void) { + paused = 1; +} + +/** + * \brief resume playing, after audio_pause() + */ +static void audio_resume(void) { + paused = 0; +} + +static int get_space(void) { + return av_fifo_space(buffer); +} + +/** + * \brief write data into buffer and reset underrun flag + */ +static int play(void *data, int len, int flags) { + if (!(flags & AOPLAY_FINAL_CHUNK)) + len -= len % ao_data.outburst; + underrun = 0; + return write_buffer(data, len); +} + +static float get_delay(void) { + int buffered = av_fifo_size(buffer); // could be less + float in_jack = jack_latency; + if (estimate && callback_interval > 0) { + float elapsed = (float)GetTimer() / 1000000.0 - callback_time; + in_jack += callback_interval - elapsed; + if (in_jack < 0) in_jack = 0; + } + return (float)buffered / (float)ao_data.bps + in_jack; +} diff --git a/audio/out/ao_lavc.c b/audio/out/ao_lavc.c new file mode 100644 index 0000000000..ef76db2717 --- /dev/null +++ b/audio/out/ao_lavc.c @@ -0,0 +1,621 @@ +/* + * audio encoding using libavformat + * Copyright (C) 2011 Rudolf Polzer <divVerent@xonotic.org> + * NOTE: this file is partially based on ao_pcm.c by Atmosfear + * + * This file is part of MPlayer. + * + * MPlayer 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. + * + * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <stdio.h> +#include <stdlib.h> + +#include <libavutil/common.h> +#include <libavutil/audioconvert.h> + +#include "config.h" +#include "options.h" +#include "mpcommon.h" +#include "fmt-conversion.h" +#include "libaf/format.h" +#include "libaf/reorder_ch.h" +#include "talloc.h" +#include "audio_out.h" +#include "mp_msg.h" + +#include "encode_lavc.h" + +static const char *sample_padding_signed = "\x00\x00\x00\x00"; +static const char *sample_padding_u8 = "\x80"; +static const char *sample_padding_float = "\x00\x00\x00\x00"; + +struct priv { + uint8_t *buffer; + size_t buffer_size; + AVStream *stream; + int pcmhack; + int aframesize; + int aframecount; + int offset; + int offset_left; + int64_t savepts; + int framecount; + int64_t lastpts; + int sample_size; + const void *sample_padding; + double expected_next_pts; + + AVRational worst_time_base; + int worst_time_base_is_stream; +}; + +// open & setup audio device +static int init(struct ao *ao, char *params) +{ + struct priv *ac = talloc_zero(ao, struct priv); + const enum AVSampleFormat *sampleformat; + AVCodec *codec; + + if (!encode_lavc_available(ao->encode_lavc_ctx)) { + mp_msg(MSGT_ENCODE, MSGL_ERR, + "ao-lavc: the option -o (output file) must be specified\n"); + return -1; + } + + if (ac->stream) { + mp_msg(MSGT_ENCODE, MSGL_ERR, "ao-lavc: rejecting reinitialization\n"); + return -1; + } + + ac->stream = encode_lavc_alloc_stream(ao->encode_lavc_ctx, + AVMEDIA_TYPE_AUDIO); + + if (!ac->stream) { + mp_msg(MSGT_ENCODE, MSGL_ERR, "ao-lavc: could not get a new audio stream\n"); + return -1; + } + + codec = encode_lavc_get_codec(ao->encode_lavc_ctx, ac->stream); + + // ac->stream->time_base.num = 1; + // ac->stream->time_base.den = ao->samplerate; + // doing this breaks mpeg2ts in ffmpeg + // which doesn't properly force the time base to be 90000 + // furthermore, ffmpeg.c doesn't do this either and works + + ac->stream->codec->time_base.num = 1; + ac->stream->codec->time_base.den = ao->samplerate; + + ac->stream->codec->sample_rate = ao->samplerate; + ac->stream->codec->channels = ao->channels; + + ac->stream->codec->sample_fmt = AV_SAMPLE_FMT_NONE; + + { + // first check if the selected format is somewhere in the list of + // supported formats by the codec + for (sampleformat = codec->sample_fmts; + sampleformat && *sampleformat != AV_SAMPLE_FMT_NONE; + ++sampleformat) { + switch (*sampleformat) { + case AV_SAMPLE_FMT_U8: + if (ao->format == AF_FORMAT_U8) + goto out_search; + break; + case AV_SAMPLE_FMT_S16: + if (ao->format == AF_FORMAT_S16_BE) + goto out_search; + if (ao->format == AF_FORMAT_S16_LE) + goto out_search; + break; + case AV_SAMPLE_FMT_S32: + if (ao->format == AF_FORMAT_S32_BE) + goto out_search; + if (ao->format == AF_FORMAT_S32_LE) + goto out_search; + break; + case AV_SAMPLE_FMT_FLT: + if (ao->format == AF_FORMAT_FLOAT_BE) + goto out_search; + if (ao->format == AF_FORMAT_FLOAT_LE) + goto out_search; + break; + default: + break; + } + } +out_search: + ; + } + + if (!sampleformat || *sampleformat == AV_SAMPLE_FMT_NONE) { + // if the selected format is not supported, we have to pick the first + // one we CAN support + // note: not needing to select endianness here, as the switch() below + // does that anyway for us + for (sampleformat = codec->sample_fmts; + sampleformat && *sampleformat != AV_SAMPLE_FMT_NONE; + ++sampleformat) { + switch (*sampleformat) { + case AV_SAMPLE_FMT_U8: + ao->format = AF_FORMAT_U8; + goto out_takefirst; + case AV_SAMPLE_FMT_S16: + ao->format = AF_FORMAT_S16_NE; + goto out_takefirst; + case AV_SAMPLE_FMT_S32: + ao->format = AF_FORMAT_S32_NE; + goto out_takefirst; + case AV_SAMPLE_FMT_FLT: + ao->format = AF_FORMAT_FLOAT_NE; + goto out_takefirst; + default: + break; + } + } +out_takefirst: + ; + } + + switch (ao->format) { + // now that we have chosen a format, set up the fields for it, boldly + // switching endianness if needed (mplayer code will convert for us + // anyway, but ffmpeg always expects native endianness) + case AF_FORMAT_U8: + ac->stream->codec->sample_fmt = AV_SAMPLE_FMT_U8; + ac->sample_size = 1; + ac->sample_padding = sample_padding_u8; + ao->format = AF_FORMAT_U8; + break; + default: + case AF_FORMAT_S16_BE: + case AF_FORMAT_S16_LE: + ac->stream->codec->sample_fmt = AV_SAMPLE_FMT_S16; + ac->sample_size = 2; + ac->sample_padding = sample_padding_signed; + ao->format = AF_FORMAT_S16_NE; + break; + case AF_FORMAT_S32_BE: + case AF_FORMAT_S32_LE: + ac->stream->codec->sample_fmt = AV_SAMPLE_FMT_S32; + ac->sample_size = 4; + ac->sample_padding = sample_padding_signed; + ao->format = AF_FORMAT_S32_NE; + break; + case AF_FORMAT_FLOAT_BE: + case AF_FORMAT_FLOAT_LE: + ac->stream->codec->sample_fmt = AV_SAMPLE_FMT_FLT; + ac->sample_size = 4; + ac->sample_padding = sample_padding_float; + ao->format = AF_FORMAT_FLOAT_NE; + break; + } + + ac->stream->codec->bits_per_raw_sample = ac->sample_size * 8; + + switch (ao->channels) { + case 1: + ac->stream->codec->channel_layout = AV_CH_LAYOUT_MONO; + break; + case 2: + ac->stream->codec->channel_layout = AV_CH_LAYOUT_STEREO; + break; + /* someone please check if these are what mplayer normally assumes + case 3: + ac->stream->codec->channel_layout = AV_CH_LAYOUT_SURROUND; + break; + case 4: + ac->stream->codec->channel_layout = AV_CH_LAYOUT_2_2; + break; + */ + case 5: + ac->stream->codec->channel_layout = AV_CH_LAYOUT_5POINT0; + break; + case 6: + ac->stream->codec->channel_layout = AV_CH_LAYOUT_5POINT1; + break; + case 8: + ac->stream->codec->channel_layout = AV_CH_LAYOUT_7POINT1; + break; + default: + mp_msg(MSGT_ENCODE, MSGL_ERR, + "ao-lavc: unknown channel layout; hoping for the best\n"); + break; + } + + if (encode_lavc_open_codec(ao->encode_lavc_ctx, ac->stream) < 0) + return -1; + + ac->pcmhack = 0; + if (ac->stream->codec->frame_size <= 1) + ac->pcmhack = av_get_bits_per_sample(ac->stream->codec->codec_id) / 8; + + if (ac->pcmhack) { + ac->aframesize = 16384; // "enough" + ac->buffer_size = ac->aframesize * ac->pcmhack * ao->channels * 2 + 200; + } else { + ac->aframesize = ac->stream->codec->frame_size; + ac->buffer_size = ac->aframesize * ac->sample_size * ao->channels * 2 + + 200; + } + if (ac->buffer_size < FF_MIN_BUFFER_SIZE) + ac->buffer_size = FF_MIN_BUFFER_SIZE; + ac->buffer = talloc_size(ac, ac->buffer_size); + + // enough frames for at least 0.25 seconds + ac->framecount = ceil(ao->samplerate * 0.25 / ac->aframesize); + // but at least one! + ac->framecount = FFMAX(ac->framecount, 1); + + ac->savepts = MP_NOPTS_VALUE; + ac->lastpts = MP_NOPTS_VALUE; + ac->offset = ac->stream->codec->sample_rate * + encode_lavc_getoffset(ao->encode_lavc_ctx, ac->stream); + ac->offset_left = ac->offset; + + //fill_ao_data: + ao->outburst = ac->aframesize * ac->sample_size * ao->channels * + ac->framecount; + ao->buffersize = ao->outburst * 2; + ao->bps = ao->channels * ao->samplerate * ac->sample_size; + ao->untimed = true; + ao->priv = ac; + + return 0; +} + +static void fill_with_padding(void *buf, int cnt, int sz, const void *padding) +{ + int i; + if (sz == 1) { + memset(buf, cnt, *(char *)padding); + return; + } + for (i = 0; i < cnt; ++i) + memcpy((char *) buf + i * sz, padding, sz); +} + +// close audio device +static int encode(struct ao *ao, double apts, void *data); +static void uninit(struct ao *ao, bool cut_audio) +{ + struct priv *ac = ao->priv; + struct encode_lavc_context *ectx = ao->encode_lavc_ctx; + + if (!encode_lavc_start(ectx)) { + mp_msg(MSGT_ENCODE, MSGL_WARN, + "ao-lavc: not even ready to encode audio at end -> dropped"); + return; + } + + if (ac->buffer) { + double pts = ao->pts + ac->offset / (double) ao->samplerate; + if (ao->buffer.len > 0) { + void *paddingbuf = talloc_size(ao, + ac->aframesize * ao->channels * ac->sample_size); + memcpy(paddingbuf, ao->buffer.start, ao->buffer.len); + fill_with_padding((char *) paddingbuf + ao->buffer.len, + (ac->aframesize * ao->channels * ac->sample_size + - ao->buffer.len) / ac->sample_size, + ac->sample_size, ac->sample_padding); + encode(ao, pts, paddingbuf); + pts += ac->aframesize / (double) ao->samplerate; + talloc_free(paddingbuf); + ao->buffer.len = 0; + } + while (encode(ao, pts, NULL) > 0) ; + } + + ao->priv = NULL; +} + +// return: how many bytes can be played without blocking +static int get_space(struct ao *ao) +{ + return ao->outburst; +} + +// must get exactly ac->aframesize amount of data +static int encode(struct ao *ao, double apts, void *data) +{ + AVFrame *frame; + AVPacket packet; + struct priv *ac = ao->priv; + struct encode_lavc_context *ectx = ao->encode_lavc_ctx; + double realapts = ac->aframecount * (double) ac->aframesize / + ao->samplerate; + int status, gotpacket; + + ac->aframecount++; + if (data && (ao->channels == 5 || ao->channels == 6 || ao->channels == 8)) { + reorder_channel_nch(data, AF_CHANNEL_LAYOUT_MPLAYER_DEFAULT, + AF_CHANNEL_LAYOUT_LAVC_DEFAULT, + ao->channels, + ac->aframesize * ao->channels, ac->sample_size); + } + + if (data) + ectx->audio_pts_offset = realapts - apts; + + av_init_packet(&packet); + packet.data = ac->buffer; + packet.size = ac->buffer_size; + if(data) + { + frame = avcodec_alloc_frame(); + frame->nb_samples = ac->aframesize; + if(avcodec_fill_audio_frame(frame, ao->channels, ac->stream->codec->sample_fmt, data, ac->aframesize * ao->channels * ac->sample_size, 1)) + { + mp_msg(MSGT_ENCODE, MSGL_ERR, "ao-lavc: error filling\n"); + return -1; + } + + if (ectx->options->rawts || ectx->options->copyts) { + // real audio pts + frame->pts = floor(apts * ac->stream->codec->time_base.den / ac->stream->codec->time_base.num + 0.5); + } else { + // audio playback time + frame->pts = floor(realapts * ac->stream->codec->time_base.den / ac->stream->codec->time_base.num + 0.5); + } + + int64_t frame_pts = av_rescale_q(frame->pts, ac->stream->codec->time_base, ac->worst_time_base); + if (ac->lastpts != MP_NOPTS_VALUE && frame_pts <= ac->lastpts) { + // this indicates broken video + // (video pts failing to increase fast enough to match audio) + mp_msg(MSGT_ENCODE, MSGL_WARN, "ao-lavc: audio frame pts went backwards " + "(%d <- %d), autofixed\n", (int)frame->pts, + (int)ac->lastpts); + frame_pts = ac->lastpts + 1; + frame->pts = av_rescale_q(frame_pts, ac->worst_time_base, ac->stream->codec->time_base); + } + ac->lastpts = frame_pts; + + frame->quality = ac->stream->codec->global_quality; + status = avcodec_encode_audio2(ac->stream->codec, &packet, frame, &gotpacket); + + if (!status) { + if (ac->savepts == MP_NOPTS_VALUE) + ac->savepts = frame->pts; + } + + avcodec_free_frame(&frame); + } + else + { + status = avcodec_encode_audio2(ac->stream->codec, &packet, NULL, &gotpacket); + } + + if(status) + { + mp_msg(MSGT_ENCODE, MSGL_ERR, "ao-lavc: error encoding\n"); + return -1; + } + + if(!gotpacket) + return 0; + + mp_msg(MSGT_ENCODE, MSGL_DBG2, + "ao-lavc: got pts %f (playback time: %f); out size: %d\n", + apts, realapts, packet.size); + + encode_lavc_write_stats(ao->encode_lavc_ctx, ac->stream); + + packet.stream_index = ac->stream->index; + + // Do we need this at all? Better be safe than sorry... + if (packet.pts == AV_NOPTS_VALUE) { + mp_msg(MSGT_ENCODE, MSGL_WARN, "ao-lavc: encoder lost pts, why?\n"); + if (ac->savepts != MP_NOPTS_VALUE) + packet.pts = ac->savepts; + } + + if (packet.pts != AV_NOPTS_VALUE) + packet.pts = av_rescale_q(packet.pts, ac->stream->codec->time_base, + ac->stream->time_base); + + if (packet.dts != AV_NOPTS_VALUE) + packet.dts = av_rescale_q(packet.dts, ac->stream->codec->time_base, + ac->stream->time_base); + + if(packet.duration > 0) + packet.duration = av_rescale_q(packet.duration, ac->stream->codec->time_base, + ac->stream->time_base); + + ac->savepts = MP_NOPTS_VALUE; + + if (encode_lavc_write_frame(ao->encode_lavc_ctx, &packet) < 0) { + mp_msg(MSGT_ENCODE, MSGL_ERR, "ao-lavc: error writing at %f %f/%f\n", + realapts, (double) ac->stream->time_base.num, + (double) ac->stream->time_base.den); + return -1; + } + + return packet.size; +} + +// plays 'len' bytes of 'data' +// it should round it down to outburst*n +// return: number of bytes played +static int play(struct ao *ao, void *data, int len, int flags) +{ + struct priv *ac = ao->priv; + struct encode_lavc_context *ectx = ao->encode_lavc_ctx; + int bufpos = 0; + int64_t ptsoffset; + void *paddingbuf = NULL; + double nextpts; + double pts = ao->pts; + double outpts; + + len /= ac->sample_size * ao->channels; + + if (!encode_lavc_start(ectx)) { + mp_msg(MSGT_ENCODE, MSGL_WARN, + "ao-lavc: not ready yet for encoding audio\n"); + return 0; + } + if (pts == MP_NOPTS_VALUE) { + mp_msg(MSGT_ENCODE, MSGL_WARN, "ao-lavc: frame without pts, please report; synthesizing pts instead\n"); + // synthesize pts from previous expected next pts + pts = ac->expected_next_pts; + } + + if (ac->worst_time_base.den == 0) { + //if (ac->stream->codec->time_base.num / ac->stream->codec->time_base.den >= ac->stream->time_base.num / ac->stream->time_base.den) + if (ac->stream->codec->time_base.num * (double) ac->stream->time_base.den >= + ac->stream->time_base.num * (double) ac->stream->codec->time_base.den) { + mp_msg(MSGT_ENCODE, MSGL_V, "ao-lavc: NOTE: using codec time base " + "(%d/%d) for pts adjustment; the stream base (%d/%d) is " + "not worse.\n", (int)ac->stream->codec->time_base.num, + (int)ac->stream->codec->time_base.den, (int)ac->stream->time_base.num, + (int)ac->stream->time_base.den); + ac->worst_time_base = ac->stream->codec->time_base; + ac->worst_time_base_is_stream = 0; + } else { + mp_msg(MSGT_ENCODE, MSGL_WARN, "ao-lavc: NOTE: not using codec time " + "base (%d/%d) for pts adjustment; the stream base (%d/%d) " + "is worse.\n", (int)ac->stream->codec->time_base.num, + (int)ac->stream->codec->time_base.den, (int)ac->stream->time_base.num, + (int)ac->stream->time_base.den); + ac->worst_time_base = ac->stream->time_base; + ac->worst_time_base_is_stream = 1; + } + + // NOTE: we use the following "axiom" of av_rescale_q: + // if time base A is worse than time base B, then + // av_rescale_q(av_rescale_q(x, A, B), B, A) == x + // this can be proven as long as av_rescale_q rounds to nearest, which + // it currently does + + // av_rescale_q(x, A, B) * B = "round x*A to nearest multiple of B" + // and: + // av_rescale_q(av_rescale_q(x, A, B), B, A) * A + // == "round av_rescale_q(x, A, B)*B to nearest multiple of A" + // == "round 'round x*A to nearest multiple of B' to nearest multiple of A" + // + // assume this fails. Then there is a value of x*A, for which the + // nearest multiple of B is outside the range [(x-0.5)*A, (x+0.5)*A[. + // Absurd, as this range MUST contain at least one multiple of B. + } + + ptsoffset = ac->offset; + // this basically just edits ao->apts for syncing purposes + + if (ectx->options->copyts || ectx->options->rawts) { + // we do not send time sync data to the video side, + // but we always need the exact pts, even if zero + } else { + // here we must "simulate" the pts editing + // 1. if we have to skip stuff, we skip it + // 2. if we have to add samples, we add them + // 3. we must still adjust ptsoffset appropriately for AV sync! + // invariant: + // if no partial skipping is done, the first frame gets ao->apts passed as pts! + + if (ac->offset_left < 0) { + if (ac->offset_left <= -len) { + // skip whole frame + ac->offset_left += len; + return len * ac->sample_size * ao->channels; + } else { + // skip part of this frame, buffer/encode the rest + bufpos -= ac->offset_left; + ptsoffset += ac->offset_left; + ac->offset_left = 0; + } + } else if (ac->offset_left > 0) { + // make a temporary buffer, filled with zeroes at the start + // (don't worry, only happens once) + + paddingbuf = talloc_size(ac, ac->sample_size * ao->channels * + (ac->offset_left + len)); + fill_with_padding(paddingbuf, ac->offset_left, ac->sample_size, + ac->sample_padding); + data = (char *) paddingbuf + ac->sample_size * ao->channels * + ac->offset_left; + bufpos -= ac->offset_left; // yes, negative! + ptsoffset += ac->offset_left; + ac->offset_left = 0; + + // now adjust the bufpos so the final value of bufpos is positive! + /* + int cnt = (len - bufpos) / ac->aframesize; + int finalbufpos = bufpos + cnt * ac->aframesize; + */ + int finalbufpos = len - (len - bufpos) % ac->aframesize; + if (finalbufpos < 0) { + mp_msg(MSGT_ENCODE, MSGL_WARN, "ao-lavc: cannot attain the " + "exact requested audio sync; shifting by %d frames\n", + -finalbufpos); + bufpos -= finalbufpos; + } + } + } + + if (!ectx->options->rawts && ectx->options->copyts) { + // fix the discontinuity pts offset + nextpts = pts + ptsoffset / (double) ao->samplerate; + if (ectx->discontinuity_pts_offset == MP_NOPTS_VALUE) { + ectx->discontinuity_pts_offset = ectx->next_in_pts - nextpts; + } + else if (fabs(nextpts + ectx->discontinuity_pts_offset - ectx->next_in_pts) > 30) { + mp_msg(MSGT_ENCODE, MSGL_WARN, + "ao-lavc: detected an unexpected discontinuity (pts jumped by " + "%f seconds)\n", + nextpts + ectx->discontinuity_pts_offset - ectx->next_in_pts); + ectx->discontinuity_pts_offset = ectx->next_in_pts - nextpts; + } + + outpts = pts + ectx->discontinuity_pts_offset; + } + else + outpts = pts; + + while (len - bufpos >= ac->aframesize) { + encode(ao, + outpts + (bufpos + ptsoffset) / (double) ao->samplerate + encode_lavc_getoffset(ectx, ac->stream), + (char *) data + ac->sample_size * bufpos * ao->channels); + bufpos += ac->aframesize; + } + + talloc_free(paddingbuf); + + // calculate expected pts of next audio frame + ac->expected_next_pts = pts + (bufpos + ptsoffset) / (double) ao->samplerate; + + if (!ectx->options->rawts && ectx->options->copyts) { + // set next allowed output pts value + nextpts = ac->expected_next_pts + ectx->discontinuity_pts_offset; + if (nextpts > ectx->next_in_pts) + ectx->next_in_pts = nextpts; + } + + return bufpos * ac->sample_size * ao->channels; +} + +const struct ao_driver audio_out_lavc = { + .is_new = true, + .info = &(const struct ao_info) { + "audio encoding using libavcodec", + "lavc", + "Rudolf Polzer <divVerent@xonotic.org>", + "" + }, + .init = init, + .uninit = uninit, + .get_space = get_space, + .play = play, +}; diff --git a/audio/out/ao_null.c b/audio/out/ao_null.c new file mode 100644 index 0000000000..87f11a51b6 --- /dev/null +++ b/audio/out/ao_null.c @@ -0,0 +1,129 @@ +/* + * null audio output driver + * + * This file is part of MPlayer. + * + * MPlayer 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. + * + * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <stdio.h> +#include <stdlib.h> + +#include "talloc.h" + +#include "config.h" +#include "osdep/timer.h" +#include "libaf/format.h" +#include "audio_out.h" + +struct priv { + unsigned last_time; + float buffered_bytes; +}; + +static void drain(struct ao *ao) +{ + struct priv *priv = ao->priv; + + unsigned now = GetTimer(); + priv->buffered_bytes -= (now - priv->last_time) / 1e6 * ao->bps; + if (priv->buffered_bytes < 0) + priv->buffered_bytes = 0; + priv->last_time = now; +} + +static int init(struct ao *ao, char *params) +{ + struct priv *priv = talloc_zero(ao, struct priv); + ao->priv = priv; + int samplesize = af_fmt2bits(ao->format) / 8; + ao->outburst = 256 * ao->channels * samplesize; + // A "buffer" for about 0.2 seconds of audio + ao->buffersize = (int)(ao->samplerate * 0.2 / 256 + 1) * ao->outburst; + ao->bps = ao->channels * ao->samplerate * samplesize; + priv->last_time = GetTimer(); + + return 0; +} + +// close audio device +static void uninit(struct ao *ao, bool cut_audio) +{ +} + +// stop playing and empty buffers (for seeking/pause) +static void reset(struct ao *ao) +{ + struct priv *priv = ao->priv; + priv->buffered_bytes = 0; +} + +// stop playing, keep buffers (for pause) +static void pause(struct ao *ao) +{ + // for now, just call reset(); + reset(ao); +} + +// resume playing, after audio_pause() +static void resume(struct ao *ao) +{ +} + +static int get_space(struct ao *ao) +{ + struct priv *priv = ao->priv; + + drain(ao); + return ao->buffersize - priv->buffered_bytes; +} + +static int play(struct ao *ao, void *data, int len, int flags) +{ + struct priv *priv = ao->priv; + + int maxbursts = (ao->buffersize - priv->buffered_bytes) / ao->outburst; + int playbursts = len / ao->outburst; + int bursts = playbursts > maxbursts ? maxbursts : playbursts; + priv->buffered_bytes += bursts * ao->outburst; + return bursts * ao->outburst; +} + +static float get_delay(struct ao *ao) +{ + struct priv *priv = ao->priv; + + drain(ao); + return priv->buffered_bytes / ao->bps; +} + +const struct ao_driver audio_out_null = { + .is_new = true, + .info = &(const struct ao_info) { + "Null audio output", + "null", + "Tobias Diedrich <ranma+mplayer@tdiedrich.de>", + "", + }, + .init = init, + .uninit = uninit, + .reset = reset, + .get_space = get_space, + .play = play, + .get_delay = get_delay, + .pause = pause, + .resume = resume, +}; + diff --git a/audio/out/ao_openal.c b/audio/out/ao_openal.c new file mode 100644 index 0000000000..e5a40a769d --- /dev/null +++ b/audio/out/ao_openal.c @@ -0,0 +1,280 @@ +/* + * OpenAL audio output driver for MPlayer + * + * Copyleft 2006 by Reimar Döffinger (Reimar.Doeffinger@stud.uni-karlsruhe.de) + * + * This file is part of MPlayer. + * + * MPlayer 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. + * + * MPlayer 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 + * along with MPlayer; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "config.h" + +#include <stdlib.h> +#include <stdio.h> +#include <inttypes.h> +#ifdef OPENAL_AL_H +#include <OpenAL/alc.h> +#include <OpenAL/al.h> +#include <OpenAL/alext.h> +#else +#include <AL/alc.h> +#include <AL/al.h> +#include <AL/alext.h> +#endif + +#include "mp_msg.h" + +#include "audio_out.h" +#include "audio_out_internal.h" +#include "libaf/format.h" +#include "osdep/timer.h" +#include "subopt-helper.h" + +static const ao_info_t info = +{ + "OpenAL audio output", + "openal", + "Reimar Döffinger <Reimar.Doeffinger@stud.uni-karlsruhe.de>", + "" +}; + +LIBAO_EXTERN(openal) + +#define MAX_CHANS 8 +#define NUM_BUF 128 +#define CHUNK_SIZE 512 +static ALuint buffers[MAX_CHANS][NUM_BUF]; +static ALuint sources[MAX_CHANS]; + +static int cur_buf[MAX_CHANS]; +static int unqueue_buf[MAX_CHANS]; +static int16_t *tmpbuf; + + +static int control(int cmd, void *arg) { + switch (cmd) { + case AOCONTROL_GET_VOLUME: + case AOCONTROL_SET_VOLUME: { + ALfloat volume; + ao_control_vol_t *vol = (ao_control_vol_t *)arg; + if (cmd == AOCONTROL_SET_VOLUME) { + volume = (vol->left + vol->right) / 200.0; + alListenerf(AL_GAIN, volume); + } + alGetListenerf(AL_GAIN, &volume); + vol->left = vol->right = volume * 100; + return CONTROL_TRUE; + } + } + return CONTROL_UNKNOWN; +} + +/** + * \brief print suboption usage help + */ +static void print_help(void) { + mp_msg(MSGT_AO, MSGL_FATAL, + "\n-ao openal commandline help:\n" + "Example: mpv -ao openal:device=subdevice\n" + "\nOptions:\n" + " device=subdevice\n" + " Audio device OpenAL should use. Devices can be listed\n" + " with -ao openal:device=help\n" + ); +} + +static void list_devices(void) { + if (alcIsExtensionPresent(NULL, "ALC_ENUMERATE_ALL_EXT") != AL_TRUE) { + mp_msg(MSGT_AO, MSGL_FATAL, "Device listing not supported.\n"); + return; + } + const char *list = alcGetString(NULL, ALC_ALL_DEVICES_SPECIFIER); + mp_msg(MSGT_AO, MSGL_FATAL, "OpenAL devices:\n"); + while (list && *list) { + mp_msg(MSGT_AO, MSGL_FATAL, " '%s'\n", list); + list = list + strlen(list) + 1; + } +} + +static int init(int rate, int channels, int format, int flags) { + float position[3] = {0, 0, 0}; + float direction[6] = {0, 0, 1, 0, -1, 0}; + float sppos[MAX_CHANS][3] = { + {-1, 0, 0.5}, {1, 0, 0.5}, + {-1, 0, -1}, {1, 0, -1}, + {0, 0, 1}, {0, 0, 0.1}, + {-1, 0, 0}, {1, 0, 0}, + }; + ALCdevice *dev = NULL; + ALCcontext *ctx = NULL; + ALCint freq = 0; + ALCint attribs[] = {ALC_FREQUENCY, rate, 0, 0}; + int i; + char *device = NULL; + const opt_t subopts[] = { + {"device", OPT_ARG_MSTRZ, &device, NULL}, + {NULL} + }; + global_ao->no_persistent_volume = true; + if (subopt_parse(ao_subdevice, subopts) != 0) { + print_help(); + return 0; + } + if (device && strcmp(device, "help") == 0) { + list_devices(); + goto err_out; + } + if (channels > MAX_CHANS) { + mp_msg(MSGT_AO, MSGL_FATAL, "[OpenAL] Invalid number of channels: %i\n", channels); + goto err_out; + } + dev = alcOpenDevice(device); + if (!dev) { + mp_msg(MSGT_AO, MSGL_FATAL, "[OpenAL] could not open device\n"); + goto err_out; + } + ctx = alcCreateContext(dev, attribs); + alcMakeContextCurrent(ctx); + alListenerfv(AL_POSITION, position); + alListenerfv(AL_ORIENTATION, direction); + alGenSources(channels, sources); + for (i = 0; i < channels; i++) { + cur_buf[i] = 0; + unqueue_buf[i] = 0; + alGenBuffers(NUM_BUF, buffers[i]); + alSourcefv(sources[i], AL_POSITION, sppos[i]); + alSource3f(sources[i], AL_VELOCITY, 0, 0, 0); + } + if (channels == 1) + alSource3f(sources[0], AL_POSITION, 0, 0, 1); + ao_data.channels = channels; + alcGetIntegerv(dev, ALC_FREQUENCY, 1, &freq); + if (alcGetError(dev) == ALC_NO_ERROR && freq) + rate = freq; + ao_data.samplerate = rate; + ao_data.format = AF_FORMAT_S16_NE; + ao_data.bps = channels * rate * 2; + ao_data.buffersize = CHUNK_SIZE * NUM_BUF; + ao_data.outburst = channels * CHUNK_SIZE; + tmpbuf = malloc(CHUNK_SIZE); + free(device); + return 1; + +err_out: + free(device); + return 0; +} + +// close audio device +static void uninit(int immed) { + ALCcontext *ctx = alcGetCurrentContext(); + ALCdevice *dev = alcGetContextsDevice(ctx); + free(tmpbuf); + if (!immed) { + ALint state; + alGetSourcei(sources[0], AL_SOURCE_STATE, &state); + while (state == AL_PLAYING) { + usec_sleep(10000); + alGetSourcei(sources[0], AL_SOURCE_STATE, &state); + } + } + reset(); + alcMakeContextCurrent(NULL); + alcDestroyContext(ctx); + alcCloseDevice(dev); +} + +static void unqueue_buffers(void) { + ALint p; + int s; + for (s = 0; s < ao_data.channels; s++) { + int till_wrap = NUM_BUF - unqueue_buf[s]; + alGetSourcei(sources[s], AL_BUFFERS_PROCESSED, &p); + if (p >= till_wrap) { + alSourceUnqueueBuffers(sources[s], till_wrap, &buffers[s][unqueue_buf[s]]); + unqueue_buf[s] = 0; + p -= till_wrap; + } + if (p) { + alSourceUnqueueBuffers(sources[s], p, &buffers[s][unqueue_buf[s]]); + unqueue_buf[s] += p; + } + } +} + +/** + * \brief stop playing and empty buffers (for seeking/pause) + */ +static void reset(void) { + alSourceStopv(ao_data.channels, sources); + unqueue_buffers(); +} + +/** + * \brief stop playing, keep buffers (for pause) + */ +static void audio_pause(void) { + alSourcePausev(ao_data.channels, sources); +} + +/** + * \brief resume playing, after audio_pause() + */ +static void audio_resume(void) { + alSourcePlayv(ao_data.channels, sources); +} + +static int get_space(void) { + ALint queued; + unqueue_buffers(); + alGetSourcei(sources[0], AL_BUFFERS_QUEUED, &queued); + queued = NUM_BUF - queued - 3; + if (queued < 0) return 0; + return queued * CHUNK_SIZE * ao_data.channels; +} + +/** + * \brief write data into buffer and reset underrun flag + */ +static int play(void *data, int len, int flags) { + ALint state; + int i, j, k; + int ch; + int16_t *d = data; + len /= ao_data.channels * CHUNK_SIZE; + for (i = 0; i < len; i++) { + for (ch = 0; ch < ao_data.channels; ch++) { + for (j = 0, k = ch; j < CHUNK_SIZE / 2; j++, k += ao_data.channels) + tmpbuf[j] = d[k]; + alBufferData(buffers[ch][cur_buf[ch]], AL_FORMAT_MONO16, tmpbuf, + CHUNK_SIZE, ao_data.samplerate); + alSourceQueueBuffers(sources[ch], 1, &buffers[ch][cur_buf[ch]]); + cur_buf[ch] = (cur_buf[ch] + 1) % NUM_BUF; + } + d += ao_data.channels * CHUNK_SIZE / 2; + } + alGetSourcei(sources[0], AL_SOURCE_STATE, &state); + if (state != AL_PLAYING) // checked here in case of an underrun + alSourcePlayv(ao_data.channels, sources); + return len * ao_data.channels * CHUNK_SIZE; +} + +static float get_delay(void) { + ALint queued; + unqueue_buffers(); + alGetSourcei(sources[0], AL_BUFFERS_QUEUED, &queued); + return queued * CHUNK_SIZE / 2 / (float)ao_data.samplerate; +} diff --git a/audio/out/ao_oss.c b/audio/out/ao_oss.c new file mode 100644 index 0000000000..9d4dde4837 --- /dev/null +++ b/audio/out/ao_oss.c @@ -0,0 +1,560 @@ +/* + * OSS audio output driver + * + * This file is part of MPlayer. + * + * MPlayer 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. + * + * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <stdio.h> +#include <stdlib.h> + +#include <sys/ioctl.h> +#include <unistd.h> +#include <sys/time.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <errno.h> +#include <string.h> + +#include "config.h" +#include "mp_msg.h" +#include "mixer.h" + +#ifdef HAVE_SYS_SOUNDCARD_H +#include <sys/soundcard.h> +#else +#ifdef HAVE_SOUNDCARD_H +#include <soundcard.h> +#endif +#endif + +#include "libaf/format.h" + +#include "audio_out.h" +#include "audio_out_internal.h" + +static const ao_info_t info = +{ + "OSS/ioctl audio output", + "oss", + "A'rpi", + "" +}; + +/* Support for >2 output channels added 2001-11-25 - Steve Davies <steve@daviesfam.org> */ + +LIBAO_EXTERN(oss) + +static int format2oss(int format) +{ + switch(format) + { + case AF_FORMAT_U8: return AFMT_U8; + case AF_FORMAT_S8: return AFMT_S8; + case AF_FORMAT_U16_LE: return AFMT_U16_LE; + case AF_FORMAT_U16_BE: return AFMT_U16_BE; + case AF_FORMAT_S16_LE: return AFMT_S16_LE; + case AF_FORMAT_S16_BE: return AFMT_S16_BE; +#ifdef AFMT_S24_PACKED + case AF_FORMAT_S24_LE: return AFMT_S24_PACKED; +#endif +#ifdef AFMT_U32_LE + case AF_FORMAT_U32_LE: return AFMT_U32_LE; +#endif +#ifdef AFMT_U32_BE + case AF_FORMAT_U32_BE: return AFMT_U32_BE; +#endif +#ifdef AFMT_S32_LE + case AF_FORMAT_S32_LE: return AFMT_S32_LE; +#endif +#ifdef AFMT_S32_BE + case AF_FORMAT_S32_BE: return AFMT_S32_BE; +#endif +#ifdef AFMT_FLOAT + case AF_FORMAT_FLOAT_NE: return AFMT_FLOAT; +#endif + // SPECIALS + case AF_FORMAT_MU_LAW: return AFMT_MU_LAW; + case AF_FORMAT_A_LAW: return AFMT_A_LAW; + case AF_FORMAT_IMA_ADPCM: return AFMT_IMA_ADPCM; +#ifdef AFMT_MPEG + case AF_FORMAT_MPEG2: return AFMT_MPEG; +#endif +#ifdef AFMT_AC3 + case AF_FORMAT_AC3_NE: return AFMT_AC3; +#endif + } + mp_msg(MSGT_AO, MSGL_V, "OSS: Unknown/not supported internal format: %s\n", af_fmt2str_short(format)); + return -1; +} + +static int oss2format(int format) +{ + switch(format) + { + case AFMT_U8: return AF_FORMAT_U8; + case AFMT_S8: return AF_FORMAT_S8; + case AFMT_U16_LE: return AF_FORMAT_U16_LE; + case AFMT_U16_BE: return AF_FORMAT_U16_BE; + case AFMT_S16_LE: return AF_FORMAT_S16_LE; + case AFMT_S16_BE: return AF_FORMAT_S16_BE; +#ifdef AFMT_S24_PACKED + case AFMT_S24_PACKED: return AF_FORMAT_S24_LE; +#endif +#ifdef AFMT_U32_LE + case AFMT_U32_LE: return AF_FORMAT_U32_LE; +#endif +#ifdef AFMT_U32_BE + case AFMT_U32_BE: return AF_FORMAT_U32_BE; +#endif +#ifdef AFMT_S32_LE + case AFMT_S32_LE: return AF_FORMAT_S32_LE; +#endif +#ifdef AFMT_S32_BE + case AFMT_S32_BE: return AF_FORMAT_S32_BE; +#endif +#ifdef AFMT_FLOAT + case AFMT_FLOAT: return AF_FORMAT_FLOAT_NE; +#endif + // SPECIALS + case AFMT_MU_LAW: return AF_FORMAT_MU_LAW; + case AFMT_A_LAW: return AF_FORMAT_A_LAW; + case AFMT_IMA_ADPCM: return AF_FORMAT_IMA_ADPCM; +#ifdef AFMT_MPEG + case AFMT_MPEG: return AF_FORMAT_MPEG2; +#endif +#ifdef AFMT_AC3 + case AFMT_AC3: return AF_FORMAT_AC3_NE; +#endif + } + mp_tmsg(MSGT_GLOBAL,MSGL_ERR,"[AO OSS] Unknown/Unsupported OSS format: %x.\n", format); + return -1; +} + +static char *dsp=PATH_DEV_DSP; +static audio_buf_info zz; +static int audio_fd=-1; +static int prepause_space; + +static const char *oss_mixer_device = PATH_DEV_MIXER; +static int oss_mixer_channel = SOUND_MIXER_PCM; + +#ifdef SNDCTL_DSP_GETPLAYVOL +static int volume_oss4(ao_control_vol_t *vol, int cmd) { + int v; + + if (audio_fd < 0) + return CONTROL_ERROR; + + if (cmd == AOCONTROL_GET_VOLUME) { + if (ioctl(audio_fd, SNDCTL_DSP_GETPLAYVOL, &v) == -1) + return CONTROL_ERROR; + vol->right = (v & 0xff00) >> 8; + vol->left = v & 0x00ff; + return CONTROL_OK; + } else if (cmd == AOCONTROL_SET_VOLUME) { + v = ((int) vol->right << 8) | (int) vol->left; + if (ioctl(audio_fd, SNDCTL_DSP_SETPLAYVOL, &v) == -1) + return CONTROL_ERROR; + return CONTROL_OK; + } else + return CONTROL_UNKNOWN; +} +#endif + +// to set/get/query special features/parameters +static int control(int cmd,void *arg){ + switch(cmd){ + case AOCONTROL_GET_VOLUME: + case AOCONTROL_SET_VOLUME: + { + ao_control_vol_t *vol = (ao_control_vol_t *)arg; + int fd, v, devs; + +#ifdef SNDCTL_DSP_GETPLAYVOL + // Try OSS4 first + if (volume_oss4(vol, cmd) == CONTROL_OK) + return CONTROL_OK; +#endif + + if(AF_FORMAT_IS_AC3(ao_data.format)) + return CONTROL_TRUE; + + if ((fd = open(oss_mixer_device, O_RDONLY)) != -1) + { + ioctl(fd, SOUND_MIXER_READ_DEVMASK, &devs); + if (devs & (1 << oss_mixer_channel)) + { + if (cmd == AOCONTROL_GET_VOLUME) + { + ioctl(fd, MIXER_READ(oss_mixer_channel), &v); + vol->right = (v & 0xFF00) >> 8; + vol->left = v & 0x00FF; + } + else + { + v = ((int)vol->right << 8) | (int)vol->left; + ioctl(fd, MIXER_WRITE(oss_mixer_channel), &v); + } + } + else + { + close(fd); + return CONTROL_ERROR; + } + close(fd); + return CONTROL_OK; + } + } + return CONTROL_ERROR; + } + return CONTROL_UNKNOWN; +} + +// open & setup audio device +// return: 1=success 0=fail +static int init(int rate,int channels,int format,int flags){ + char *mixer_channels [SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_NAMES; + int oss_format; + char *mdev = mixer_device, *mchan = mixer_channel; + + mp_msg(MSGT_AO,MSGL_V,"ao2: %d Hz %d chans %s\n",rate,channels, + af_fmt2str_short(format)); + + if (ao_subdevice) { + char *m,*c; + m = strchr(ao_subdevice,':'); + if(m) { + c = strchr(m+1,':'); + if(c) { + mchan = c+1; + c[0] = '\0'; + } + mdev = m+1; + m[0] = '\0'; + } + dsp = ao_subdevice; + } + + if(mdev) + oss_mixer_device=mdev; + else + oss_mixer_device=PATH_DEV_MIXER; + + if(mchan){ + int fd, devs, i; + + if ((fd = open(oss_mixer_device, O_RDONLY)) == -1){ + mp_tmsg(MSGT_AO,MSGL_ERR,"[AO OSS] audio_setup: Can't open mixer device %s: %s\n", + oss_mixer_device, strerror(errno)); + }else{ + ioctl(fd, SOUND_MIXER_READ_DEVMASK, &devs); + close(fd); + + for (i=0; i<SOUND_MIXER_NRDEVICES; i++){ + if(!strcasecmp(mixer_channels[i], mchan)){ + if(!(devs & (1 << i))){ + mp_tmsg(MSGT_AO,MSGL_ERR,"[AO OSS] audio_setup: Audio card mixer does not have channel '%s', using default.\n",mchan); + i = SOUND_MIXER_NRDEVICES+1; + break; + } + oss_mixer_channel = i; + break; + } + } + if(i==SOUND_MIXER_NRDEVICES){ + mp_tmsg(MSGT_AO,MSGL_ERR,"[AO OSS] audio_setup: Audio card mixer does not have channel '%s', using default.\n",mchan); + } + } + } else + oss_mixer_channel = SOUND_MIXER_PCM; + + mp_msg(MSGT_AO,MSGL_V,"audio_setup: using '%s' dsp device\n", dsp); + mp_msg(MSGT_AO,MSGL_V,"audio_setup: using '%s' mixer device\n", oss_mixer_device); + mp_msg(MSGT_AO,MSGL_V,"audio_setup: using '%s' mixer device\n", mixer_channels[oss_mixer_channel]); + +#ifdef __linux__ + audio_fd=open(dsp, O_WRONLY | O_NONBLOCK); +#else + audio_fd=open(dsp, O_WRONLY); +#endif + if(audio_fd<0){ + mp_tmsg(MSGT_AO,MSGL_ERR,"[AO OSS] audio_setup: Can't open audio device %s: %s\n", dsp, strerror(errno)); + return 0; + } + +#ifdef __linux__ + /* Remove the non-blocking flag */ + if(fcntl(audio_fd, F_SETFL, 0) < 0) { + mp_tmsg(MSGT_AO,MSGL_ERR,"[AO OSS] audio_setup: Can't make file descriptor blocking: %s\n", strerror(errno)); + return 0; + } +#endif + +#if defined(FD_CLOEXEC) && defined(F_SETFD) + fcntl(audio_fd, F_SETFD, FD_CLOEXEC); +#endif + + if(AF_FORMAT_IS_AC3(format)) { + ao_data.samplerate=rate; + ioctl (audio_fd, SNDCTL_DSP_SPEED, &ao_data.samplerate); + } + +ac3_retry: + if (AF_FORMAT_IS_AC3(format)) + format = AF_FORMAT_AC3_NE; + ao_data.format=format; + oss_format=format2oss(format); + if (oss_format == -1) { +#if BYTE_ORDER == BIG_ENDIAN + oss_format=AFMT_S16_BE; +#else + oss_format=AFMT_S16_LE; +#endif + format=AF_FORMAT_S16_NE; + } + if( ioctl(audio_fd, SNDCTL_DSP_SETFMT, &oss_format)<0 || + oss_format != format2oss(format)) { + mp_tmsg(MSGT_AO,MSGL_WARN, "[AO OSS] Can't set audio device %s to %s output, trying %s...\n", dsp, + af_fmt2str_short(format), af_fmt2str_short(AF_FORMAT_S16_NE) ); + format=AF_FORMAT_S16_NE; + goto ac3_retry; + } +#if 0 + if(oss_format!=format2oss(format)) + mp_msg(MSGT_AO,MSGL_WARN,"WARNING! Your soundcard does NOT support %s sample format! Broken audio or bad playback speed are possible! Try with '-af format'\n",audio_out_format_name(format)); +#endif + + ao_data.format = oss2format(oss_format); + if (ao_data.format == -1) return 0; + + mp_msg(MSGT_AO,MSGL_V,"audio_setup: sample format: %s (requested: %s)\n", + af_fmt2str_short(ao_data.format), af_fmt2str_short(format)); + + ao_data.channels = channels; + if(!AF_FORMAT_IS_AC3(format)) { + // We only use SNDCTL_DSP_CHANNELS for >2 channels, in case some drivers don't have it + if (ao_data.channels > 2) { + if ( ioctl(audio_fd, SNDCTL_DSP_CHANNELS, &ao_data.channels) == -1 || + ao_data.channels != channels ) { + mp_tmsg(MSGT_AO,MSGL_ERR,"[AO OSS] audio_setup: Failed to set audio device to %d channels.\n", channels); + return 0; + } + } + else { + int c = ao_data.channels-1; + if (ioctl (audio_fd, SNDCTL_DSP_STEREO, &c) == -1) { + mp_tmsg(MSGT_AO,MSGL_ERR,"[AO OSS] audio_setup: Failed to set audio device to %d channels.\n", ao_data.channels); + return 0; + } + ao_data.channels=c+1; + } + mp_msg(MSGT_AO,MSGL_V,"audio_setup: using %d channels (requested: %d)\n", ao_data.channels, channels); + // set rate + ao_data.samplerate=rate; + ioctl (audio_fd, SNDCTL_DSP_SPEED, &ao_data.samplerate); + mp_msg(MSGT_AO,MSGL_V,"audio_setup: using %d Hz samplerate (requested: %d)\n",ao_data.samplerate,rate); + } + + if(ioctl(audio_fd, SNDCTL_DSP_GETOSPACE, &zz)==-1){ + int r=0; + mp_tmsg(MSGT_AO,MSGL_WARN,"[AO OSS] audio_setup: driver doesn't support SNDCTL_DSP_GETOSPACE :-(\n"); + if(ioctl(audio_fd, SNDCTL_DSP_GETBLKSIZE, &r)==-1){ + mp_msg(MSGT_AO,MSGL_V,"audio_setup: %d bytes/frag (config.h)\n",ao_data.outburst); + } else { + ao_data.outburst=r; + mp_msg(MSGT_AO,MSGL_V,"audio_setup: %d bytes/frag (GETBLKSIZE)\n",ao_data.outburst); + } + } else { + mp_msg(MSGT_AO,MSGL_V,"audio_setup: frags: %3d/%d (%d bytes/frag) free: %6d\n", + zz.fragments, zz.fragstotal, zz.fragsize, zz.bytes); + if(ao_data.buffersize==-1) ao_data.buffersize=zz.bytes; + ao_data.outburst=zz.fragsize; + } + + if(ao_data.buffersize==-1){ + // Measuring buffer size: + void* data; + ao_data.buffersize=0; +#ifdef HAVE_AUDIO_SELECT + data=malloc(ao_data.outburst); memset(data,0,ao_data.outburst); + while(ao_data.buffersize<0x40000){ + fd_set rfds; + struct timeval tv; + FD_ZERO(&rfds); FD_SET(audio_fd,&rfds); + tv.tv_sec=0; tv.tv_usec = 0; + if(!select(audio_fd+1, NULL, &rfds, NULL, &tv)) break; + write(audio_fd,data,ao_data.outburst); + ao_data.buffersize+=ao_data.outburst; + } + free(data); + if(ao_data.buffersize==0){ + mp_tmsg(MSGT_AO,MSGL_ERR,"[AO OSS]\n *** Your audio driver DOES NOT support select() ***\n Recompile mpv with #undef HAVE_AUDIO_SELECT in config.h !\n\n"); + return 0; + } +#endif + } + + ao_data.bps=ao_data.channels; + switch (ao_data.format & AF_FORMAT_BITS_MASK) { + case AF_FORMAT_8BIT: + break; + case AF_FORMAT_16BIT: + ao_data.bps*=2; + break; + case AF_FORMAT_24BIT: + ao_data.bps*=3; + break; + case AF_FORMAT_32BIT: + ao_data.bps*=4; + break; + } + + ao_data.outburst-=ao_data.outburst % ao_data.bps; // round down + ao_data.bps*=ao_data.samplerate; + + return 1; +} + +// close audio device +static void uninit(int immed){ + if(audio_fd == -1) return; +#ifdef SNDCTL_DSP_SYNC + // to get the buffer played + if (!immed) + ioctl(audio_fd, SNDCTL_DSP_SYNC, NULL); +#endif +#ifdef SNDCTL_DSP_RESET + if (immed) + ioctl(audio_fd, SNDCTL_DSP_RESET, NULL); +#endif + close(audio_fd); + audio_fd = -1; +} + +// stop playing and empty buffers (for seeking/pause) +static void reset(void){ + int oss_format; + uninit(1); + audio_fd=open(dsp, O_WRONLY); + if(audio_fd < 0){ + mp_tmsg(MSGT_AO,MSGL_ERR,"[AO OSS]\nFatal error: *** CANNOT RE-OPEN / RESET AUDIO DEVICE *** %s\n", strerror(errno)); + return; + } + +#if defined(FD_CLOEXEC) && defined(F_SETFD) + fcntl(audio_fd, F_SETFD, FD_CLOEXEC); +#endif + + oss_format = format2oss(ao_data.format); + if(AF_FORMAT_IS_AC3(ao_data.format)) + ioctl (audio_fd, SNDCTL_DSP_SPEED, &ao_data.samplerate); + ioctl (audio_fd, SNDCTL_DSP_SETFMT, &oss_format); + if(!AF_FORMAT_IS_AC3(ao_data.format)) { + if (ao_data.channels > 2) + ioctl (audio_fd, SNDCTL_DSP_CHANNELS, &ao_data.channels); + else { + int c = ao_data.channels-1; + ioctl (audio_fd, SNDCTL_DSP_STEREO, &c); + } + ioctl (audio_fd, SNDCTL_DSP_SPEED, &ao_data.samplerate); + } +} + +// stop playing, keep buffers (for pause) +static void audio_pause(void) +{ + prepause_space = get_space(); + uninit(1); +} + +// resume playing, after audio_pause() +static void audio_resume(void) +{ + int fillcnt; + reset(); + fillcnt = get_space() - prepause_space; + if (fillcnt > 0 && !(ao_data.format & AF_FORMAT_SPECIAL_MASK)) { + void *silence = calloc(fillcnt, 1); + play(silence, fillcnt, 0); + free(silence); + } +} + + +// return: how many bytes can be played without blocking +static int get_space(void){ + int playsize=ao_data.outburst; + +#ifdef SNDCTL_DSP_GETOSPACE + if(ioctl(audio_fd, SNDCTL_DSP_GETOSPACE, &zz)!=-1){ + // calculate exact buffer space: + playsize = zz.fragments*zz.fragsize; + return playsize; + } +#endif + + // check buffer +#ifdef HAVE_AUDIO_SELECT + { fd_set rfds; + struct timeval tv; + FD_ZERO(&rfds); + FD_SET(audio_fd, &rfds); + tv.tv_sec = 0; + tv.tv_usec = 0; + if(!select(audio_fd+1, NULL, &rfds, NULL, &tv)) return 0; // not block! + } +#endif + + return ao_data.outburst; +} + +// plays 'len' bytes of 'data' +// it should round it down to outburst*n +// return: number of bytes played +static int play(void* data,int len,int flags){ + if(len==0) + return len; + if(len>ao_data.outburst || !(flags & AOPLAY_FINAL_CHUNK)) { + len/=ao_data.outburst; + len*=ao_data.outburst; + } + len=write(audio_fd,data,len); + return len; +} + +static int audio_delay_method=2; + +// return: delay in seconds between first and last sample in buffer +static float get_delay(void){ + /* Calculate how many bytes/second is sent out */ + if(audio_delay_method==2){ +#ifdef SNDCTL_DSP_GETODELAY + int r=0; + if(ioctl(audio_fd, SNDCTL_DSP_GETODELAY, &r)!=-1) + return ((float)r)/(float)ao_data.bps; +#endif + audio_delay_method=1; // fallback if not supported + } + if(audio_delay_method==1){ + // SNDCTL_DSP_GETOSPACE + if(ioctl(audio_fd, SNDCTL_DSP_GETOSPACE, &zz)!=-1) + return ((float)(ao_data.buffersize-zz.bytes))/(float)ao_data.bps; + audio_delay_method=0; // fallback if not supported + } + return ((float)ao_data.buffersize)/(float)ao_data.bps; +} diff --git a/audio/out/ao_pcm.c b/audio/out/ao_pcm.c new file mode 100644 index 0000000000..0b1c527e89 --- /dev/null +++ b/audio/out/ao_pcm.c @@ -0,0 +1,256 @@ +/* + * PCM audio output driver + * + * This file is part of MPlayer. + * + * MPlayer 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. + * + * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <libavutil/common.h> + +#include "talloc.h" + +#include "subopt-helper.h" +#include "libaf/format.h" +#include "libaf/reorder_ch.h" +#include "audio_out.h" +#include "mp_msg.h" + +#ifdef __MINGW32__ +// for GetFileType to detect pipes +#include <windows.h> +#include <io.h> +#endif + +struct priv { + char *outputfilename; + int waveheader; + uint64_t data_length; + FILE *fp; +}; + +#define WAV_ID_RIFF 0x46464952 /* "RIFF" */ +#define WAV_ID_WAVE 0x45564157 /* "WAVE" */ +#define WAV_ID_FMT 0x20746d66 /* "fmt " */ +#define WAV_ID_DATA 0x61746164 /* "data" */ +#define WAV_ID_PCM 0x0001 +#define WAV_ID_FLOAT_PCM 0x0003 +#define WAV_ID_FORMAT_EXTENSIBLE 0xfffe + +static void fput16le(uint16_t val, FILE *fp) +{ + uint8_t bytes[2] = {val, val >> 8}; + fwrite(bytes, 1, 2, fp); +} + +static void fput32le(uint32_t val, FILE *fp) +{ + uint8_t bytes[4] = {val, val >> 8, val >> 16, val >> 24}; + fwrite(bytes, 1, 4, fp); +} + +static void write_wave_header(struct ao *ao, FILE *fp, uint64_t data_length) +{ + bool use_waveex = ao->channels >= 5 && ao->channels <= 8; + uint16_t fmt = ao->format == AF_FORMAT_FLOAT_LE ? + WAV_ID_FLOAT_PCM : WAV_ID_PCM; + uint32_t fmt_chunk_size = use_waveex ? 40 : 16; + int bits = af_fmt2bits(ao->format); + + // Master RIFF chunk + fput32le(WAV_ID_RIFF, fp); + // RIFF chunk size: 'WAVE' + 'fmt ' + 4 + fmt_chunk_size + + // data chunk hdr (8) + data length + fput32le(12 + fmt_chunk_size + 8 + data_length, fp); + fput32le(WAV_ID_WAVE, fp); + + // Format chunk + fput32le(WAV_ID_FMT, fp); + fput32le(fmt_chunk_size, fp); + fput16le(use_waveex ? WAV_ID_FORMAT_EXTENSIBLE : fmt, fp); + fput16le(ao->channels, fp); + fput32le(ao->samplerate, fp); + fput32le(ao->bps, fp); + fput16le(ao->channels * (bits / 8), fp); + fput16le(bits, fp); + + if (use_waveex) { + // Extension chunk + fput16le(22, fp); + fput16le(bits, fp); + switch (ao->channels) { + case 5: + fput32le(0x0607, fp); // L R C Lb Rb + break; + case 6: + fput32le(0x060f, fp); // L R C Lb Rb LFE + break; + case 7: + fput32le(0x0727, fp); // L R C Cb Ls Rs LFE + break; + case 8: + fput32le(0x063f, fp); // L R C Lb Rb Ls Rs LFE + break; + } + // 2 bytes format + 14 bytes guid + fput32le(fmt, fp); + fput32le(0x00100000, fp); + fput32le(0xAA000080, fp); + fput32le(0x719B3800, fp); + } + + // Data chunk + fput32le(WAV_ID_DATA, fp); + fput32le(data_length, fp); +} + +static int init(struct ao *ao, char *params) +{ + struct priv *priv = talloc_zero(ao, struct priv); + ao->priv = priv; + + int fast = 0; + const opt_t subopts[] = { + {"waveheader", OPT_ARG_BOOL, &priv->waveheader, NULL}, + {"file", OPT_ARG_MSTRZ, &priv->outputfilename, NULL}, + {"fast", OPT_ARG_BOOL, &fast, NULL}, + {NULL} + }; + // set defaults + priv->waveheader = 1; + + if (subopt_parse(params, subopts) != 0) + return -1; + + if (fast) + mp_msg(MSGT_AO, MSGL_WARN, + "[AO PCM] Suboption \"fast\" is deprecated.\n" + "[AO PCM] Use -novideo, or -benchmark if you want " + "faster playback with video.\n"); + if (!priv->outputfilename) + priv->outputfilename = + strdup(priv->waveheader ? "audiodump.wav" : "audiodump.pcm"); + if (priv->waveheader) { + // WAV files must have one of the following formats + + switch (ao->format) { + case AF_FORMAT_U8: + case AF_FORMAT_S16_LE: + case AF_FORMAT_S24_LE: + case AF_FORMAT_S32_LE: + case AF_FORMAT_FLOAT_LE: + case AF_FORMAT_AC3_BE: + case AF_FORMAT_AC3_LE: + break; + default: + ao->format = AF_FORMAT_S16_LE; + break; + } + } + + ao->outburst = 65536; + ao->bps = ao->channels * ao->samplerate * (af_fmt2bits(ao->format) / 8); + + mp_tmsg(MSGT_AO, MSGL_INFO, "[AO PCM] File: %s (%s)\n" + "PCM: Samplerate: %d Hz Channels: %d Format: %s\n", + priv->outputfilename, + priv->waveheader ? "WAVE" : "RAW PCM", ao->samplerate, + ao->channels, af_fmt2str_short(ao->format)); + mp_tmsg(MSGT_AO, MSGL_INFO, + "[AO PCM] Info: Faster dumping is achieved with -novideo\n" + "[AO PCM] Info: To write WAVE files use -ao pcm:waveheader (default).\n"); + + priv->fp = fopen(priv->outputfilename, "wb"); + if (!priv->fp) { + mp_tmsg(MSGT_AO, MSGL_ERR, "[AO PCM] Failed to open %s for writing!\n", + priv->outputfilename); + return -1; + } + if (priv->waveheader) // Reserve space for wave header + write_wave_header(ao, priv->fp, 0x7ffff000); + ao->untimed = true; + + return 0; +} + +// close audio device +static void uninit(struct ao *ao, bool cut_audio) +{ + struct priv *priv = ao->priv; + + if (priv->waveheader) { // Rewrite wave header + bool broken_seek = false; +#ifdef __MINGW32__ + // Windows, in its usual idiocy "emulates" seeks on pipes so it always + // looks like they work. So we have to detect them brute-force. + broken_seek = FILE_TYPE_DISK != + GetFileType((HANDLE)_get_osfhandle(_fileno(priv->fp))); +#endif + if (broken_seek || fseek(priv->fp, 0, SEEK_SET) != 0) + mp_msg(MSGT_AO, MSGL_ERR, "Could not seek to start, " + "WAV size headers not updated!\n"); + else { + if (priv->data_length > 0xfffff000) { + mp_msg(MSGT_AO, MSGL_ERR, "File larger than allowed for " + "WAV files, may play truncated!\n"); + priv->data_length = 0xfffff000; + } + write_wave_header(ao, priv->fp, priv->data_length); + } + } + fclose(priv->fp); + free(priv->outputfilename); +} + +static int get_space(struct ao *ao) +{ + return ao->outburst; +} + +static int play(struct ao *ao, void *data, int len, int flags) +{ + struct priv *priv = ao->priv; + + if (ao->channels == 5 || ao->channels == 6 || ao->channels == 8) { + int frame_size = af_fmt2bits(ao->format) / 8; + len -= len % (frame_size * ao->channels); + reorder_channel_nch(data, AF_CHANNEL_LAYOUT_MPLAYER_DEFAULT, + AF_CHANNEL_LAYOUT_WAVEEX_DEFAULT, + ao->channels, len / frame_size, frame_size); + } + fwrite(data, len, 1, priv->fp); + priv->data_length += len; + return len; +} + +const struct ao_driver audio_out_pcm = { + .is_new = true, + .info = &(const struct ao_info) { + "RAW PCM/WAVE file writer audio output", + "pcm", + "Atmosfear", + "", + }, + .init = init, + .uninit = uninit, + .get_space = get_space, + .play = play, +}; diff --git a/audio/out/ao_portaudio.c b/audio/out/ao_portaudio.c new file mode 100644 index 0000000000..36b08f8288 --- /dev/null +++ b/audio/out/ao_portaudio.c @@ -0,0 +1,431 @@ +/* + * This file is part of mplayer2. + * + * mplayer2 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. + * + * mplayer2 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 mplayer2. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <stdlib.h> +#include <stdbool.h> +#include <string.h> +#include <stdlib.h> +#include <assert.h> +#include <pthread.h> + +#include <libavutil/common.h> +#include <portaudio.h> + +#include "config.h" +#include "subopt-helper.h" +#include "libaf/format.h" +#include "mp_msg.h" +#include "audio_out.h" + +struct priv { + PaStream *stream; + int framelen; + + pthread_mutex_t ring_mutex; + + // protected by ring_mutex + unsigned char *ring; + int ring_size; // max size of the ring + int read_pos; // points to first byte that can be read + int read_len; // number of bytes that can be read + double play_time; // time when last packet returned to PA is on speaker + // 0 is N/A (0 is not a valid PA time value) + int play_silence; // play this many bytes of silence, before real data + bool play_remaining;// play what's left in the buffer, then stop stream +}; + +struct format_map { + int mp_format; + PaSampleFormat pa_format; +}; + +static const struct format_map format_maps[] = { + // first entry is the default format + {AF_FORMAT_S16_NE, paInt16}, + {AF_FORMAT_S24_NE, paInt24}, + {AF_FORMAT_S32_NE, paInt32}, + {AF_FORMAT_S8, paInt8}, + {AF_FORMAT_U8, paUInt8}, + {AF_FORMAT_FLOAT_NE, paFloat32}, + {AF_FORMAT_UNKNOWN, 0} +}; + +static void print_help(void) +{ + mp_msg(MSGT_AO, MSGL_FATAL, + "\n-ao portaudio commandline help:\n" + "Example: mpv -ao portaudio:device=subdevice\n" + "\nOptions:\n" + " device=subdevice\n" + " Audio device PortAudio should use. Devices can be listed\n" + " with -ao portaudio:device=help\n" + " The subdevice can be passed as index, or as complete name.\n"); +} + +static bool check_pa_ret(int ret) +{ + if (ret < 0) { + mp_msg(MSGT_AO, MSGL_ERR, "[portaudio] %s\n", + Pa_GetErrorText(ret)); + if (ret == paUnanticipatedHostError) { + const PaHostErrorInfo* hosterr = Pa_GetLastHostErrorInfo(); + mp_msg(MSGT_AO, MSGL_ERR, "[portaudio] Host error: %s\n", + hosterr->errorText); + } + return false; + } + return true; +} + +// Amount of bytes that contain audio of the given duration, aligned to frames. +static int seconds_to_bytes(struct ao *ao, double duration_seconds) +{ + struct priv *priv = ao->priv; + + int bytes = duration_seconds * ao->bps; + if (bytes % priv->framelen) + bytes += priv->framelen - (bytes % priv->framelen); + return bytes; +} + +static int to_int(const char *s, int return_on_error) +{ + char *endptr; + int res = strtol(s, &endptr, 10); + return (s[0] && !endptr[0]) ? res : return_on_error; +} + +static int find_device(struct ao *ao, const char *name) +{ + int help = strcmp(name, "help") == 0; + int count = Pa_GetDeviceCount(); + check_pa_ret(count); + int found = paNoDevice; + int index = to_int(name, -1); + if (help) + mp_msg(MSGT_AO, MSGL_INFO, "PortAudio devices:\n"); + for (int n = 0; n < count; n++) { + const PaDeviceInfo* info = Pa_GetDeviceInfo(n); + if (help) { + if (info->maxOutputChannels < 1) + continue; + mp_msg(MSGT_AO, MSGL_INFO, " %d '%s', %d channels, latency: %.2f " + "ms, sample rate: %.0f\n", n, info->name, + info->maxOutputChannels, + info->defaultHighOutputLatency * 1000, + info->defaultSampleRate); + } + if (strcmp(name, info->name) == 0 || n == index) { + found = n; + break; + } + } + if (found == paNoDevice && !help) + mp_msg(MSGT_AO, MSGL_FATAL, "[portaudio] Device '%s' not found!\n", + name); + return found; +} + +static int ring_write(struct ao *ao, unsigned char *data, int len) +{ + struct priv *priv = ao->priv; + + int free = priv->ring_size - priv->read_len; + int write_pos = (priv->read_pos + priv->read_len) % priv->ring_size; + int write_len = FFMIN(len, free); + int len1 = FFMIN(priv->ring_size - write_pos, write_len); + int len2 = write_len - len1; + + memcpy(priv->ring + write_pos, data, len1); + memcpy(priv->ring, data + len1, len2); + + priv->read_len += write_len; + + return write_len; +} + +static int ring_read(struct ao *ao, unsigned char *data, int len) +{ + struct priv *priv = ao->priv; + + int read_len = FFMIN(len, priv->read_len); + int len1 = FFMIN(priv->ring_size - priv->read_pos, read_len); + int len2 = read_len - len1; + + memcpy(data, priv->ring + priv->read_pos, len1); + memcpy(data + len1, priv->ring, len2); + + priv->read_len -= read_len; + priv->read_pos = (priv->read_pos + read_len) % priv->ring_size; + + return read_len; +} + +static void fill_silence(unsigned char *ptr, int len) +{ + memset(ptr, 0, len); +} + +static int stream_callback(const void *input, + void *output_v, + unsigned long frameCount, + const PaStreamCallbackTimeInfo *timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData) +{ + struct ao *ao = userData; + struct priv *priv = ao->priv; + int res = paContinue; + unsigned char *output = output_v; + int len_bytes = frameCount * priv->framelen; + + pthread_mutex_lock(&priv->ring_mutex); + + // NOTE: PA + ALSA in dmix mode seems to pretend that there is no latency + // (outputBufferDacTime == currentTime) + priv->play_time = timeInfo->outputBufferDacTime + + len_bytes / (float)ao->bps; + + if (priv->play_silence > 0) { + int bytes = FFMIN(priv->play_silence, len_bytes); + fill_silence(output, bytes); + priv->play_silence -= bytes; + len_bytes -= bytes; + output += bytes; + } + int read = ring_read(ao, output, len_bytes); + len_bytes -= read; + output += read; + + if (len_bytes > 0) { + if (priv->play_remaining) { + res = paComplete; + priv->play_remaining = false; + } else { + mp_msg(MSGT_AO, MSGL_ERR, "[portaudio] Buffer underflow!\n"); + } + fill_silence(output, len_bytes); + } + + pthread_mutex_unlock(&priv->ring_mutex); + + return res; +} + +static void uninit(struct ao *ao, bool cut_audio) +{ + struct priv *priv = ao->priv; + + if (priv->stream) { + if (!cut_audio && Pa_IsStreamActive(priv->stream) == 1) { + pthread_mutex_lock(&priv->ring_mutex); + + priv->play_remaining = true; + + pthread_mutex_unlock(&priv->ring_mutex); + + check_pa_ret(Pa_StopStream(priv->stream)); + } + check_pa_ret(Pa_CloseStream(priv->stream)); + } + + pthread_mutex_destroy(&priv->ring_mutex); + Pa_Terminate(); +} + +static int init(struct ao *ao, char *params) +{ + struct priv *priv = talloc_zero(ao, struct priv); + ao->priv = priv; + + if (!check_pa_ret(Pa_Initialize())) + return -1; + + pthread_mutex_init(&priv->ring_mutex, NULL); + + char *device = NULL; + const opt_t subopts[] = { + {"device", OPT_ARG_MSTRZ, &device, NULL}, + {NULL} + }; + if (subopt_parse(params, subopts) != 0) { + print_help(); + goto error_exit; + } + + int pa_device = Pa_GetDefaultOutputDevice(); + if (device) + pa_device = find_device(ao, device); + if (pa_device == paNoDevice) + goto error_exit; + + PaStreamParameters sp = { + .device = pa_device, + .channelCount = ao->channels, + .suggestedLatency + = Pa_GetDeviceInfo(pa_device)->defaultHighOutputLatency, + }; + + const struct format_map *fmt = format_maps; + while (fmt->pa_format) { + if (fmt->mp_format == ao->format) { + PaStreamParameters test = sp; + test.sampleFormat = fmt->pa_format; + if (Pa_IsFormatSupported(NULL, &test, ao->samplerate) == paNoError) + break; + } + fmt++; + } + if (!fmt->pa_format) { + mp_msg(MSGT_AO, MSGL_V, + "[portaudio] Unsupported format, using default.\n"); + fmt = format_maps; + } + + ao->format = fmt->mp_format; + sp.sampleFormat = fmt->pa_format; + priv->framelen = ao->channels * (af_fmt2bits(ao->format) / 8); + ao->bps = ao->samplerate * priv->framelen; + + if (!check_pa_ret(Pa_IsFormatSupported(NULL, &sp, ao->samplerate))) + goto error_exit; + if (!check_pa_ret(Pa_OpenStream(&priv->stream, NULL, &sp, ao->samplerate, + paFramesPerBufferUnspecified, paNoFlag, + stream_callback, ao))) + goto error_exit; + + priv->ring_size = seconds_to_bytes(ao, 0.5); + priv->ring = talloc_zero_size(priv, priv->ring_size); + + free(device); + return 0; + +error_exit: + uninit(ao, true); + free(device); + return -1; +} + +static int play(struct ao *ao, void *data, int len, int flags) +{ + struct priv *priv = ao->priv; + + pthread_mutex_lock(&priv->ring_mutex); + + int write_len = ring_write(ao, data, len); + if (flags & AOPLAY_FINAL_CHUNK) + priv->play_remaining = true; + + pthread_mutex_unlock(&priv->ring_mutex); + + if (Pa_IsStreamStopped(priv->stream) == 1) + check_pa_ret(Pa_StartStream(priv->stream)); + + return write_len; +} + +static int get_space(struct ao *ao) +{ + struct priv *priv = ao->priv; + + pthread_mutex_lock(&priv->ring_mutex); + + int free = priv->ring_size - priv->read_len; + + pthread_mutex_unlock(&priv->ring_mutex); + + return free; +} + +static float get_delay(struct ao *ao) +{ + struct priv *priv = ao->priv; + + double stream_time = Pa_GetStreamTime(priv->stream); + + pthread_mutex_lock(&priv->ring_mutex); + + float frame_time = priv->play_time ? priv->play_time - stream_time : 0; + float buffer_latency = (priv->read_len + priv->play_silence) + / (float)ao->bps; + + pthread_mutex_unlock(&priv->ring_mutex); + + return buffer_latency + frame_time; +} + +static void reset(struct ao *ao) +{ + struct priv *priv = ao->priv; + + if (Pa_IsStreamStopped(priv->stream) != 1) + check_pa_ret(Pa_AbortStream(priv->stream)); + + pthread_mutex_lock(&priv->ring_mutex); + + priv->read_len = 0; + priv->read_pos = 0; + priv->play_remaining = false; + priv->play_time = 0; + priv->play_silence = 0; + + pthread_mutex_unlock(&priv->ring_mutex); +} + +static void pause(struct ao *ao) +{ + struct priv *priv = ao->priv; + + check_pa_ret(Pa_AbortStream(priv->stream)); + + double stream_time = Pa_GetStreamTime(priv->stream); + + pthread_mutex_lock(&priv->ring_mutex); + + // When playback resumes, replace the lost audio (due to dropping the + // portaudio/driver/hardware internal buffers) with silence. + float frame_time = priv->play_time ? priv->play_time - stream_time : 0; + priv->play_silence += seconds_to_bytes(ao, FFMAX(frame_time, 0)); + priv->play_time = 0; + + pthread_mutex_unlock(&priv->ring_mutex); +} + +static void resume(struct ao *ao) +{ + struct priv *priv = ao->priv; + + check_pa_ret(Pa_StartStream(priv->stream)); +} + +const struct ao_driver audio_out_portaudio = { + .is_new = true, + .info = &(const struct ao_info) { + "PortAudio", + "portaudio", + "wm4", + "", + }, + .init = init, + .uninit = uninit, + .reset = reset, + .get_space = get_space, + .play = play, + .get_delay = get_delay, + .pause = pause, + .resume = resume, +}; diff --git a/audio/out/ao_pulse.c b/audio/out/ao_pulse.c new file mode 100644 index 0000000000..1d2ebc5281 --- /dev/null +++ b/audio/out/ao_pulse.c @@ -0,0 +1,554 @@ +/* + * PulseAudio audio output driver. + * Copyright (C) 2006 Lennart Poettering + * Copyright (C) 2007 Reimar Doeffinger + * + * This file is part of MPlayer. + * + * MPlayer 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. + * + * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <stdlib.h> +#include <stdbool.h> +#include <string.h> + +#include <pulse/pulseaudio.h> + +#include "config.h" +#include "libaf/format.h" +#include "mp_msg.h" +#include "audio_out.h" +#include "input/input.h" + +#define PULSE_CLIENT_NAME "mpv" + +#define VOL_PA2MP(v) ((v) * 100 / PA_VOLUME_UI_MAX) +#define VOL_MP2PA(v) ((v) * PA_VOLUME_UI_MAX / 100) + +struct priv { + // PulseAudio playback stream object + struct pa_stream *stream; + + // PulseAudio connection context + struct pa_context *context; + + // Main event loop object + struct pa_threaded_mainloop *mainloop; + + // temporary during control() + struct pa_sink_input_info pi; + + bool broken_pause; + int retval; +}; + +#define GENERIC_ERR_MSG(ctx, str) \ + mp_msg(MSGT_AO, MSGL_ERR, "AO: [pulse] "str": %s\n", \ + pa_strerror(pa_context_errno(ctx))) + +static void context_state_cb(pa_context *c, void *userdata) +{ + struct ao *ao = userdata; + struct priv *priv = ao->priv; + switch (pa_context_get_state(c)) { + case PA_CONTEXT_READY: + case PA_CONTEXT_TERMINATED: + case PA_CONTEXT_FAILED: + pa_threaded_mainloop_signal(priv->mainloop, 0); + break; + } +} + +static void stream_state_cb(pa_stream *s, void *userdata) +{ + struct ao *ao = userdata; + struct priv *priv = ao->priv; + switch (pa_stream_get_state(s)) { + case PA_STREAM_READY: + case PA_STREAM_FAILED: + case PA_STREAM_TERMINATED: + pa_threaded_mainloop_signal(priv->mainloop, 0); + break; + } +} + +static void stream_request_cb(pa_stream *s, size_t length, void *userdata) +{ + struct ao *ao = userdata; + struct priv *priv = ao->priv; + mp_input_wakeup(ao->input_ctx); + pa_threaded_mainloop_signal(priv->mainloop, 0); +} + +static void stream_latency_update_cb(pa_stream *s, void *userdata) +{ + struct ao *ao = userdata; + struct priv *priv = ao->priv; + pa_threaded_mainloop_signal(priv->mainloop, 0); +} + +static void success_cb(pa_stream *s, int success, void *userdata) +{ + struct ao *ao = userdata; + struct priv *priv = ao->priv; + priv->retval = success; + pa_threaded_mainloop_signal(priv->mainloop, 0); +} + +/** + * \brief waits for a pulseaudio operation to finish, frees it and + * unlocks the mainloop + * \param op operation to wait for + * \return 1 if operation has finished normally (DONE state), 0 otherwise + */ +static int waitop(struct priv *priv, pa_operation *op) +{ + if (!op) { + pa_threaded_mainloop_unlock(priv->mainloop); + return 0; + } + pa_operation_state_t state = pa_operation_get_state(op); + while (state == PA_OPERATION_RUNNING) { + pa_threaded_mainloop_wait(priv->mainloop); + state = pa_operation_get_state(op); + } + pa_operation_unref(op); + pa_threaded_mainloop_unlock(priv->mainloop); + return state == PA_OPERATION_DONE; +} + +static const struct format_map { + int mp_format; + pa_sample_format_t pa_format; +} format_maps[] = { + {AF_FORMAT_S16_LE, PA_SAMPLE_S16LE}, + {AF_FORMAT_S16_BE, PA_SAMPLE_S16BE}, + {AF_FORMAT_S32_LE, PA_SAMPLE_S32LE}, + {AF_FORMAT_S32_BE, PA_SAMPLE_S32BE}, + {AF_FORMAT_FLOAT_LE, PA_SAMPLE_FLOAT32LE}, + {AF_FORMAT_FLOAT_BE, PA_SAMPLE_FLOAT32BE}, + {AF_FORMAT_U8, PA_SAMPLE_U8}, + {AF_FORMAT_MU_LAW, PA_SAMPLE_ULAW}, + {AF_FORMAT_A_LAW, PA_SAMPLE_ALAW}, + {AF_FORMAT_UNKNOWN, 0} +}; + +static void uninit(struct ao *ao, bool cut_audio) +{ + struct priv *priv = ao->priv; + if (priv->stream && !cut_audio) { + pa_threaded_mainloop_lock(priv->mainloop); + waitop(priv, pa_stream_drain(priv->stream, success_cb, ao)); + } + + if (priv->mainloop) + pa_threaded_mainloop_stop(priv->mainloop); + + if (priv->stream) { + pa_stream_disconnect(priv->stream); + pa_stream_unref(priv->stream); + priv->stream = NULL; + } + + if (priv->context) { + pa_context_disconnect(priv->context); + pa_context_unref(priv->context); + priv->context = NULL; + } + + if (priv->mainloop) { + pa_threaded_mainloop_free(priv->mainloop); + priv->mainloop = NULL; + } +} + +static int init(struct ao *ao, char *params) +{ + struct pa_sample_spec ss; + struct pa_channel_map map; + char *devarg = NULL; + char *host = NULL; + char *sink = NULL; + const char *version = pa_get_library_version(); + + struct priv *priv = talloc_zero(ao, struct priv); + ao->priv = priv; + + ao->per_application_mixer = true; + + if (params) { + devarg = strdup(params); + sink = strchr(devarg, ':'); + if (sink) + *sink++ = 0; + if (devarg[0]) + host = devarg; + } + + priv->broken_pause = false; + /* not sure which versions are affected, assume 0.9.11* to 0.9.14* + * known bad: 0.9.14, 0.9.13 + * known good: 0.9.9, 0.9.10, 0.9.15 + * To test: pause, wait ca. 5 seconds, framestep and see if MPlayer + * hangs somewhen. */ + if (strncmp(version, "0.9.1", 5) == 0 && version[5] >= '1' + && version[5] <= '4') { + mp_msg(MSGT_AO, MSGL_WARN, + "[pulse] working around probably broken pause functionality,\n" + " see http://www.pulseaudio.org/ticket/440\n"); + priv->broken_pause = true; + } + + ss.channels = ao->channels; + ss.rate = ao->samplerate; + + const struct format_map *fmt_map = format_maps; + while (fmt_map->mp_format != ao->format) { + if (fmt_map->mp_format == AF_FORMAT_UNKNOWN) { + mp_msg(MSGT_AO, MSGL_V, + "AO: [pulse] Unsupported format, using default\n"); + fmt_map = format_maps; + break; + } + fmt_map++; + } + ao->format = fmt_map->mp_format; + ss.format = fmt_map->pa_format; + + if (!pa_sample_spec_valid(&ss)) { + mp_msg(MSGT_AO, MSGL_ERR, "AO: [pulse] Invalid sample spec\n"); + goto fail; + } + + pa_channel_map_init_auto(&map, ss.channels, PA_CHANNEL_MAP_ALSA); + ao->bps = pa_bytes_per_second(&ss); + + if (!(priv->mainloop = pa_threaded_mainloop_new())) { + mp_msg(MSGT_AO, MSGL_ERR, "AO: [pulse] Failed to allocate main loop\n"); + goto fail; + } + + if (!(priv->context = pa_context_new(pa_threaded_mainloop_get_api( + priv->mainloop), PULSE_CLIENT_NAME))) { + mp_msg(MSGT_AO, MSGL_ERR, "AO: [pulse] Failed to allocate context\n"); + goto fail; + } + + pa_context_set_state_callback(priv->context, context_state_cb, ao); + + if (pa_context_connect(priv->context, host, 0, NULL) < 0) + goto fail; + + pa_threaded_mainloop_lock(priv->mainloop); + + if (pa_threaded_mainloop_start(priv->mainloop) < 0) + goto unlock_and_fail; + + /* Wait until the context is ready */ + pa_threaded_mainloop_wait(priv->mainloop); + + if (pa_context_get_state(priv->context) != PA_CONTEXT_READY) + goto unlock_and_fail; + + if (!(priv->stream = pa_stream_new(priv->context, "audio stream", &ss, + &map))) + goto unlock_and_fail; + + pa_stream_set_state_callback(priv->stream, stream_state_cb, ao); + pa_stream_set_write_callback(priv->stream, stream_request_cb, ao); + pa_stream_set_latency_update_callback(priv->stream, + stream_latency_update_cb, ao); + pa_buffer_attr bufattr = { + .maxlength = -1, + .tlength = pa_usec_to_bytes(1000000, &ss), + .prebuf = -1, + .minreq = -1, + .fragsize = -1, + }; + if (pa_stream_connect_playback(priv->stream, sink, &bufattr, + PA_STREAM_NOT_MONOTONIC, NULL, NULL) < 0) + goto unlock_and_fail; + + /* Wait until the stream is ready */ + pa_threaded_mainloop_wait(priv->mainloop); + + if (pa_stream_get_state(priv->stream) != PA_STREAM_READY) + goto unlock_and_fail; + + pa_threaded_mainloop_unlock(priv->mainloop); + + free(devarg); + return 0; + +unlock_and_fail: + + if (priv->mainloop) + pa_threaded_mainloop_unlock(priv->mainloop); + +fail: + if (priv->context) { + if (!(pa_context_errno(priv->context) == PA_ERR_CONNECTIONREFUSED + && ao->probing)) + GENERIC_ERR_MSG(priv->context, "Init failed"); + } + free(devarg); + uninit(ao, true); + return -1; +} + +static void cork(struct ao *ao, bool pause) +{ + struct priv *priv = ao->priv; + pa_threaded_mainloop_lock(priv->mainloop); + priv->retval = 0; + if (!waitop(priv, pa_stream_cork(priv->stream, pause, success_cb, ao)) || + !priv->retval) + GENERIC_ERR_MSG(priv->context, "pa_stream_cork() failed"); +} + +// Play the specified data to the pulseaudio server +static int play(struct ao *ao, void *data, int len, int flags) +{ + struct priv *priv = ao->priv; + pa_threaded_mainloop_lock(priv->mainloop); + if (pa_stream_write(priv->stream, data, len, NULL, 0, + PA_SEEK_RELATIVE) < 0) { + GENERIC_ERR_MSG(priv->context, "pa_stream_write() failed"); + len = -1; + } + if (flags & AOPLAY_FINAL_CHUNK) { + // Force start in case the stream was too short for prebuf + pa_operation *op = pa_stream_trigger(priv->stream, NULL, NULL); + pa_operation_unref(op); + } + pa_threaded_mainloop_unlock(priv->mainloop); + return len; +} + +// Reset the audio stream, i.e. flush the playback buffer on the server side +static void reset(struct ao *ao) +{ + // pa_stream_flush() works badly if not corked + cork(ao, true); + struct priv *priv = ao->priv; + pa_threaded_mainloop_lock(priv->mainloop); + priv->retval = 0; + if (!waitop(priv, pa_stream_flush(priv->stream, success_cb, ao)) || + !priv->retval) + GENERIC_ERR_MSG(priv->context, "pa_stream_flush() failed"); + cork(ao, false); +} + +// Pause the audio stream by corking it on the server +static void pause(struct ao *ao) +{ + cork(ao, true); +} + +// Resume the audio stream by uncorking it on the server +static void resume(struct ao *ao) +{ + struct priv *priv = ao->priv; + /* Without this, certain versions will cause an infinite hang because + * pa_stream_writable_size returns 0 always. + * Note that this workaround causes A-V desync after pause. */ + if (priv->broken_pause) + reset(ao); + cork(ao, false); +} + +// Return number of bytes that may be written to the server without blocking +static int get_space(struct ao *ao) +{ + struct priv *priv = ao->priv; + pa_threaded_mainloop_lock(priv->mainloop); + size_t space = pa_stream_writable_size(priv->stream); + pa_threaded_mainloop_unlock(priv->mainloop); + return space; +} + +// Return the current latency in seconds +static float get_delay(struct ao *ao) +{ + /* This code basically does what pa_stream_get_latency() _should_ + * do, but doesn't due to multiple known bugs in PulseAudio (at + * PulseAudio version 2.1). In particular, the timing interpolation + * mode (PA_STREAM_INTERPOLATE_TIMING) can return completely bogus + * values, and the non-interpolating code has a bug causing too + * large results at end of stream (so a stream never seems to finish). + * This code can still return wrong values in some cases due to known + * PulseAudio bugs that can not be worked around on the client side. + * + * We always query the server for latest timing info. This may take + * too long to work well with remote audio servers, but at least + * this should be enough to fix the normal local playback case. + */ + struct priv *priv = ao->priv; + pa_threaded_mainloop_lock(priv->mainloop); + if (!waitop(priv, pa_stream_update_timing_info(priv->stream, NULL, NULL))) { + GENERIC_ERR_MSG(priv->context, "pa_stream_update_timing_info() failed"); + return 0; + } + pa_threaded_mainloop_lock(priv->mainloop); + const pa_timing_info *ti = pa_stream_get_timing_info(priv->stream); + if (!ti) { + pa_threaded_mainloop_unlock(priv->mainloop); + GENERIC_ERR_MSG(priv->context, "pa_stream_get_timing_info() failed"); + return 0; + } + const struct pa_sample_spec *ss = pa_stream_get_sample_spec(priv->stream); + if (!ss) { + pa_threaded_mainloop_unlock(priv->mainloop); + GENERIC_ERR_MSG(priv->context, "pa_stream_get_sample_spec() failed"); + return 0; + } + // data left in PulseAudio's main buffers (not written to sink yet) + int64_t latency = pa_bytes_to_usec(ti->write_index - ti->read_index, ss); + // since this info may be from a while ago, playback has progressed since + latency -= ti->transport_usec; + // data already moved from buffers to sink, but not played yet + int64_t sink_latency = ti->sink_usec; + if (!ti->playing) + /* At the end of a stream, part of the data "left" in the sink may + * be padding silence after the end; that should be subtracted to + * get the amount of real audio from our stream. This adjustment + * is missing from Pulseaudio's own get_latency calculations + * (as of PulseAudio 2.1). */ + sink_latency -= pa_bytes_to_usec(ti->since_underrun, ss); + if (sink_latency > 0) + latency += sink_latency; + if (latency < 0) + latency = 0; + pa_threaded_mainloop_unlock(priv->mainloop); + return latency / 1e6; +} + +/* A callback function that is called when the + * pa_context_get_sink_input_info() operation completes. Saves the + * volume field of the specified structure to the global variable volume. + */ +static void info_func(struct pa_context *c, const struct pa_sink_input_info *i, + int is_last, void *userdata) +{ + struct ao *ao = userdata; + struct priv *priv = ao->priv; + if (is_last < 0) { + GENERIC_ERR_MSG(priv->context, "Failed to get sink input info"); + return; + } + if (!i) + return; + priv->pi = *i; + pa_threaded_mainloop_signal(priv->mainloop, 0); +} + +static int control(struct ao *ao, enum aocontrol cmd, void *arg) +{ + struct priv *priv = ao->priv; + switch (cmd) { + case AOCONTROL_GET_MUTE: + case AOCONTROL_GET_VOLUME: { + uint32_t devidx = pa_stream_get_index(priv->stream); + pa_threaded_mainloop_lock(priv->mainloop); + if (!waitop(priv, pa_context_get_sink_input_info(priv->context, devidx, + info_func, ao))) { + GENERIC_ERR_MSG(priv->context, + "pa_stream_get_sink_input_info() failed"); + return CONTROL_ERROR; + } + // Warning: some information in pi might be unaccessible, because + // we naively copied the struct, without updating pointers etc. + // Pointers might point to invalid data, accessors might fail. + if (cmd == AOCONTROL_GET_VOLUME) { + ao_control_vol_t *vol = arg; + if (priv->pi.volume.channels != 2) + vol->left = vol->right = + VOL_PA2MP(pa_cvolume_avg(&priv->pi.volume)); + else { + vol->left = VOL_PA2MP(priv->pi.volume.values[0]); + vol->right = VOL_PA2MP(priv->pi.volume.values[1]); + } + } else if (cmd == AOCONTROL_GET_MUTE) { + bool *mute = arg; + *mute = priv->pi.mute; + } + return CONTROL_OK; + } + + case AOCONTROL_SET_MUTE: + case AOCONTROL_SET_VOLUME: { + pa_operation *o; + + pa_threaded_mainloop_lock(priv->mainloop); + uint32_t stream_index = pa_stream_get_index(priv->stream); + if (cmd == AOCONTROL_SET_VOLUME) { + const ao_control_vol_t *vol = arg; + struct pa_cvolume volume; + + pa_cvolume_reset(&volume, ao->channels); + if (volume.channels != 2) + pa_cvolume_set(&volume, volume.channels, VOL_MP2PA(vol->left)); + else { + volume.values[0] = VOL_MP2PA(vol->left); + volume.values[1] = VOL_MP2PA(vol->right); + } + o = pa_context_set_sink_input_volume(priv->context, stream_index, + &volume, NULL, NULL); + if (!o) { + pa_threaded_mainloop_unlock(priv->mainloop); + GENERIC_ERR_MSG(priv->context, + "pa_context_set_sink_input_volume() failed"); + return CONTROL_ERROR; + } + } else if (cmd == AOCONTROL_SET_MUTE) { + const bool *mute = arg; + o = pa_context_set_sink_input_mute(priv->context, stream_index, + *mute, NULL, NULL); + if (!o) { + pa_threaded_mainloop_unlock(priv->mainloop); + GENERIC_ERR_MSG(priv->context, + "pa_context_set_sink_input_mute() failed"); + return CONTROL_ERROR; + } + } else + abort(); + /* We don't wait for completion here */ + pa_operation_unref(o); + pa_threaded_mainloop_unlock(priv->mainloop); + return CONTROL_OK; + } + default: + return CONTROL_UNKNOWN; + } +} + +const struct ao_driver audio_out_pulse = { + .is_new = true, + .info = &(const struct ao_info) { + "PulseAudio audio output", + "pulse", + "Lennart Poettering", + "", + }, + .control = control, + .init = init, + .uninit = uninit, + .reset = reset, + .get_space = get_space, + .play = play, + .get_delay = get_delay, + .pause = pause, + .resume = resume, +}; diff --git a/audio/out/ao_rsound.c b/audio/out/ao_rsound.c new file mode 100644 index 0000000000..8232aad865 --- /dev/null +++ b/audio/out/ao_rsound.c @@ -0,0 +1,214 @@ +/* + * RSound audio output driver + * + * Copyright (C) 2011 Hans-Kristian Arntzen + * + * This file is part of mplayer2. + * + * mplayer2 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. + * + * mplayer2 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 mplayer2; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" + +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <rsound.h> + +#include "talloc.h" + +#include "subopt-helper.h" +#include "osdep/timer.h" +#include "libaf/format.h" +#include "audio_out.h" + +struct priv { + rsound_t *rd; +}; + +static int set_format(struct ao *ao) +{ + int rsd_format; + + switch (ao->format) { + case AF_FORMAT_U8: + rsd_format = RSD_U8; + break; + case AF_FORMAT_S8: + rsd_format = RSD_S8; + break; + case AF_FORMAT_S16_LE: + rsd_format = RSD_S16_LE; + break; + case AF_FORMAT_S16_BE: + rsd_format = RSD_S16_BE; + break; + case AF_FORMAT_U16_LE: + rsd_format = RSD_U16_LE; + break; + case AF_FORMAT_U16_BE: + rsd_format = RSD_U16_BE; + break; + case AF_FORMAT_S24_LE: + case AF_FORMAT_S24_BE: + case AF_FORMAT_U24_LE: + case AF_FORMAT_U24_BE: + rsd_format = RSD_S32_LE; + ao->format = AF_FORMAT_S32_LE; + break; + case AF_FORMAT_S32_LE: + rsd_format = RSD_S32_LE; + break; + case AF_FORMAT_S32_BE: + rsd_format = RSD_S32_BE; + break; + case AF_FORMAT_U32_LE: + rsd_format = RSD_U32_LE; + break; + case AF_FORMAT_U32_BE: + rsd_format = RSD_U32_BE; + break; + case AF_FORMAT_A_LAW: + rsd_format = RSD_ALAW; + break; + case AF_FORMAT_MU_LAW: + rsd_format = RSD_MULAW; + break; + default: + rsd_format = RSD_S16_LE; + ao->format = AF_FORMAT_S16_LE; + } + + return rsd_format; +} + +static int init(struct ao *ao, char *params) +{ + struct priv *priv = talloc_zero(ao, struct priv); + ao->priv = priv; + + char *host = NULL; + char *port = NULL; + + const opt_t subopts[] = { + {"host", OPT_ARG_MSTRZ, &host, NULL}, + {"port", OPT_ARG_MSTRZ, &port, NULL}, + {NULL} + }; + + if (subopt_parse(params, subopts) != 0) + return -1; + + if (rsd_init(&priv->rd) < 0) { + free(host); + free(port); + return -1; + } + + if (host) { + rsd_set_param(priv->rd, RSD_HOST, host); + free(host); + } + + if (port) { + rsd_set_param(priv->rd, RSD_PORT, port); + free(port); + } + + rsd_set_param(priv->rd, RSD_SAMPLERATE, &ao->samplerate); + rsd_set_param(priv->rd, RSD_CHANNELS, &ao->channels); + + int rsd_format = set_format(ao); + rsd_set_param(priv->rd, RSD_FORMAT, &rsd_format); + + if (rsd_start(priv->rd) < 0) { + rsd_free(priv->rd); + return -1; + } + + ao->bps = ao->channels * ao->samplerate * af_fmt2bits(ao->format) / 8; + + return 0; +} + +static void uninit(struct ao *ao, bool cut_audio) +{ + struct priv *priv = ao->priv; + /* The API does not provide a direct way to explicitly wait until + * the last byte has been played server-side as this cannot be + * guaranteed by backend drivers, so we approximate this behavior. + */ + if (!cut_audio) + usec_sleep(rsd_delay_ms(priv->rd) * 1000); + + rsd_stop(priv->rd); + rsd_free(priv->rd); +} + +static void reset(struct ao *ao) +{ + struct priv *priv = ao->priv; + rsd_stop(priv->rd); + rsd_start(priv->rd); +} + +static void audio_pause(struct ao *ao) +{ + struct priv *priv = ao->priv; + rsd_pause(priv->rd, 1); +} + +static void audio_resume(struct ao *ao) +{ + struct priv *priv = ao->priv; + rsd_pause(priv->rd, 0); +} + +static int get_space(struct ao *ao) +{ + struct priv *priv = ao->priv; + return rsd_get_avail(priv->rd); +} + +static int play(struct ao *ao, void *data, int len, int flags) +{ + struct priv *priv = ao->priv; + return rsd_write(priv->rd, data, len); +} + +static float get_delay(struct ao *ao) +{ + struct priv *priv = ao->priv; + return rsd_delay_ms(priv->rd) / 1000.0; +} + +const struct ao_driver audio_out_rsound = { + .is_new = true, + .info = &(const struct ao_info) { + .name = "RSound output driver", + .short_name = "rsound", + .author = "Hans-Kristian Arntzen", + .comment = "", + }, + .init = init, + .uninit = uninit, + .reset = reset, + .get_space = get_space, + .play = play, + .get_delay = get_delay, + .pause = audio_pause, + .resume = audio_resume, +}; + diff --git a/audio/out/audio_out_internal.h b/audio/out/audio_out_internal.h new file mode 100644 index 0000000000..215428fb0e --- /dev/null +++ b/audio/out/audio_out_internal.h @@ -0,0 +1,65 @@ +/* + * This file is part of MPlayer. + * + * MPlayer 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. + * + * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPLAYER_AUDIO_OUT_INTERNAL_H +#define MPLAYER_AUDIO_OUT_INTERNAL_H + +#include "options.h" + +// prototypes: +//static ao_info_t info; +static int control(int cmd, void *arg); +static int init(int rate,int channels,int format,int flags); +static void uninit(int immed); +static void reset(void); +static int get_space(void); +static int play(void* data,int len,int flags); +static float get_delay(void); +static void audio_pause(void); +static void audio_resume(void); + +extern struct ao *global_ao; +#define ao_data (*global_ao) +#define mixer_channel (global_ao->opts->mixer_channel) +#define mixer_device (global_ao->opts->mixer_device) + +#define LIBAO_EXTERN(x) const struct ao_driver audio_out_##x = { \ + .info = &info, \ + .control = old_ao_control, \ + .init = old_ao_init, \ + .uninit = old_ao_uninit, \ + .reset = old_ao_reset, \ + .get_space = old_ao_get_space, \ + .play = old_ao_play, \ + .get_delay = old_ao_get_delay, \ + .pause = old_ao_pause, \ + .resume = old_ao_resume, \ + .old_functions = &(const struct ao_old_functions) { \ + .control = control, \ + .init = init, \ + .uninit = uninit, \ + .reset = reset, \ + .get_space = get_space, \ + .play = play, \ + .get_delay = get_delay, \ + .pause = audio_pause, \ + .resume = audio_resume, \ + }, \ +}; + +#endif /* MPLAYER_AUDIO_OUT_INTERNAL_H */ diff --git a/audio/reorder_ch.c b/audio/reorder_ch.c new file mode 100644 index 0000000000..50379de2c5 --- /dev/null +++ b/audio/reorder_ch.c @@ -0,0 +1,1400 @@ +/* + * common functions for reordering audio channels + * + * Copyright (C) 2007 Ulion <ulion A gmail P com> + * + * This file is part of MPlayer. + * + * MPlayer 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. + * + * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <inttypes.h> +#include <string.h> + +#include "reorder_ch.h" + +#ifdef TEST +#define mp_msg(mod,lev, fmt, args... ) printf( fmt, ## args ) +#else +#include "mp_msg.h" +#endif + + +#define REORDER_COPY_5(DEST,SRC,SAMPLES,S0,S1,S2,S3,S4) \ +for (i = 0; i < SAMPLES; i += 5) {\ + DEST[i] = SRC[i+S0];\ + DEST[i+1] = SRC[i+S1];\ + DEST[i+2] = SRC[i+S2];\ + DEST[i+3] = SRC[i+S3];\ + DEST[i+4] = SRC[i+S4];\ +} + +static int reorder_copy_5ch(void *dest, const void *src, + unsigned int samples, unsigned int samplesize, + int s0, int s1, int s2, int s3, int s4) +{ + int i; + switch (samplesize) { + case 1: + { + int8_t *dest_8 = dest; + const int8_t *src_8 = src; + REORDER_COPY_5(dest_8,src_8,samples,s0,s1,s2,s3,s4); + break; + } + case 2: + { + int16_t *dest_16 = dest; + const int16_t *src_16 = src; + REORDER_COPY_5(dest_16,src_16,samples,s0,s1,s2,s3,s4); + break; + } + case 3: + { + int8_t *dest_8 = dest; + const int8_t *src_8 = src; + for (i = 0; i < samples * 3; i += 15) { + dest_8[i] = src_8[i+s0*3]; + dest_8[i+1] = src_8[i+s0*3+1]; + dest_8[i+2] = src_8[i+s0*3+2]; + dest_8[i+3] = src_8[i+s1*3]; + dest_8[i+4] = src_8[i+s1*3+1]; + dest_8[i+5] = src_8[i+s1*3+2]; + dest_8[i+6] = src_8[i+s2*3]; + dest_8[i+7] = src_8[i+s2*3+1]; + dest_8[i+8] = src_8[i+s2*3+2]; + dest_8[i+9] = src_8[i+s3*3]; + dest_8[i+10] = src_8[i+s3*3+1]; + dest_8[i+11] = src_8[i+s3*3+2]; + dest_8[i+12] = src_8[i+s4*3]; + dest_8[i+13] = src_8[i+s4*3+1]; + dest_8[i+14] = src_8[i+s4*3+2]; + } + break; + } + case 4: + { + int32_t *dest_32 = dest; + const int32_t *src_32 = src; + REORDER_COPY_5(dest_32,src_32,samples,s0,s1,s2,s3,s4); + break; + } + case 8: + { + int64_t *dest_64 = dest; + const int64_t *src_64 = src; + REORDER_COPY_5(dest_64,src_64,samples,s0,s1,s2,s3,s4); + break; + } + default: + mp_msg(MSGT_GLOBAL, MSGL_WARN, + "[reorder_ch] Unsupported sample size: %d, please " + "report this error on the MPlayer mailing list.\n",samplesize); + return 0; + } + return 1; +} + +#define REORDER_COPY_6(DEST,SRC,SAMPLES,S0,S1,S2,S3,S4,S5) \ +for (i = 0; i < SAMPLES; i += 6) {\ + DEST[i] = SRC[i+S0];\ + DEST[i+1] = SRC[i+S1];\ + DEST[i+2] = SRC[i+S2];\ + DEST[i+3] = SRC[i+S3];\ + DEST[i+4] = SRC[i+S4];\ + DEST[i+5] = SRC[i+S5];\ +} + +static int reorder_copy_6ch(void *dest, const void *src, + unsigned int samples, uint8_t samplesize, + int s0, int s1, int s2, int s3, int s4, int s5) +{ + int i; + switch (samplesize) { + case 1: + { + int8_t *dest_8 = dest; + const int8_t *src_8 = src; + REORDER_COPY_6(dest_8,src_8,samples,s0,s1,s2,s3,s4,s5); + break; + } + case 2: + { + int16_t *dest_16 = dest; + const int16_t *src_16 = src; + REORDER_COPY_6(dest_16,src_16,samples,s0,s1,s2,s3,s4,s5); + break; + } + case 3: + { + int8_t *dest_8 = dest; + const int8_t *src_8 = src; + for (i = 0; i < samples * 3; i += 18) { + dest_8[i] = src_8[i+s0*3]; + dest_8[i+1] = src_8[i+s0*3+1]; + dest_8[i+2] = src_8[i+s0*3+2]; + dest_8[i+3] = src_8[i+s1*3]; + dest_8[i+4] = src_8[i+s1*3+1]; + dest_8[i+5] = src_8[i+s1*3+2]; + dest_8[i+6] = src_8[i+s2*3]; + dest_8[i+7] = src_8[i+s2*3+1]; + dest_8[i+8] = src_8[i+s2*3+2]; + dest_8[i+9] = src_8[i+s3*3]; + dest_8[i+10] = src_8[i+s3*3+1]; + dest_8[i+11] = src_8[i+s3*3+2]; + dest_8[i+12] = src_8[i+s4*3]; + dest_8[i+13] = src_8[i+s4*3+1]; + dest_8[i+14] = src_8[i+s4*3+2]; + dest_8[i+15] = src_8[i+s5*3]; + dest_8[i+16] = src_8[i+s5*3+1]; + dest_8[i+17] = src_8[i+s5*3+2]; + } + break; + } + case 4: + { + int32_t *dest_32 = dest; + const int32_t *src_32 = src; + REORDER_COPY_6(dest_32,src_32,samples,s0,s1,s2,s3,s4,s5); + break; + } + case 8: + { + int64_t *dest_64 = dest; + const int64_t *src_64 = src; + REORDER_COPY_6(dest_64,src_64,samples,s0,s1,s2,s3,s4,s5); + break; + } + default: + mp_msg(MSGT_GLOBAL, MSGL_WARN, + "[reorder_ch] Unsupported sample size: %d, please " + "report this error on the MPlayer mailing list.\n",samplesize); + return 0; + } + return 1; +} + +#define REORDER_COPY_8(DEST,SRC,SAMPLES,S0,S1,S2,S3,S4,S5,S6,S7) \ +for (i = 0; i < SAMPLES; i += 8) {\ + DEST[i] = SRC[i+S0];\ + DEST[i+1] = SRC[i+S1];\ + DEST[i+2] = SRC[i+S2];\ + DEST[i+3] = SRC[i+S3];\ + DEST[i+4] = SRC[i+S4];\ + DEST[i+5] = SRC[i+S5];\ + DEST[i+6] = SRC[i+S6];\ + DEST[i+7] = SRC[i+S7];\ +} + +static int reorder_copy_8ch(void *dest, const void *src, + unsigned int samples, uint8_t samplesize, + int s0, int s1, int s2, int s3, + int s4, int s5, int s6, int s7) +{ + int i; + switch (samplesize) { + case 1: + { + int8_t *dest_8 = dest; + const int8_t *src_8 = src; + REORDER_COPY_8(dest_8,src_8,samples,s0,s1,s2,s3,s4,s5,s6,s7); + break; + } + case 2: + { + int16_t *dest_16 = dest; + const int16_t *src_16 = src; + REORDER_COPY_8(dest_16,src_16,samples,s0,s1,s2,s3,s4,s5,s6,s7); + break; + } + case 3: + { + int8_t *dest_8 = dest; + const int8_t *src_8 = src; + for (i = 0; i < samples * 3; i += 24) { + dest_8[i] = src_8[i+s0*3]; + dest_8[i+1] = src_8[i+s0*3+1]; + dest_8[i+2] = src_8[i+s0*3+2]; + dest_8[i+3] = src_8[i+s1*3]; + dest_8[i+4] = src_8[i+s1*3+1]; + dest_8[i+5] = src_8[i+s1*3+2]; + dest_8[i+6] = src_8[i+s2*3]; + dest_8[i+7] = src_8[i+s2*3+1]; + dest_8[i+8] = src_8[i+s2*3+2]; + dest_8[i+9] = src_8[i+s3*3]; + dest_8[i+10] = src_8[i+s3*3+1]; + dest_8[i+11] = src_8[i+s3*3+2]; + dest_8[i+12] = src_8[i+s4*3]; + dest_8[i+13] = src_8[i+s4*3+1]; + dest_8[i+14] = src_8[i+s4*3+2]; + dest_8[i+15] = src_8[i+s5*3]; + dest_8[i+16] = src_8[i+s5*3+1]; + dest_8[i+17] = src_8[i+s5*3+2]; + dest_8[i+18] = src_8[i+s6*3]; + dest_8[i+19] = src_8[i+s6*3+1]; + dest_8[i+20] = src_8[i+s6*3+2]; + dest_8[i+21] = src_8[i+s7*3]; + dest_8[i+22] = src_8[i+s7*3+1]; + dest_8[i+23] = src_8[i+s7*3+2]; + } + break; + } + case 4: + { + int32_t *dest_32 = dest; + const int32_t *src_32 = src; + REORDER_COPY_8(dest_32,src_32,samples,s0,s1,s2,s3,s4,s5,s6,s7); + break; + } + case 8: + { + int64_t *dest_64 = dest; + const int64_t *src_64 = src; + REORDER_COPY_8(dest_64,src_64,samples,s0,s1,s2,s3,s4,s5,s6,s7); + break; + } + default: + mp_msg(MSGT_GLOBAL, MSGL_WARN, + "[reorder_ch] Unsupported sample size: %d, please " + "report this error on the MPlayer mailing list.\n",samplesize); + return 0; + } + return 1; +} + +void reorder_channel_copy(void *src, + int src_layout, + void *dest, + int dest_layout, + int samples, + int samplesize) +{ + if (dest_layout==src_layout) { + memcpy(dest, src, samples*samplesize); + return; + } + if (!AF_IS_SAME_CH_NUM(dest_layout,src_layout)) { + mp_msg(MSGT_GLOBAL, MSGL_WARN, "[reorder_ch] different channel count " + "between src and dest: %x, %x\n", + AF_GET_CH_NUM_WITH_LFE(src_layout), + AF_GET_CH_NUM_WITH_LFE(dest_layout)); + return; + } + switch ((src_layout<<16)|dest_layout) { + // AF_CHANNEL_LAYOUT_5_0_A L R C Ls Rs + // AF_CHANNEL_LAYOUT_5_0_B L R Ls Rs C + // AF_CHANNEL_LAYOUT_5_0_C L C R Ls Rs + // AF_CHANNEL_LAYOUT_5_0_D C L R Ls Rs + case AF_CHANNEL_LAYOUT_5_0_A << 16 | AF_CHANNEL_LAYOUT_5_0_B: + reorder_copy_5ch(dest, src, samples, samplesize, 0, 1, 3, 4, 2); + break; + case AF_CHANNEL_LAYOUT_5_0_A << 16 | AF_CHANNEL_LAYOUT_5_0_C: + reorder_copy_5ch(dest, src, samples, samplesize, 0, 2, 1, 3, 4); + break; + case AF_CHANNEL_LAYOUT_5_0_A << 16 | AF_CHANNEL_LAYOUT_5_0_D: + reorder_copy_5ch(dest, src, samples, samplesize, 2, 0, 1, 3, 4); + break; + case AF_CHANNEL_LAYOUT_5_0_B << 16 | AF_CHANNEL_LAYOUT_5_0_A: + reorder_copy_5ch(dest, src, samples, samplesize, 0, 1, 4, 2, 3); + break; + case AF_CHANNEL_LAYOUT_5_0_B << 16 | AF_CHANNEL_LAYOUT_5_0_C: + reorder_copy_5ch(dest, src, samples, samplesize, 0, 4, 1, 2, 3); + break; + case AF_CHANNEL_LAYOUT_5_0_B << 16 | AF_CHANNEL_LAYOUT_5_0_D: + reorder_copy_5ch(dest, src, samples, samplesize, 4, 0, 1, 2, 3); + break; + case AF_CHANNEL_LAYOUT_5_0_C << 16 | AF_CHANNEL_LAYOUT_5_0_A: + reorder_copy_5ch(dest, src, samples, samplesize, 0, 2, 1, 3, 4); + break; + case AF_CHANNEL_LAYOUT_5_0_C << 16 | AF_CHANNEL_LAYOUT_5_0_B: + reorder_copy_5ch(dest, src, samples, samplesize, 0, 2, 3, 4, 1); + break; + case AF_CHANNEL_LAYOUT_5_0_C << 16 | AF_CHANNEL_LAYOUT_5_0_D: + reorder_copy_5ch(dest, src, samples, samplesize, 1, 0, 2, 3, 4); + break; + case AF_CHANNEL_LAYOUT_5_0_D << 16 | AF_CHANNEL_LAYOUT_5_0_A: + reorder_copy_5ch(dest, src, samples, samplesize, 1, 2, 0, 3, 4); + break; + case AF_CHANNEL_LAYOUT_5_0_D << 16 | AF_CHANNEL_LAYOUT_5_0_B: + reorder_copy_5ch(dest, src, samples, samplesize, 1, 2, 3, 4, 0); + break; + case AF_CHANNEL_LAYOUT_5_0_D << 16 | AF_CHANNEL_LAYOUT_5_0_C: + reorder_copy_5ch(dest, src, samples, samplesize, 1, 0, 2, 3, 4); + break; + // AF_CHANNEL_LAYOUT_5_1_A L R C LFE Ls Rs + // AF_CHANNEL_LAYOUT_5_1_B L R Ls Rs C LFE + // AF_CHANNEL_LAYOUT_5_1_C L C R Ls Rs LFE + // AF_CHANNEL_LAYOUT_5_1_D C L R Ls Rs LFE + // AF_CHANNEL_LAYOUT_5_1_E LFE L C R Ls Rs + case AF_CHANNEL_LAYOUT_5_1_A << 16 | AF_CHANNEL_LAYOUT_5_1_B: + reorder_copy_6ch(dest, src, samples, samplesize, 0, 1, 4, 5, 2, 3); + break; + case AF_CHANNEL_LAYOUT_5_1_A << 16 | AF_CHANNEL_LAYOUT_5_1_C: + reorder_copy_6ch(dest, src, samples, samplesize, 0, 2, 1, 4, 5, 3); + break; + case AF_CHANNEL_LAYOUT_5_1_A << 16 | AF_CHANNEL_LAYOUT_5_1_D: + reorder_copy_6ch(dest, src, samples, samplesize, 2, 0, 1, 4, 5, 3); + break; + case AF_CHANNEL_LAYOUT_5_1_B << 16 | AF_CHANNEL_LAYOUT_5_1_A: + reorder_copy_6ch(dest, src, samples, samplesize, 0, 1, 4, 5, 2, 3); + break; + case AF_CHANNEL_LAYOUT_5_1_B << 16 | AF_CHANNEL_LAYOUT_5_1_C: + reorder_copy_6ch(dest, src, samples, samplesize, 0, 4, 1, 2, 3, 5); + break; + case AF_CHANNEL_LAYOUT_5_1_B << 16 | AF_CHANNEL_LAYOUT_5_1_D: + reorder_copy_6ch(dest, src, samples, samplesize, 4, 0, 1, 2, 3, 5); + break; + case AF_CHANNEL_LAYOUT_5_1_B << 16 | AF_CHANNEL_LAYOUT_5_1_E: + reorder_copy_6ch(dest, src, samples, samplesize, 5, 0, 4, 1, 2, 3); + break; + case AF_CHANNEL_LAYOUT_5_1_C << 16 | AF_CHANNEL_LAYOUT_5_1_A: + reorder_copy_6ch(dest, src, samples, samplesize, 0, 2, 1, 5, 3, 4); + break; + case AF_CHANNEL_LAYOUT_5_1_C << 16 | AF_CHANNEL_LAYOUT_5_1_B: + reorder_copy_6ch(dest, src, samples, samplesize, 0, 2, 3, 4, 1, 5); + break; + case AF_CHANNEL_LAYOUT_5_1_C << 16 | AF_CHANNEL_LAYOUT_5_1_D: + reorder_copy_6ch(dest, src, samples, samplesize, 1, 0, 2, 3, 4, 5); + break; + case AF_CHANNEL_LAYOUT_5_1_D << 16 | AF_CHANNEL_LAYOUT_5_1_A: + reorder_copy_6ch(dest, src, samples, samplesize, 1, 2, 0, 5, 3, 4); + break; + case AF_CHANNEL_LAYOUT_5_1_D << 16 | AF_CHANNEL_LAYOUT_5_1_B: + reorder_copy_6ch(dest, src, samples, samplesize, 1, 2, 3, 4, 0, 5); + break; + case AF_CHANNEL_LAYOUT_5_1_D << 16 | AF_CHANNEL_LAYOUT_5_1_C: + reorder_copy_6ch(dest, src, samples, samplesize, 1, 0, 2, 3, 4, 5); + break; + case AF_CHANNEL_LAYOUT_5_1_E << 16 | AF_CHANNEL_LAYOUT_5_1_B: + reorder_copy_6ch(dest, src, samples, samplesize, 1, 3, 4, 5, 2, 0); + break; + case AF_CHANNEL_LAYOUT_5_1_F << 16 | AF_CHANNEL_LAYOUT_5_1_B: + reorder_copy_6ch(dest, src, samples, samplesize, 1, 2, 4, 5, 0, 3); + break; + // AF_CHANNEL_LAYOUT_7_1_A L R C LFE Ls Rs Rls Rrs + // AF_CHANNEL_LAYOUT_7_1_B L R Ls Rs C LFE Rls Rrs + // AF_CHANNEL_LAYOUT_7_1_D C L R Ls Rs Rls Rrs LFE + case AF_CHANNEL_LAYOUT_7_1_A << 16 | AF_CHANNEL_LAYOUT_7_1_B: + case AF_CHANNEL_LAYOUT_7_1_B << 16 | AF_CHANNEL_LAYOUT_7_1_A: + reorder_copy_8ch(dest, src, samples, samplesize, 0, 1, 4, 5, 2, 3, 6, 7); + break; + case AF_CHANNEL_LAYOUT_7_1_D << 16 | AF_CHANNEL_LAYOUT_7_1_B: + reorder_copy_8ch(dest, src, samples, samplesize, 1, 2, 3, 4, 0, 7, 5, 6); + break; + default: + mp_msg(MSGT_GLOBAL, MSGL_WARN, "[reorder_channel_copy] unsupport " + "from %x to %x, %d * %d\n", src_layout, dest_layout, + samples, samplesize); + memcpy(dest, src, samples*samplesize); + } +} + + +#define REORDER_SELF_SWAP_2(SRC,TMP,SAMPLES,CHNUM,S0,S1) \ +for (i = 0; i < SAMPLES; i += CHNUM) {\ + TMP = SRC[i+S0];\ + SRC[i+S0] = SRC[i+S1];\ + SRC[i+S1] = TMP;\ +} + +static int reorder_self_2(void *src, unsigned int samples, + unsigned int samplesize, unsigned int chnum, + int s0, int s1) +{ + int i; + switch (samplesize) { + case 1: + { + int8_t *src_8 = src; + int8_t tmp; + if (chnum==6) { + REORDER_SELF_SWAP_2(src_8,tmp,samples,6,s0,s1); + } + else if (chnum==8) { + REORDER_SELF_SWAP_2(src_8,tmp,samples,8,s0,s1); + } + else { + REORDER_SELF_SWAP_2(src_8,tmp,samples,5,s0,s1); + } + break; + } + case 2: + { + int16_t *src_16 = src; + int16_t tmp; + if (chnum==6) { + REORDER_SELF_SWAP_2(src_16,tmp,samples,6,s0,s1); + } + else if (chnum==3) { + REORDER_SELF_SWAP_2(src_16,tmp,samples,3,s0,s1); + } + else if (chnum==4) { + REORDER_SELF_SWAP_2(src_16,tmp,samples,3,s0,s1); + } + else { + REORDER_SELF_SWAP_2(src_16,tmp,samples,5,s0,s1); + } + break; + } + case 3: + { + int8_t *src_8 = src; + int8_t tmp0, tmp1, tmp2; + for (i = 0; i < samples * 3; i += chnum * 3) { + tmp0 = src_8[i+s0*3]; + tmp1 = src_8[i+s0*3+1]; + tmp2 = src_8[i+s0*3+2]; + src_8[i+s0*3] = src_8[i+s1*3]; + src_8[i+s0*3+1] = src_8[i+s1*3+1]; + src_8[i+s0*3+2] = src_8[i+s1*3+2]; + src_8[i+s1*3] = tmp0; + src_8[i+s1*3+1] = tmp1; + src_8[i+s1*3+2] = tmp2; + } + break; + } + case 4: + { + int32_t *src_32 = src; + int32_t tmp; + if (chnum==6) { + REORDER_SELF_SWAP_2(src_32,tmp,samples,6,s0,s1); + } + else if (chnum==3) { + REORDER_SELF_SWAP_2(src_32,tmp,samples,3,s0,s1); + } + else if (chnum==4) { + REORDER_SELF_SWAP_2(src_32,tmp,samples,4,s0,s1); + } + else { + REORDER_SELF_SWAP_2(src_32,tmp,samples,5,s0,s1); + } + break; + } + case 8: + { + int64_t *src_64 = src; + int64_t tmp; + if (chnum==6) { + REORDER_SELF_SWAP_2(src_64,tmp,samples,6,s0,s1); + } + else if (chnum==3) { + REORDER_SELF_SWAP_2(src_64,tmp,samples,3,s0,s1); + } + else if (chnum==4) { + REORDER_SELF_SWAP_2(src_64,tmp,samples,4,s0,s1); + } + else { + REORDER_SELF_SWAP_2(src_64,tmp,samples,5,s0,s1); + } + break; + } + default: + mp_msg(MSGT_GLOBAL, MSGL_WARN, + "[reorder_ch] Unsupported sample size: %d, please " + "report this error on the MPlayer mailing list.\n",samplesize); + return 0; + } + return 1; +} + +#define REORDER_SELF_SWAP_3(SRC,TMP,SAMPLES,CHNUM,S0,S1,S2) \ +for (i = 0; i < SAMPLES; i += CHNUM) {\ + TMP = SRC[i+S0];\ + SRC[i+S0] = SRC[i+S1];\ + SRC[i+S1] = SRC[i+S2];\ + SRC[i+S2] = TMP;\ +} + +static int reorder_self_3(void *src, unsigned int samples, + unsigned int samplesize, unsigned int chnum, + int s0, int s1, int s2) +{ + int i; + switch (samplesize) { + case 1: + { + int8_t *src_8 = src; + int8_t tmp; + if (chnum==6) { + REORDER_SELF_SWAP_3(src_8,tmp,samples,6,s0,s1,s2); + } + else { + REORDER_SELF_SWAP_3(src_8,tmp,samples,5,s0,s1,s2); + } + break; + } + case 2: + { + int16_t *src_16 = src; + int16_t tmp; + if (chnum==6) { + REORDER_SELF_SWAP_3(src_16,tmp,samples,6,s0,s1,s2); + } + else { + REORDER_SELF_SWAP_3(src_16,tmp,samples,5,s0,s1,s2); + } + break; + } + case 3: + { + int8_t *src_8 = src; + int8_t tmp0, tmp1, tmp2; + for (i = 0; i < samples * 3; i += chnum * 3) { + tmp0 = src_8[i+s0*3]; + tmp1 = src_8[i+s0*3+1]; + tmp2 = src_8[i+s0*3+2]; + src_8[i+s0*3] = src_8[i+s1*3]; + src_8[i+s0*3+1] = src_8[i+s1*3+1]; + src_8[i+s0*3+2] = src_8[i+s1*3+2]; + src_8[i+s1*3] = src_8[i+s2*3]; + src_8[i+s1*3+1] = src_8[i+s2*3+1]; + src_8[i+s1*3+2] = src_8[i+s2*3+2]; + src_8[i+s2*3] = tmp0; + src_8[i+s2*3+1] = tmp1; + src_8[i+s2*3+2] = tmp2; + } + break; + } + case 4: + { + int32_t *src_32 = src; + int32_t tmp; + if (chnum==6) { + REORDER_SELF_SWAP_3(src_32,tmp,samples,6,s0,s1,s2); + } + else { + REORDER_SELF_SWAP_3(src_32,tmp,samples,5,s0,s1,s2); + } + break; + } + case 8: + { + int64_t *src_64 = src; + int64_t tmp; + if (chnum==6) { + REORDER_SELF_SWAP_3(src_64,tmp,samples,6,s0,s1,s2); + } + else { + REORDER_SELF_SWAP_3(src_64,tmp,samples,5,s0,s1,s2); + } + break; + } + default: + mp_msg(MSGT_GLOBAL, MSGL_WARN, + "[reorder_ch] Unsupported sample size: %d, please " + "report this error on the MPlayer mailing list.\n",samplesize); + return 0; + } + return 1; +} + +#define REORDER_SELF_SWAP_4_STEP_1(SRC,TMP,SAMPLES,CHNUM,S0,S1,S2,S3) \ +for (i = 0; i < SAMPLES; i += CHNUM) {\ + TMP = SRC[i+S0];\ + SRC[i+S0] = SRC[i+S1];\ + SRC[i+S1] = SRC[i+S2];\ + SRC[i+S2] = SRC[i+S3];\ + SRC[i+S3] = TMP;\ +} + +static int reorder_self_4_step_1(void *src, unsigned int samples, + unsigned int samplesize, unsigned int chnum, + int s0, int s1, int s2, int s3) +{ + int i; + switch (samplesize) { + case 1: + { + int8_t *src_8 = src; + int8_t tmp; + if (chnum==6) { + REORDER_SELF_SWAP_4_STEP_1(src_8,tmp,samples,6,s0,s1,s2,s3); + } + else if (chnum==8) { + REORDER_SELF_SWAP_4_STEP_1(src_8,tmp,samples,8,s0,s1,s2,s3); + } + else { + REORDER_SELF_SWAP_4_STEP_1(src_8,tmp,samples,5,s0,s1,s2,s3); + } + break; + } + case 2: + { + int16_t *src_16 = src; + int16_t tmp; + if (chnum==6) { + REORDER_SELF_SWAP_4_STEP_1(src_16,tmp,samples,6,s0,s1,s2,s3); + } + else if (chnum==8) { + REORDER_SELF_SWAP_4_STEP_1(src_16,tmp,samples,8,s0,s1,s2,s3); + } + else { + REORDER_SELF_SWAP_4_STEP_1(src_16,tmp,samples,5,s0,s1,s2,s3); + } + break; + } + case 3: + { + int8_t *src_8 = src; + int8_t tmp0, tmp1, tmp2; + for (i = 0; i < samples * 3; i += chnum * 3) { + tmp0 = src_8[i+s0*3]; + tmp1 = src_8[i+s0*3+1]; + tmp2 = src_8[i+s0*3+2]; + src_8[i+s0*3] = src_8[i+s1*3]; + src_8[i+s0*3+1] = src_8[i+s1*3+1]; + src_8[i+s0*3+2] = src_8[i+s1*3+2]; + src_8[i+s1*3] = src_8[i+s2*3]; + src_8[i+s1*3+1] = src_8[i+s2*3+1]; + src_8[i+s1*3+2] = src_8[i+s2*3+2]; + src_8[i+s2*3] = src_8[i+s3*3]; + src_8[i+s2*3+1] = src_8[i+s3*3+1]; + src_8[i+s2*3+2] = src_8[i+s3*3+2]; + src_8[i+s3*3] = tmp0; + src_8[i+s3*3+1] = tmp1; + src_8[i+s3*3+2] = tmp2; + } + break; + } + case 4: + { + int32_t *src_32 = src; + int32_t tmp; + if (chnum==6) { + REORDER_SELF_SWAP_4_STEP_1(src_32,tmp,samples,6,s0,s1,s2,s3); + } + else if (chnum==8) { + REORDER_SELF_SWAP_4_STEP_1(src_32,tmp,samples,8,s0,s1,s2,s3); + } + else { + REORDER_SELF_SWAP_4_STEP_1(src_32,tmp,samples,5,s0,s1,s2,s3); + } + break; + } + case 8: + { + int64_t *src_64 = src; + int64_t tmp; + if (chnum==6) { + REORDER_SELF_SWAP_4_STEP_1(src_64,tmp,samples,6,s0,s1,s2,s3); + } + else if (chnum==8) { + REORDER_SELF_SWAP_4_STEP_1(src_64,tmp,samples,8,s0,s1,s2,s3); + } + else { + REORDER_SELF_SWAP_4_STEP_1(src_64,tmp,samples,5,s0,s1,s2,s3); + } + break; + } + default: + mp_msg(MSGT_GLOBAL, MSGL_WARN, + "[reorder_ch] Unsupported sample size: %d, please " + "report this error on the MPlayer mailing list.\n",samplesize); + return 0; + } + return 1; +} + +#define REORDER_SELF_SWAP_4_STEP_2(SRC,TMP,SAMPLES,CHNUM,S0,S1,S2,S3) \ +for (i = 0; i < SAMPLES; i += CHNUM) {\ + TMP = SRC[i+S0];\ + SRC[i+S0] = SRC[i+S2];\ + SRC[i+S2] = TMP;\ + TMP = SRC[i+S1];\ + SRC[i+S1] = SRC[i+S3];\ + SRC[i+S3] = TMP;\ +} + +static int reorder_self_4_step_2(void *src, unsigned int samples, + unsigned int samplesize, unsigned int chnum, + int s0, int s1, int s2, int s3) +{ + int i; + switch (samplesize) { + case 3: + { + int8_t *src_8 = src; + int8_t tmp0, tmp1, tmp2; + for (i = 0; i < samples * 3; i += chnum * 3) { + tmp0 = src_8[i+s0*3]; + tmp1 = src_8[i+s0*3+1]; + tmp2 = src_8[i+s0*3+2]; + src_8[i+s0*3] = src_8[i+s2*3]; + src_8[i+s0*3+1] = src_8[i+s2*3+1]; + src_8[i+s0*3+2] = src_8[i+s2*3+2]; + src_8[i+s2*3] = tmp0; + src_8[i+s2*3+1] = tmp1; + src_8[i+s2*3+2] = tmp2; + tmp0 = src_8[i+s1*3]; + tmp1 = src_8[i+s1*3+1]; + tmp2 = src_8[i+s1*3+2]; + src_8[i+s1*3] = src_8[i+s3*3]; + src_8[i+s1*3+1] = src_8[i+s3*3+1]; + src_8[i+s1*3+2] = src_8[i+s3*3+2]; + src_8[i+s3*3] = tmp0; + src_8[i+s3*3+1] = tmp1; + src_8[i+s3*3+2] = tmp2; + } + break; + } + default: + mp_msg(MSGT_GLOBAL, MSGL_WARN, + "[reorder_ch] Unsupported sample size: %d, please " + "report this error on the MPlayer mailing list.\n",samplesize); + return 0; + } + return 1; +} + +#define REORDER_SELF_SWAP_5_STEP_1(SRC,TMP,SAMPLES,CHNUM,S0,S1,S2,S3,S4) \ +for (i = 0; i < SAMPLES; i += CHNUM) {\ + TMP = SRC[i+S0];\ + SRC[i+S0] = SRC[i+S1];\ + SRC[i+S1] = SRC[i+S2];\ + SRC[i+S2] = SRC[i+S3];\ + SRC[i+S3] = SRC[i+S4];\ + SRC[i+S4] = TMP;\ +} + +static int reorder_self_5_step_1(void *src, unsigned int samples, + unsigned int samplesize, unsigned int chnum, + int s0, int s1, int s2, int s3, int s4) +{ + int i; + switch (samplesize) { + case 1: + { + int8_t *src_8 = src; + int8_t tmp; + if (chnum==6) { + REORDER_SELF_SWAP_5_STEP_1(src_8,tmp,samples,6,s0,s1,s2,s3,s4); + } + else if (chnum==8) { + REORDER_SELF_SWAP_5_STEP_1(src_8,tmp,samples,8,s0,s1,s2,s3,s4); + } + else { + REORDER_SELF_SWAP_5_STEP_1(src_8,tmp,samples,5,s0,s1,s2,s3,s4); + } + break; + } + case 2: + { + int16_t *src_16 = src; + int16_t tmp; + if (chnum==6) { + REORDER_SELF_SWAP_5_STEP_1(src_16,tmp,samples,6,s0,s1,s2,s3,s4); + } + else if (chnum==8) { + REORDER_SELF_SWAP_5_STEP_1(src_16,tmp,samples,8,s0,s1,s2,s3,s4); + } + else { + REORDER_SELF_SWAP_5_STEP_1(src_16,tmp,samples,5,s0,s1,s2,s3,s4); + } + break; + } + case 3: + { + int8_t *src_8 = src; + int8_t tmp0, tmp1, tmp2; + for (i = 0; i < samples * 3; i += chnum * 3) { + tmp0 = src_8[i+s0*3]; + tmp1 = src_8[i+s0*3+1]; + tmp2 = src_8[i+s0*3+2]; + src_8[i+s0*3] = src_8[i+s1*3]; + src_8[i+s0*3+1] = src_8[i+s1*3+1]; + src_8[i+s0*3+2] = src_8[i+s1*3+2]; + src_8[i+s1*3] = src_8[i+s2*3]; + src_8[i+s1*3+1] = src_8[i+s2*3+1]; + src_8[i+s1*3+2] = src_8[i+s2*3+2]; + src_8[i+s2*3] = src_8[i+s3*3]; + src_8[i+s2*3+1] = src_8[i+s3*3+1]; + src_8[i+s2*3+2] = src_8[i+s3*3+2]; + src_8[i+s3*3] = src_8[i+s4*3]; + src_8[i+s3*3+1] = src_8[i+s4*3+1]; + src_8[i+s3*3+2] = src_8[i+s4*3+2]; + src_8[i+s4*3] = tmp0; + src_8[i+s4*3+1] = tmp1; + src_8[i+s4*3+2] = tmp2; + } + break; + } + case 4: + { + int32_t *src_32 = src; + int32_t tmp; + if (chnum==6) { + REORDER_SELF_SWAP_5_STEP_1(src_32,tmp,samples,6,s0,s1,s2,s3,s4); + } + else if (chnum==8) { + REORDER_SELF_SWAP_5_STEP_1(src_32,tmp,samples,8,s0,s1,s2,s3,s4); + } + else { + REORDER_SELF_SWAP_5_STEP_1(src_32,tmp,samples,5,s0,s1,s2,s3,s4); + } + break; + } + case 8: + { + int64_t *src_64 = src; + int64_t tmp; + if (chnum==6) { + REORDER_SELF_SWAP_5_STEP_1(src_64,tmp,samples,6,s0,s1,s2,s3,s4); + } + else if (chnum==8) { + REORDER_SELF_SWAP_5_STEP_1(src_64,tmp,samples,8,s0,s1,s2,s3,s4); + } + else { + REORDER_SELF_SWAP_5_STEP_1(src_64,tmp,samples,5,s0,s1,s2,s3,s4); + } + break; + } + default: + mp_msg(MSGT_GLOBAL, MSGL_WARN, + "[reorder_ch] Unsupported sample size: %d, please " + "report this error on the MPlayer mailing list.\n",samplesize); + return 0; + } + return 1; +} + +#define REORDER_SELF_SWAP_2_3(SRC,TMP,SAMPLES,CHNUM,S0,S1,S2,S3,S4) \ +for (i = 0; i < SAMPLES; i += CHNUM) {\ + TMP = SRC[i+S0];\ + SRC[i+S0] = SRC[i+S1];\ + SRC[i+S1] = TMP;\ + TMP = SRC[i+S2];\ + SRC[i+S2] = SRC[i+S3];\ + SRC[i+S3] = SRC[i+S4];\ + SRC[i+S4] = TMP;\ +} + +static int reorder_self_2_3(void *src, unsigned int samples, + unsigned int samplesize, + int s0, int s1, int s2, int s3, int s4) +{ + int i; + switch (samplesize) { + case 1: + { + int8_t *src_8 = src; + int8_t tmp; + REORDER_SELF_SWAP_2_3(src_8,tmp,samples,6,s0,s1,s2,s3,s4); + break; + } + case 2: + { + int16_t *src_16 = src; + int16_t tmp; + REORDER_SELF_SWAP_2_3(src_16,tmp,samples,6,s0,s1,s2,s3,s4); + break; + } + case 3: + { + int8_t *src_8 = src; + int8_t tmp0, tmp1, tmp2; + for (i = 0; i < samples * 3; i += 18) { + tmp0 = src_8[i+s0*3]; + tmp1 = src_8[i+s0*3+1]; + tmp2 = src_8[i+s0*3+2]; + src_8[i+s0*3] = src_8[i+s1*3]; + src_8[i+s0*3+1] = src_8[i+s1*3+1]; + src_8[i+s0*3+2] = src_8[i+s1*3+2]; + src_8[i+s1*3] = tmp0; + src_8[i+s1*3+1] = tmp1; + src_8[i+s1*3+2] = tmp2; + tmp0 = src_8[i+s2*3]; + tmp1 = src_8[i+s2*3+1]; + tmp2 = src_8[i+s2*3+2]; + src_8[i+s2*3] = src_8[i+s3*3]; + src_8[i+s2*3+1] = src_8[i+s3*3+1]; + src_8[i+s2*3+2] = src_8[i+s3*3+2]; + src_8[i+s3*3] = src_8[i+s4*3]; + src_8[i+s3*3+1] = src_8[i+s4*3+1]; + src_8[i+s3*3+2] = src_8[i+s4*3+2]; + src_8[i+s4*3] = tmp0; + src_8[i+s4*3+1] = tmp1; + src_8[i+s4*3+2] = tmp2; + } + break; + } + case 4: + { + int32_t *src_32 = src; + int32_t tmp; + REORDER_SELF_SWAP_2_3(src_32,tmp,samples,6,s0,s1,s2,s3,s4); + break; + } + case 8: + { + int64_t *src_64 = src; + int64_t tmp; + REORDER_SELF_SWAP_2_3(src_64,tmp,samples,6,s0,s1,s2,s3,s4); + break; + } + default: + mp_msg(MSGT_GLOBAL, MSGL_WARN, + "[reorder_ch] Unsupported sample size: %d, please " + "report this error on the MPlayer mailing list.\n",samplesize); + return 0; + } + return 1; +} + +#define REORDER_SELF_SWAP_3_3(SRC,TMP,SAMPLES,CHNUM,S0,S1,S2,S3,S4,S5) \ +for (i = 0; i < SAMPLES; i += CHNUM) {\ + TMP = SRC[i+S0];\ + SRC[i+S0] = SRC[i+S1];\ + SRC[i+S1] = SRC[i+S2];\ + SRC[i+S2] = TMP;\ + TMP = SRC[i+S3];\ + SRC[i+S3] = SRC[i+S4];\ + SRC[i+S4] = SRC[i+S5];\ + SRC[i+S5] = TMP;\ +} + +static int reorder_self_3_3(void *src, unsigned int samples, + unsigned int samplesize, + int s0, int s1, int s2, int s3, int s4, int s5) +{ + int i; + switch (samplesize) { + case 1: + { + int8_t *src_8 = src; + int8_t tmp; + REORDER_SELF_SWAP_3_3(src_8,tmp,samples,6,s0,s1,s2,s3,s4,s5); + break; + } + case 2: + { + int16_t *src_16 = src; + int16_t tmp; + REORDER_SELF_SWAP_3_3(src_16,tmp,samples,6,s0,s1,s2,s3,s4,s5); + break; + } + case 3: + { + int8_t *src_8 = src; + int8_t tmp0, tmp1, tmp2; + for (i = 0; i < samples * 3; i += 18) { + tmp0 = src_8[i+s0*3]; + tmp1 = src_8[i+s0*3+1]; + tmp2 = src_8[i+s0*3+2]; + src_8[i+s0*3] = src_8[i+s1*3]; + src_8[i+s0*3+1] = src_8[i+s1*3+1]; + src_8[i+s0*3+2] = src_8[i+s1*3+2]; + src_8[i+s1*3] = src_8[i+s2*3]; + src_8[i+s1*3+1] = src_8[i+s2*3+1]; + src_8[i+s1*3+2] = src_8[i+s2*3+2]; + src_8[i+s2*3] = tmp0; + src_8[i+s2*3+1] = tmp1; + src_8[i+s2*3+2] = tmp2; + tmp0 = src_8[i+s3*3]; + tmp1 = src_8[i+s3*3+1]; + tmp2 = src_8[i+s3*3+2]; + src_8[i+s3*3] = src_8[i+s4*3]; + src_8[i+s3*3+1] = src_8[i+s4*3+1]; + src_8[i+s3*3+2] = src_8[i+s4*3+2]; + src_8[i+s4*3] = src_8[i+s5*3]; + src_8[i+s4*3+1] = src_8[i+s5*3+1]; + src_8[i+s4*3+2] = src_8[i+s5*3+2]; + src_8[i+s5*3] = tmp0; + src_8[i+s5*3+1] = tmp1; + src_8[i+s5*3+2] = tmp2; + } + break; + } + case 4: + { + int32_t *src_32 = src; + int32_t tmp; + REORDER_SELF_SWAP_3_3(src_32,tmp,samples,6,s0,s1,s2,s3,s4,s5); + break; + } + case 8: + { + int64_t *src_64 = src; + int64_t tmp; + REORDER_SELF_SWAP_3_3(src_64,tmp,samples,6,s0,s1,s2,s3,s4,s5); + break; + } + default: + mp_msg(MSGT_GLOBAL, MSGL_WARN, + "[reorder_ch] Unsupported sample size: %d, please " + "report this error on the MPlayer mailing list.\n",samplesize); + return 0; + } + return 1; +} + +#define REORDER_SELF_SWAP_2_4(SRC,TMP,SAMPLES,CHNUM,S0,S1,S2,S3,S4,S5) \ +for (i = 0; i < SAMPLES; i += CHNUM) {\ + TMP = SRC[i+S0];\ + SRC[i+S0] = SRC[i+S1];\ + SRC[i+S1] = TMP;\ + TMP = SRC[i+S2];\ + SRC[i+S2] = SRC[i+S3];\ + SRC[i+S3] = SRC[i+S4];\ + SRC[i+S4] = SRC[i+S5];\ + SRC[i+S5] = TMP;\ +} + +static int reorder_self_2_4(void *src, unsigned int samples, + unsigned int samplesize, int chnum, + int s0, int s1, int s2, int s3, int s4, int s5) +{ + int i; + switch (samplesize) { + case 1: + { + int8_t *src_8 = src; + int8_t tmp; + if (chnum==6) { + REORDER_SELF_SWAP_2_4(src_8,tmp,samples,6,s0,s1,s2,s3,s4,s5); + } else { + REORDER_SELF_SWAP_2_4(src_8,tmp,samples,8,s0,s1,s2,s3,s4,s5); + } + break; + } + case 2: + { + int16_t *src_16 = src; + int16_t tmp; + if (chnum==6) { + REORDER_SELF_SWAP_2_4(src_16,tmp,samples,6,s0,s1,s2,s3,s4,s5); + } else { + REORDER_SELF_SWAP_2_4(src_16,tmp,samples,8,s0,s1,s2,s3,s4,s5); + } + break; + } + case 3: + { + int8_t *src_8 = src; + int8_t tmp0, tmp1, tmp2; + for (i = 0; i < samples * 3; i += chnum * 3) { + tmp0 = src_8[i+s0*3]; + tmp1 = src_8[i+s0*3+1]; + tmp2 = src_8[i+s0*3+2]; + src_8[i+s0*3] = src_8[i+s1*3]; + src_8[i+s0*3+1] = src_8[i+s1*3+1]; + src_8[i+s0*3+2] = src_8[i+s1*3+2]; + src_8[i+s1*3] = tmp0; + src_8[i+s1*3+1] = tmp1; + src_8[i+s1*3+2] = tmp2; + tmp0 = src_8[i+s2*3]; + tmp1 = src_8[i+s2*3+1]; + tmp2 = src_8[i+s2*3+2]; + src_8[i+s2*3] = src_8[i+s3*3]; + src_8[i+s2*3+1] = src_8[i+s3*3+1]; + src_8[i+s2*3+2] = src_8[i+s3*3+2]; + src_8[i+s3*3] = src_8[i+s4*3]; + src_8[i+s3*3+1] = src_8[i+s4*3+1]; + src_8[i+s3*3+2] = src_8[i+s4*3+2]; + src_8[i+s4*3] = src_8[i+s5*3]; + src_8[i+s4*3+1] = src_8[i+s5*3+1]; + src_8[i+s4*3+2] = src_8[i+s5*3+2]; + src_8[i+s5*3] = tmp0; + src_8[i+s5*3+1] = tmp1; + src_8[i+s5*3+2] = tmp2; + } + break; + } + case 4: + { + int32_t *src_32 = src; + int32_t tmp; + if (chnum==6) { + REORDER_SELF_SWAP_2_4(src_32,tmp,samples,6,s0,s1,s2,s3,s4,s5); + } else { + REORDER_SELF_SWAP_2_4(src_32,tmp,samples,8,s0,s1,s2,s3,s4,s5); + } + break; + } + case 8: + { + int64_t *src_64 = src; + int64_t tmp; + if (chnum==6) { + REORDER_SELF_SWAP_2_4(src_64,tmp,samples,6,s0,s1,s2,s3,s4,s5); + } else { + REORDER_SELF_SWAP_2_4(src_64,tmp,samples,8,s0,s1,s2,s3,s4,s5); + } + break; + } + default: + mp_msg(MSGT_GLOBAL, MSGL_WARN, + "[reorder_ch] Unsupported sample size: %d, please " + "report this error on the MPlayer mailing list.\n",samplesize); + return 0; + } + return 1; +} + +void reorder_channel(void *src, + int src_layout, + int dest_layout, + int samples, + int samplesize) +{ + if (dest_layout==src_layout) + return; + if (!AF_IS_SAME_CH_NUM(dest_layout,src_layout)) { + mp_msg(MSGT_GLOBAL, MSGL_WARN, + "[reorder_channel] different channel count " + "between current and target: %x, %x\n", + AF_GET_CH_NUM_WITH_LFE(src_layout), + AF_GET_CH_NUM_WITH_LFE(dest_layout)); + return; + } + switch ((src_layout<<16)|dest_layout) { + // AF_CHANNEL_LAYOUT_5_0_A L R C Ls Rs + // AF_CHANNEL_LAYOUT_5_0_B L R Ls Rs C + // AF_CHANNEL_LAYOUT_5_0_C L C R Ls Rs + // AF_CHANNEL_LAYOUT_5_0_D C L R Ls Rs + case AF_CHANNEL_LAYOUT_5_0_A << 16 | AF_CHANNEL_LAYOUT_5_0_B: + reorder_self_3(src, samples, samplesize, 5, 2, 3, 4); + break; + case AF_CHANNEL_LAYOUT_5_0_A << 16 | AF_CHANNEL_LAYOUT_5_0_C: + reorder_self_2(src, samples, samplesize, 5, 1, 2); + break; + case AF_CHANNEL_LAYOUT_5_0_A << 16 | AF_CHANNEL_LAYOUT_5_0_D: + reorder_self_3(src, samples, samplesize, 5, 2, 1, 0); + break; + case AF_CHANNEL_LAYOUT_5_0_B << 16 | AF_CHANNEL_LAYOUT_5_0_A: + reorder_self_3(src, samples, samplesize, 5, 4, 3, 2); + break; + case AF_CHANNEL_LAYOUT_5_0_B << 16 | AF_CHANNEL_LAYOUT_5_0_C: + reorder_self_4_step_1(src, samples, samplesize, 5, 4, 3, 2, 1); + break; + case AF_CHANNEL_LAYOUT_5_0_B << 16 | AF_CHANNEL_LAYOUT_5_0_D: + reorder_self_5_step_1(src, samples, samplesize, 5, 4, 3, 2, 1, 0); + break; + case AF_CHANNEL_LAYOUT_5_0_C << 16 | AF_CHANNEL_LAYOUT_5_0_A: + reorder_self_2(src, samples, samplesize, 5, 1, 2); + break; + case AF_CHANNEL_LAYOUT_5_0_C << 16 | AF_CHANNEL_LAYOUT_5_0_B: + reorder_self_4_step_1(src, samples, samplesize, 5, 1, 2, 3, 4); + break; + case AF_CHANNEL_LAYOUT_5_0_C << 16 | AF_CHANNEL_LAYOUT_5_0_D: + reorder_self_2(src, samples, samplesize, 5, 0, 1); + break; + case AF_CHANNEL_LAYOUT_5_0_D << 16 | AF_CHANNEL_LAYOUT_5_0_A: + reorder_self_3(src, samples, samplesize, 5, 0, 1, 2); + break; + case AF_CHANNEL_LAYOUT_5_0_D << 16 | AF_CHANNEL_LAYOUT_5_0_B: + reorder_self_5_step_1(src, samples, samplesize, 5, 0, 1, 2, 3, 4); + break; + case AF_CHANNEL_LAYOUT_5_0_D << 16 | AF_CHANNEL_LAYOUT_5_0_C: + reorder_self_2(src, samples, samplesize, 5, 0, 1); + break; + // AF_CHANNEL_LAYOUT_5_1_A L R C LFE Ls Rs + // AF_CHANNEL_LAYOUT_5_1_B L R Ls Rs C LFE + // AF_CHANNEL_LAYOUT_5_1_C L C R Ls Rs LFE + // AF_CHANNEL_LAYOUT_5_1_D C L R Ls Rs LFE + // AF_CHANNEL_LAYOUT_5_1_E LFE L C R Ls Rs + case AF_CHANNEL_LAYOUT_5_1_A << 16 | AF_CHANNEL_LAYOUT_5_1_B: + if (samplesize != 3) + reorder_self_2(src, samples/2, samplesize*2, 3, 1, 2); + else + reorder_self_4_step_2(src, samples, samplesize, 6, 2, 3, 4, 5); + break; + case AF_CHANNEL_LAYOUT_5_1_A << 16 | AF_CHANNEL_LAYOUT_5_1_C: + reorder_self_2_3(src, samples, samplesize, 1, 2, 3, 4, 5); + break; + case AF_CHANNEL_LAYOUT_5_1_A << 16 | AF_CHANNEL_LAYOUT_5_1_D: + reorder_self_3_3(src, samples, samplesize, 2, 1, 0, 3, 4, 5); + break; + case AF_CHANNEL_LAYOUT_5_1_B << 16 | AF_CHANNEL_LAYOUT_5_1_A: + if (samplesize != 3) + reorder_self_2(src, samples/2, samplesize*2, 3, 1, 2); + else + reorder_self_4_step_2(src, samples, samplesize, 6, 2, 3, 4, 5); + break; + case AF_CHANNEL_LAYOUT_5_1_B << 16 | AF_CHANNEL_LAYOUT_5_1_C: + reorder_self_4_step_1(src, samples, samplesize, 6, 4, 3, 2, 1); + break; + case AF_CHANNEL_LAYOUT_5_1_B << 16 | AF_CHANNEL_LAYOUT_5_1_D: + reorder_self_5_step_1(src, samples, samplesize, 6, 4, 3, 2, 1, 0); + break; + case AF_CHANNEL_LAYOUT_5_1_B << 16 | AF_CHANNEL_LAYOUT_5_1_E: + reorder_self_2_4(src, samples, samplesize, 6, 2, 4, 5, 3, 1, 0); + break; + case AF_CHANNEL_LAYOUT_5_1_C << 16 | AF_CHANNEL_LAYOUT_5_1_A: + reorder_self_2_3(src, samples, samplesize, 1, 2, 5, 4, 3); + break; + case AF_CHANNEL_LAYOUT_5_1_C << 16 | AF_CHANNEL_LAYOUT_5_1_B: + reorder_self_4_step_1(src, samples, samplesize, 6, 1, 2, 3, 4); + break; + case AF_CHANNEL_LAYOUT_5_1_C << 16 | AF_CHANNEL_LAYOUT_5_1_D: + reorder_self_2(src, samples, samplesize, 6, 0, 1); + break; + case AF_CHANNEL_LAYOUT_5_1_D << 16 | AF_CHANNEL_LAYOUT_5_1_A: + reorder_self_3_3(src, samples, samplesize, 0, 1, 2, 5, 4, 3); + break; + case AF_CHANNEL_LAYOUT_5_1_D << 16 | AF_CHANNEL_LAYOUT_5_1_B: + reorder_self_5_step_1(src, samples, samplesize, 6, 0, 1, 2, 3, 4); + break; + case AF_CHANNEL_LAYOUT_5_1_D << 16 | AF_CHANNEL_LAYOUT_5_1_C: + reorder_self_2(src, samples, samplesize, 6, 0, 1); + break; + case AF_CHANNEL_LAYOUT_5_1_E << 16 | AF_CHANNEL_LAYOUT_5_1_B: + reorder_self_2_4(src, samples, samplesize, 6, 2, 4, 0, 1, 3, 5); + break; + case AF_CHANNEL_LAYOUT_5_1_F << 16 | AF_CHANNEL_LAYOUT_5_1_B: + reorder_self_2_4(src, samples, samplesize, 6, 3, 5, 0, 1, 2, 4); + break; + // AF_CHANNEL_LAYOUT_7_1_A L R C LFE Ls Rs Rls Rrs + // AF_CHANNEL_LAYOUT_7_1_B L R Ls Rs C LFE Rls Rrs + // AF_CHANNEL_LAYOUT_7_1_C L C R Ls Rs LFE Rls Rrs + // AF_CHANNEL_LAYOUT_7_1_D C L R Ls Rs Rls Rrs LFE + // AF_CHANNEL_LAYOUT_7_1_F C L R LFE Ls Rs Rls Rrs + case AF_CHANNEL_LAYOUT_7_1_A << 16 | AF_CHANNEL_LAYOUT_7_1_B: + case AF_CHANNEL_LAYOUT_7_1_B << 16 | AF_CHANNEL_LAYOUT_7_1_A: + if (samplesize != 3) + reorder_self_2(src, samples/2, samplesize*2, 4, 1, 2); + else + reorder_self_4_step_2(src, samples, samplesize, 8, 2, 3, 4, 5); + break; + case AF_CHANNEL_LAYOUT_7_1_B << 16 | AF_CHANNEL_LAYOUT_7_1_D: + // First convert to AF_CHANNEL_LAYOUT_7_1_F + reorder_self_2_4(src, samples, samplesize, 8, 3, 5, 4, 2, 1, 0); + // then convert to AF_CHANNEL_LAYOUT_7_1_D + reorder_self_5_step_1(src, samples, samplesize, 8, 3, 4, 5, 6, 7); + break; + case AF_CHANNEL_LAYOUT_7_1_C << 16 | AF_CHANNEL_LAYOUT_7_1_B: + reorder_self_4_step_1(src, samples, samplesize, 8, 1, 2, 3, 4); + break; + case AF_CHANNEL_LAYOUT_7_1_F << 16 | AF_CHANNEL_LAYOUT_7_1_B: + reorder_self_2_4(src, samples, samplesize, 8, 3, 5, 0, 1, 2, 4); + break; + default: + mp_msg(MSGT_GLOBAL, MSGL_WARN, + "[reorder_channel] unsupported from %x to %x, %d * %d\n", + src_layout, dest_layout, samples, samplesize); + } +} + + +static int channel_layout_mapping_5ch[AF_CHANNEL_LAYOUT_SOURCE_NUM] = { + AF_CHANNEL_LAYOUT_ALSA_5CH_DEFAULT, + AF_CHANNEL_LAYOUT_AAC_5CH_DEFAULT, + AF_CHANNEL_LAYOUT_WAVEEX_5CH_DEFAULT, + AF_CHANNEL_LAYOUT_LAVC_5CH_DEFAULT, + AF_CHANNEL_LAYOUT_VORBIS_5CH_DEFAULT, +}; + +static int channel_layout_mapping_6ch[AF_CHANNEL_LAYOUT_SOURCE_NUM] = { + AF_CHANNEL_LAYOUT_ALSA_6CH_DEFAULT, + AF_CHANNEL_LAYOUT_AAC_6CH_DEFAULT, + AF_CHANNEL_LAYOUT_WAVEEX_6CH_DEFAULT, + AF_CHANNEL_LAYOUT_LAVC_6CH_DEFAULT, + AF_CHANNEL_LAYOUT_VORBIS_6CH_DEFAULT, +}; + +static int channel_layout_mapping_8ch[AF_CHANNEL_LAYOUT_SOURCE_NUM] = { + AF_CHANNEL_LAYOUT_ALSA_8CH_DEFAULT, + AF_CHANNEL_LAYOUT_AAC_8CH_DEFAULT, + AF_CHANNEL_LAYOUT_WAVEEX_8CH_DEFAULT, + AF_CHANNEL_LAYOUT_LAVC_8CH_DEFAULT, + AF_CHANNEL_LAYOUT_VORBIS_8CH_DEFAULT, +}; + +void reorder_channel_copy_nch(void *src, + int src_layout, + void *dest, + int dest_layout, + int chnum, + int samples, + int samplesize) +{ + if (chnum < 5 || chnum == 7 || chnum > 8 || + src_layout < 0 || dest_layout < 0 || + src_layout >= AF_CHANNEL_LAYOUT_SOURCE_NUM || + dest_layout >= AF_CHANNEL_LAYOUT_SOURCE_NUM) + memcpy(dest, src, samples*samplesize); + else if (chnum == 6) + reorder_channel_copy(src, channel_layout_mapping_6ch[src_layout], + dest, channel_layout_mapping_6ch[dest_layout], + samples, samplesize); + else if (chnum == 8) + reorder_channel_copy(src, channel_layout_mapping_8ch[src_layout], + dest, channel_layout_mapping_8ch[dest_layout], + samples, samplesize); + else + reorder_channel_copy(src, channel_layout_mapping_5ch[src_layout], + dest, channel_layout_mapping_5ch[dest_layout], + samples, samplesize); +} + +void reorder_channel_nch(void *buf, + int src_layout, + int dest_layout, + int chnum, + int samples, + int samplesize) +{ + if (src_layout == dest_layout || chnum < 5 || chnum == 7 || chnum > 8 || + src_layout < 0 || dest_layout < 0 || + src_layout >= AF_CHANNEL_LAYOUT_SOURCE_NUM || + dest_layout >= AF_CHANNEL_LAYOUT_SOURCE_NUM || + src_layout == dest_layout) + return; + if (chnum == 6) + reorder_channel(buf, channel_layout_mapping_6ch[src_layout], + channel_layout_mapping_6ch[dest_layout], + samples, samplesize); + else if (chnum == 8) + reorder_channel(buf, channel_layout_mapping_8ch[src_layout], + channel_layout_mapping_8ch[dest_layout], + samples, samplesize); + else + reorder_channel(buf, channel_layout_mapping_5ch[src_layout], + channel_layout_mapping_5ch[dest_layout], + samples, samplesize); +} + + +#ifdef TEST + +static void test_copy(int channels) { + int samples = 12*1024*1024; + int samplesize = 2; + int i; + unsigned char *bufin = malloc((samples+100)*samplesize); + unsigned char *bufout = malloc((samples+100)*samplesize); + memset(bufin, 0xFF, samples*samplesize); + for (i = 0;i < 100; ++i) + reorder_channel_copy(bufin, AF_CHANNEL_LAYOUT_5_1_A, + bufout, AF_CHANNEL_LAYOUT_5_1_B, + samples, samplesize); +// reorder_channel(bufin, AF_CHANNEL_LAYOUT_5_1_B, +// AF_CHANNEL_LAYOUT_5_1_D, +// samples, samplesize); + free(bufin); + free(bufout); +} + +int main(int argc, char *argv[]) { + int channels = 6; + if (argc > 1) + channels = atoi(argv[1]); + test_copy(channels); + return 0; +} + +#endif diff --git a/audio/reorder_ch.h b/audio/reorder_ch.h new file mode 100644 index 0000000000..44b533988d --- /dev/null +++ b/audio/reorder_ch.h @@ -0,0 +1,133 @@ +/* + * common functions for reordering audio channels + * + * Copyright (C) 2007 Ulion <ulion A gmail P com> + * + * This file is part of MPlayer. + * + * MPlayer 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. + * + * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPLAYER_REORDER_CH_H +#define MPLAYER_REORDER_CH_H + +// L - Left +// R - Right +// C - Center +// Ls - Left Surround +// Rs - Right Surround +// Cs - Center Surround +// Rls - Rear Left Surround +// Rrs - Rear Right Surround + +#define AF_LFE (1<<7) + +#define AF_CHANNEL_LAYOUT_MONO ((100<<8)|1) +#define AF_CHANNEL_LAYOUT_STEREO ((101<<8)|2) +#define AF_CHANNEL_LAYOUT_1_0 AF_CHANNEL_LAYOUT_MONO // C +#define AF_CHANNEL_LAYOUT_2_0 AF_CHANNEL_LAYOUT_STEREO // L R +#define AF_CHANNEL_LAYOUT_2_1 ((102<<8)|3) // L R LFE +#define AF_CHANNEL_LAYOUT_3_0_A ((103<<8)|3) // L R C +#define AF_CHANNEL_LAYOUT_3_0_B ((104<<8)|3) // C L R +#define AF_CHANNEL_LAYOUT_4_0_A ((105<<8)|4) // L R C Cs +#define AF_CHANNEL_LAYOUT_4_0_B ((106<<8)|4) // C L R Cs +#define AF_CHANNEL_LAYOUT_4_0_C ((107<<8)|4) // L R Ls Rs +#define AF_CHANNEL_LAYOUT_5_0_A ((108<<8)|5) // L R C Ls Rs +#define AF_CHANNEL_LAYOUT_5_0_B ((109<<8)|5) // L R Ls Rs C +#define AF_CHANNEL_LAYOUT_5_0_C ((110<<8)|5) // L C R Ls Rs +#define AF_CHANNEL_LAYOUT_5_0_D ((111<<8)|5) // C L R Ls Rs +#define AF_CHANNEL_LAYOUT_5_1_A ((112<<8)|6|AF_LFE) // L R C LFE Ls Rs +#define AF_CHANNEL_LAYOUT_5_1_B ((113<<8)|6|AF_LFE) // L R Ls Rs C LFE +#define AF_CHANNEL_LAYOUT_5_1_C ((114<<8)|6|AF_LFE) // L C R Ls Rs LFE +#define AF_CHANNEL_LAYOUT_5_1_D ((115<<8)|6|AF_LFE) // C L R Ls Rs LFE +#define AF_CHANNEL_LAYOUT_5_1_E ((116<<8)|6|AF_LFE) // LFE L C R Ls Rs +#define AF_CHANNEL_LAYOUT_5_1_F ((117<<8)|6|AF_LFE) // C L R LFE Ls Rs +#define AF_CHANNEL_LAYOUT_6_1_A ((118<<8)|7|AF_LFE) // L R C LFE Ls Rs Cs +#define AF_CHANNEL_LAYOUT_7_1_A ((119<<8)|8|AF_LFE) // L R C LFE Ls Rs Rls Rrs +#define AF_CHANNEL_LAYOUT_7_1_B ((120<<8)|8|AF_LFE) // L R Ls Rs C LFE Rls Rrs +#define AF_CHANNEL_LAYOUT_7_1_C ((121<<8)|8|AF_LFE) // L C R Ls Rs LFE Rls Rrs +#define AF_CHANNEL_LAYOUT_7_1_D ((122<<8)|8|AF_LFE) // C L R Ls Rs Rls Rrs LFE +#define AF_CHANNEL_LAYOUT_7_1_F ((123<<8)|8|AF_LFE) // C L R LFE Ls Rs Rls Rrs + + +#define AF_CHANNEL_LAYOUT_ALSA_5CH_DEFAULT AF_CHANNEL_LAYOUT_5_0_B +#define AF_CHANNEL_LAYOUT_ALSA_6CH_DEFAULT AF_CHANNEL_LAYOUT_5_1_B +#define AF_CHANNEL_LAYOUT_ALSA_8CH_DEFAULT AF_CHANNEL_LAYOUT_7_1_B +#define AF_CHANNEL_LAYOUT_MPLAYER_5CH_DEFAULT AF_CHANNEL_LAYOUT_ALSA_5CH_DEFAULT +#define AF_CHANNEL_LAYOUT_MPLAYER_6CH_DEFAULT AF_CHANNEL_LAYOUT_ALSA_6CH_DEFAULT +#define AF_CHANNEL_LAYOUT_MPLAYER_8CH_DEFAULT AF_CHANNEL_LAYOUT_ALSA_8CH_DEFAULT +#define AF_CHANNEL_LAYOUT_AAC_5CH_DEFAULT AF_CHANNEL_LAYOUT_5_0_D +#define AF_CHANNEL_LAYOUT_AAC_6CH_DEFAULT AF_CHANNEL_LAYOUT_5_1_D +#define AF_CHANNEL_LAYOUT_AAC_8CH_DEFAULT AF_CHANNEL_LAYOUT_7_1_D +#define AF_CHANNEL_LAYOUT_WAVEEX_5CH_DEFAULT AF_CHANNEL_LAYOUT_5_0_A +#define AF_CHANNEL_LAYOUT_WAVEEX_6CH_DEFAULT AF_CHANNEL_LAYOUT_5_1_A +#define AF_CHANNEL_LAYOUT_WAVEEX_8CH_DEFAULT AF_CHANNEL_LAYOUT_7_1_A +#define AF_CHANNEL_LAYOUT_LAVC_5CH_DEFAULT AF_CHANNEL_LAYOUT_5_0_A +#define AF_CHANNEL_LAYOUT_LAVC_6CH_DEFAULT AF_CHANNEL_LAYOUT_5_1_A +#define AF_CHANNEL_LAYOUT_LAVC_8CH_DEFAULT AF_CHANNEL_LAYOUT_7_1_A +#define AF_CHANNEL_LAYOUT_VORBIS_5CH_DEFAULT AF_CHANNEL_LAYOUT_5_0_C +#define AF_CHANNEL_LAYOUT_VORBIS_6CH_DEFAULT AF_CHANNEL_LAYOUT_5_1_C +#define AF_CHANNEL_LAYOUT_VORBIS_8CH_DEFAULT AF_CHANNEL_LAYOUT_7_1_C + +#define AF_CHANNEL_MASK 0xFF +#define AF_GET_CH_NUM(A) ((A)&0x7F) +#define AF_GET_CH_NUM_WITH_LFE(A) ((A)&0xFF) +#define AF_IS_SAME_CH_NUM(A,B) (((A)&0xFF)==((B)&0xFF)) +#define AF_IS_LAYOUT_SPECIFIED(A) ((A)&0xFFFFF800) +#define AF_IS_LAYOUT_UNSPECIFIED(A) (!AF_IS_LAYOUT_SPECIFIED(A)) + +/// Optimized channel reorder between channel layouts with same channel number. +void reorder_channel_copy(void *src, + int src_layout, + void *dest, + int dest_layout, + int samples, + int samplesize); + +/// Same with reorder_channel_copy, but works on single buffer. +void reorder_channel(void *buf, + int src_layout, + int dest_layout, + int samples, + int samplesize); + +// Channel layout definitions for different audio sources or targets +// When specified channel number, they will be map to the specific layouts. +#define AF_CHANNEL_LAYOUT_ALSA_DEFAULT 0 +#define AF_CHANNEL_LAYOUT_AAC_DEFAULT 1 +#define AF_CHANNEL_LAYOUT_WAVEEX_DEFAULT 2 +#define AF_CHANNEL_LAYOUT_LAVC_DEFAULT 3 +#define AF_CHANNEL_LAYOUT_VORBIS_DEFAULT 4 +#define AF_CHANNEL_LAYOUT_SOURCE_NUM 5 +#define AF_CHANNEL_LAYOUT_MPLAYER_DEFAULT AF_CHANNEL_LAYOUT_ALSA_DEFAULT + +/// Optimized channel reorder between different audio sources and targets. +void reorder_channel_copy_nch(void *src, + int src_layout, + void *dest, + int dest_layout, + int chnum, + int samples, + int samplesize); + +/// Same with reorder_channel_copy_nch, but works on single buffer. +void reorder_channel_nch(void *buf, + int src_layout, + int dest_layout, + int chnum, + int samples, + int samplesize); + +#endif /* MPLAYER_REORDER_CH_H */ |