diff options
Diffstat (limited to 'libavcodec/libcelt_dec.c')
-rw-r--r-- | libavcodec/libcelt_dec.c | 146 |
1 files changed, 146 insertions, 0 deletions
diff --git a/libavcodec/libcelt_dec.c b/libavcodec/libcelt_dec.c new file mode 100644 index 0000000000..7f0f9ad25d --- /dev/null +++ b/libavcodec/libcelt_dec.c @@ -0,0 +1,146 @@ +/* + * Xiph CELT 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 "internal.h" +#include "libavutil/intreadwrite.h" + +struct libcelt_context { + CELTMode *mode; + CELTDecoder *dec; + AVFrame frame; + 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(ENOSYS); +#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->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; + } + } + 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); + } + c->sample_fmt = AV_SAMPLE_FMT_S16; + avcodec_get_frame_defaults(&celt->frame); + c->coded_frame = &celt->frame; + 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 *frame, + int *got_frame_ptr, AVPacket *pkt) +{ + struct libcelt_context *celt = c->priv_data; + int err; + int16_t *pcm; + + celt->frame.nb_samples = c->frame_size; + err = ff_get_buffer(c, &celt->frame); + if (err < 0) { + av_log(c, AV_LOG_ERROR, "get_buffer() failed\n"); + return err; + } + pcm = (int16_t *)celt->frame.data[0]; + err = celt_decode(celt->dec, pkt->data, pkt->size, pcm, c->frame_size); + if (err < 0) + return ff_celt_error_to_averror(err); + if (celt->discard) { + celt->frame.nb_samples -= celt->discard; + memmove(pcm, pcm + celt->discard * c->channels, + celt->frame.nb_samples * c->channels * sizeof(int16_t)); + celt->discard = 0; + } + *got_frame_ptr = 1; + *(AVFrame *)frame = celt->frame; + return pkt->size; +} + +AVCodec ff_libcelt_decoder = { + .name = "libcelt", + .type = AVMEDIA_TYPE_AUDIO, + .id = AV_CODEC_ID_CELT, + .priv_data_size = sizeof(struct libcelt_context), + .init = libcelt_dec_init, + .close = libcelt_dec_close, + .decode = libcelt_dec_decode, + .capabilities = CODEC_CAP_DR1, + .long_name = NULL_IF_CONFIG_SMALL("Xiph CELT decoder using libcelt"), +}; |