diff options
Diffstat (limited to 'libavcodec/libcelt_dec.c')
-rw-r--r-- | libavcodec/libcelt_dec.c | 136 |
1 files changed, 136 insertions, 0 deletions
diff --git a/libavcodec/libcelt_dec.c b/libavcodec/libcelt_dec.c new file mode 100644 index 0000000000..6f3965401c --- /dev/null +++ b/libavcodec/libcelt_dec.c @@ -0,0 +1,136 @@ +/* + * Xiph CELT / Opus decoder using libcelt + * Copyright (c) 2011 Nicolas George + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <celt/celt.h> +#include <celt/celt_header.h> +#include "avcodec.h" +#include "libavutil/intreadwrite.h" + +struct libcelt_context { + CELTMode *mode; + CELTDecoder *dec; + int frame_bytes; + int discard; +}; + +static int ff_celt_error_to_averror(int err) +{ + switch(err) { + case CELT_BAD_ARG: return AVERROR(EINVAL); +#ifdef CELT_BUFFER_TOO_SMALL + case CELT_BUFFER_TOO_SMALL: return AVERROR(ENOBUFS); +#endif + case CELT_INTERNAL_ERROR: return AVERROR(EFAULT); + case CELT_CORRUPTED_DATA: return AVERROR_INVALIDDATA; + case CELT_UNIMPLEMENTED: return AVERROR(ENOTSUP); +#ifdef ENOTRECOVERABLE + case CELT_INVALID_STATE: return AVERROR(ENOTRECOVERABLE); +#endif + case CELT_ALLOC_FAIL: return AVERROR(ENOMEM); + default: return AVERROR(EINVAL); + } +} + +static int ff_celt_bitstream_version_hack(CELTMode *mode) +{ + CELTHeader header = { .version_id = 0 }; + celt_header_init(&header, mode, 960, 2); + return header.version_id; +} + +static av_cold int libcelt_dec_init(AVCodecContext *c) +{ + struct libcelt_context *celt = c->priv_data; + int err; + + if (!c->channels || !c->frame_size || + c->frame_size > INT_MAX / sizeof(int16_t) / c->channels) + return AVERROR(EINVAL); + celt->frame_bytes = c->frame_size * c->channels * sizeof(int16_t); + celt->mode = celt_mode_create(c->sample_rate, c->frame_size, &err); + if (!celt->mode) + return ff_celt_error_to_averror(err); + celt->dec = celt_decoder_create_custom(celt->mode, c->channels, &err); + if (!celt->dec) { + celt_mode_destroy(celt->mode); + return ff_celt_error_to_averror(err); + } + if (c->extradata_size >= 4) { + celt->discard = AV_RL32(c->extradata); + if (celt->discard < 0 || celt->discard >= c->frame_size) { + av_log(c, AV_LOG_WARNING, + "Invalid overlap (%d), ignored.\n", celt->discard); + celt->discard = 0; + } + celt->discard *= c->channels * sizeof(int16_t); + } + if(c->extradata_size >= 8) { + unsigned version = AV_RL32(c->extradata + 4); + unsigned lib_version = ff_celt_bitstream_version_hack(celt->mode); + if (version != lib_version) + av_log(c, AV_LOG_WARNING, + "CELT bitstream version 0x%x may be " + "improperly decoded by libcelt for version 0x%x.\n", + version, lib_version); + } + return 0; +} + +static av_cold int libcelt_dec_close(AVCodecContext *c) +{ + struct libcelt_context *celt = c->priv_data; + + celt_decoder_destroy(celt->dec); + celt_mode_destroy(celt->mode); + return 0; +} + +static int libcelt_dec_decode(AVCodecContext *c, void *pcm, int *pcm_size, + AVPacket *pkt) +{ + struct libcelt_context *celt = c->priv_data; + int err; + + if (*pcm_size < celt->frame_bytes) + return AVERROR(ENOBUFS); + err = celt_decode(celt->dec, pkt->data, pkt->size, pcm, c->frame_size); + if (err < 0) + return ff_celt_error_to_averror(err); + *pcm_size = celt->frame_bytes; + if (celt->discard) { + *pcm_size = celt->frame_bytes - celt->discard; + memmove(pcm, (char *)pcm + celt->discard, *pcm_size); + celt->discard = 0; + } + return pkt->size; +} + +AVCodec ff_libcelt_decoder = { + .name = "libcelt", + .type = AVMEDIA_TYPE_AUDIO, + .id = CODEC_ID_CELT, + .priv_data_size = sizeof(struct libcelt_context), + .init = libcelt_dec_init, + .close = libcelt_dec_close, + .decode = libcelt_dec_decode, + .capabilities = 0, + .long_name = NULL_IF_CONFIG_SMALL("Xiph CELT/Opus decoder using libcelt"), +}; |