From b606a017593b2a7a5f435f2b75b8de163c367ac6 Mon Sep 17 00:00:00 2001 From: Justin Ruggles Date: Tue, 18 Oct 2011 12:16:27 -0700 Subject: [PATCH] libspeexenc: add libspeex encoder --- Changelog | 1 + configure | 3 +- doc/general.texi | 2 +- libavcodec/Makefile | 1 + libavcodec/allcodecs.c | 2 +- libavcodec/libspeexenc.c | 324 +++++++++++++++++++++++++++++++++++++++ libavcodec/version.h | 2 +- 7 files changed, 331 insertions(+), 4 deletions(-) create mode 100644 libavcodec/libspeexenc.c diff --git a/Changelog b/Changelog index f61d3e2b13..3041632a33 100644 --- a/Changelog +++ b/Changelog @@ -53,6 +53,7 @@ easier to use. The changes are: - lut, lutrgb, and lutyuv filters - boxblur filter - Ut Video decoder +- Speex encoding via libspeex version 0.7: diff --git a/configure b/configure index a036873682..1860dc8ae5 100755 --- a/configure +++ b/configure @@ -178,7 +178,7 @@ External library support: --enable-libopenjpeg enable JPEG 2000 decoding via OpenJPEG [no] --enable-librtmp enable RTMP[E] support via librtmp [no] --enable-libschroedinger enable Dirac support via libschroedinger [no] - --enable-libspeex enable Speex decoding via libspeex [no] + --enable-libspeex enable Speex support via libspeex [no] --enable-libtheora enable Theora encoding via libtheora [no] --enable-libvo-aacenc enable AAC encoding via libvo-aacenc [no] --enable-libvo-amrwbenc enable AMR-WB encoding via libvo-amrwbenc [no] @@ -1406,6 +1406,7 @@ libopenjpeg_decoder_deps="libopenjpeg" libschroedinger_decoder_deps="libschroedinger" libschroedinger_encoder_deps="libschroedinger" libspeex_decoder_deps="libspeex" +libspeex_encoder_deps="libspeex" libtheora_encoder_deps="libtheora" libvo_aacenc_encoder_deps="libvo_aacenc" libvo_amrwbenc_encoder_deps="libvo_amrwbenc" diff --git a/doc/general.texi b/doc/general.texi index c4c4414c4f..b1c2534be0 100644 --- a/doc/general.texi +++ b/doc/general.texi @@ -682,7 +682,7 @@ following image formats are supported: @tab Used in Sierra VMD files. @item Smacker audio @tab @tab X @item SMPTE 302M AES3 audio @tab @tab X -@item Speex @tab @tab E +@item Speex @tab E @tab E @tab supported through external library libspeex @item True Audio (TTA) @tab @tab X @item TrueHD @tab @tab X diff --git a/libavcodec/Makefile b/libavcodec/Makefile index c47424e913..81881e44f0 100644 --- a/libavcodec/Makefile +++ b/libavcodec/Makefile @@ -576,6 +576,7 @@ OBJS-$(CONFIG_LIBSCHROEDINGER_ENCODER) += libschroedingerenc.o \ libschroedinger.o \ libdirac_libschro.o OBJS-$(CONFIG_LIBSPEEX_DECODER) += libspeexdec.o +OBJS-$(CONFIG_LIBSPEEX_ENCODER) += libspeexenc.o OBJS-$(CONFIG_LIBTHEORA_ENCODER) += libtheoraenc.o OBJS-$(CONFIG_LIBVO_AACENC_ENCODER) += libvo-aacenc.o mpeg4audio.o OBJS-$(CONFIG_LIBVO_AMRWBENC_ENCODER) += libvo-amrwbenc.o diff --git a/libavcodec/allcodecs.c b/libavcodec/allcodecs.c index f85f06d6cc..64669b7c8f 100644 --- a/libavcodec/allcodecs.c +++ b/libavcodec/allcodecs.c @@ -370,7 +370,7 @@ void avcodec_register_all(void) REGISTER_DECODER (LIBOPENCORE_AMRWB, libopencore_amrwb); REGISTER_DECODER (LIBOPENJPEG, libopenjpeg); REGISTER_ENCDEC (LIBSCHROEDINGER, libschroedinger); - REGISTER_DECODER (LIBSPEEX, libspeex); + REGISTER_ENCDEC (LIBSPEEX, libspeex); REGISTER_ENCODER (LIBTHEORA, libtheora); REGISTER_ENCODER (LIBVO_AACENC, libvo_aacenc); REGISTER_ENCODER (LIBVO_AMRWBENC, libvo_amrwbenc); diff --git a/libavcodec/libspeexenc.c b/libavcodec/libspeexenc.c new file mode 100644 index 0000000000..18f973f4fd --- /dev/null +++ b/libavcodec/libspeexenc.c @@ -0,0 +1,324 @@ +/* + * Copyright (C) 2009 Justin Ruggles + * Copyright (c) 2009 Xuggle Incorporated + * + * This file is part of Libav. + * + * Libav 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. + * + * Libav 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 Libav; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * libspeex Speex audio encoder + * + * Usage Guide + * This explains the values that need to be set prior to initialization in + * order to control various encoding parameters. + * + * Channels + * Speex only supports mono or stereo, so avctx->channels must be set to + * 1 or 2. + * + * Sample Rate / Encoding Mode + * Speex has 3 modes, each of which uses a specific sample rate. + * narrowband : 8 kHz + * wideband : 16 kHz + * ultra-wideband : 32 kHz + * avctx->sample_rate must be set to one of these 3 values. This will be + * used to set the encoding mode. + * + * Rate Control + * VBR mode is turned on by setting CODEC_FLAG_QSCALE in avctx->flags. + * avctx->global_quality is used to set the encoding quality. + * For CBR mode, avctx->bit_rate can be used to set the constant bitrate. + * Alternatively, the 'cbr_quality' option can be set from 0 to 10 to set + * a constant bitrate based on quality. + * For ABR mode, set avctx->bit_rate and set the 'abr' option to 1. + * Approx. Bitrate Range: + * narrowband : 2400 - 25600 bps + * wideband : 4000 - 43200 bps + * ultra-wideband : 4400 - 45200 bps + * + * Complexity + * Encoding complexity is controlled by setting avctx->compression_level. + * The valid range is 0 to 10. A higher setting gives generally better + * quality at the expense of encoding speed. This does not affect the + * bit rate. + * + * Frames-per-Packet + * The encoder defaults to using 1 frame-per-packet. However, it is + * sometimes desirable to use multiple frames-per-packet to reduce the + * amount of container overhead. This can be done by setting the + * 'frames_per_packet' option to a value 1 to 8. + */ + +#include +#include +#include +#include "libavutil/mathematics.h" +#include "libavutil/opt.h" +#include "avcodec.h" +#include "internal.h" + +typedef struct { + AVClass *class; ///< AVClass for private options + SpeexBits bits; ///< libspeex bitwriter context + SpeexHeader header; ///< libspeex header struct + void *enc_state; ///< libspeex encoder state + int frames_per_packet; ///< number of frames to encode in each packet + float vbr_quality; ///< VBR quality 0.0 to 10.0 + int cbr_quality; ///< CBR quality 0 to 10 + int abr; ///< flag to enable ABR + int pkt_frame_count; ///< frame count for the current packet + int lookahead; ///< encoder delay + int sample_count; ///< total sample count (used for pts) +} LibSpeexEncContext; + +static av_cold void print_enc_params(AVCodecContext *avctx, + LibSpeexEncContext *s) +{ + const char *mode_str = "unknown"; + + av_log(avctx, AV_LOG_DEBUG, "channels: %d\n", avctx->channels); + switch (s->header.mode) { + case SPEEX_MODEID_NB: mode_str = "narrowband"; break; + case SPEEX_MODEID_WB: mode_str = "wideband"; break; + case SPEEX_MODEID_UWB: mode_str = "ultra-wideband"; break; + } + av_log(avctx, AV_LOG_DEBUG, "mode: %s\n", mode_str); + if (s->header.vbr) { + av_log(avctx, AV_LOG_DEBUG, "rate control: VBR\n"); + av_log(avctx, AV_LOG_DEBUG, " quality: %f\n", s->vbr_quality); + } else if (s->abr) { + av_log(avctx, AV_LOG_DEBUG, "rate control: ABR\n"); + av_log(avctx, AV_LOG_DEBUG, " bitrate: %d bps\n", avctx->bit_rate); + } else { + av_log(avctx, AV_LOG_DEBUG, "rate control: CBR\n"); + av_log(avctx, AV_LOG_DEBUG, " bitrate: %d bps\n", avctx->bit_rate); + } + av_log(avctx, AV_LOG_DEBUG, "complexity: %d\n", + avctx->compression_level); + av_log(avctx, AV_LOG_DEBUG, "frame size: %d samples\n", + avctx->frame_size); + av_log(avctx, AV_LOG_DEBUG, "frames per packet: %d\n", + s->frames_per_packet); + av_log(avctx, AV_LOG_DEBUG, "packet size: %d\n", + avctx->frame_size * s->frames_per_packet); +} + +static av_cold int encode_init(AVCodecContext *avctx) +{ + LibSpeexEncContext *s = avctx->priv_data; + const SpeexMode *mode; + uint8_t *header_data; + int header_size; + int32_t complexity; + + /* channels */ + if (avctx->channels < 1 || avctx->channels > 2) { + av_log(avctx, AV_LOG_ERROR, "Invalid channels (%d). Only stereo and " + "mono are supported\n", avctx->channels); + return AVERROR(EINVAL); + } + + /* sample rate and encoding mode */ + switch (avctx->sample_rate) { + case 8000: mode = &speex_nb_mode; break; + case 16000: mode = &speex_wb_mode; break; + case 32000: mode = &speex_uwb_mode; break; + default: + av_log(avctx, AV_LOG_ERROR, "Sample rate of %d Hz is not supported. " + "Resample to 8, 16, or 32 kHz.\n", avctx->sample_rate); + return AVERROR(EINVAL); + } + + /* initialize libspeex */ + s->enc_state = speex_encoder_init(mode); + if (!s->enc_state) { + av_log(avctx, AV_LOG_ERROR, "Error initializing libspeex\n"); + return -1; + } + speex_init_header(&s->header, avctx->sample_rate, avctx->channels, mode); + + /* rate control method and parameters */ + if (avctx->flags & CODEC_FLAG_QSCALE) { + /* VBR */ + s->header.vbr = 1; + speex_encoder_ctl(s->enc_state, SPEEX_SET_VBR, &s->header.vbr); + s->vbr_quality = av_clipf(avctx->global_quality / (float)FF_QP2LAMBDA, + 0.0f, 10.0f); + speex_encoder_ctl(s->enc_state, SPEEX_SET_VBR_QUALITY, &s->vbr_quality); + } else { + s->header.bitrate = avctx->bit_rate; + if (avctx->bit_rate > 0) { + /* CBR or ABR by bitrate */ + if (s->abr) { + speex_encoder_ctl(s->enc_state, SPEEX_SET_ABR, + &s->header.bitrate); + speex_encoder_ctl(s->enc_state, SPEEX_GET_ABR, + &s->header.bitrate); + } else { + speex_encoder_ctl(s->enc_state, SPEEX_SET_BITRATE, + &s->header.bitrate); + speex_encoder_ctl(s->enc_state, SPEEX_GET_BITRATE, + &s->header.bitrate); + } + } else { + /* CBR by quality */ + speex_encoder_ctl(s->enc_state, SPEEX_SET_QUALITY, + &s->cbr_quality); + speex_encoder_ctl(s->enc_state, SPEEX_GET_BITRATE, + &s->header.bitrate); + } + /* stereo side information adds about 800 bps to the base bitrate */ + /* TODO: this should be calculated exactly */ + avctx->bit_rate = s->header.bitrate + (avctx->channels == 2 ? 800 : 0); + } + + /* set encoding complexity */ + if (avctx->compression_level > FF_COMPRESSION_DEFAULT) { + complexity = av_clip(avctx->compression_level, 0, 10); + speex_encoder_ctl(s->enc_state, SPEEX_SET_COMPLEXITY, &complexity); + } + speex_encoder_ctl(s->enc_state, SPEEX_GET_COMPLEXITY, &complexity); + avctx->compression_level = complexity; + + /* set packet size */ + avctx->frame_size = s->header.frame_size; + s->header.frames_per_packet = s->frames_per_packet; + + /* set encoding delay */ + speex_encoder_ctl(s->enc_state, SPEEX_GET_LOOKAHEAD, &s->lookahead); + s->sample_count = -s->lookahead; + + /* create header packet bytes from header struct */ + /* note: libspeex allocates the memory for header_data, which is freed + below with speex_header_free() */ + header_data = speex_header_to_packet(&s->header, &header_size); + + /* allocate extradata and coded_frame */ + avctx->extradata = av_malloc(header_size + FF_INPUT_BUFFER_PADDING_SIZE); + avctx->coded_frame = avcodec_alloc_frame(); + if (!avctx->extradata || !avctx->coded_frame) { + speex_header_free(header_data); + speex_encoder_destroy(s->enc_state); + av_log(avctx, AV_LOG_ERROR, "memory allocation error\n"); + return AVERROR(ENOMEM); + } + + /* copy header packet to extradata */ + memcpy(avctx->extradata, header_data, header_size); + avctx->extradata_size = header_size; + speex_header_free(header_data); + + /* init libspeex bitwriter */ + speex_bits_init(&s->bits); + + print_enc_params(avctx, s); + return 0; +} + +static int encode_frame(AVCodecContext *avctx, uint8_t *frame, int buf_size, + void *data) +{ + LibSpeexEncContext *s = avctx->priv_data; + int16_t *samples = data; + int sample_count = s->sample_count; + + if (data) { + /* encode Speex frame */ + if (avctx->channels == 2) + speex_encode_stereo_int(samples, s->header.frame_size, &s->bits); + speex_encode_int(s->enc_state, samples, &s->bits); + s->pkt_frame_count++; + s->sample_count += avctx->frame_size; + } else { + /* handle end-of-stream */ + if (!s->pkt_frame_count) + return 0; + /* add extra terminator codes for unused frames in last packet */ + while (s->pkt_frame_count < s->frames_per_packet) { + speex_bits_pack(&s->bits, 15, 5); + s->pkt_frame_count++; + } + } + + /* write output if all frames for the packet have been encoded */ + if (s->pkt_frame_count == s->frames_per_packet) { + s->pkt_frame_count = 0; + avctx->coded_frame->pts = + av_rescale_q(sample_count, (AVRational){ 1, avctx->sample_rate }, + avctx->time_base); + if (buf_size > speex_bits_nbytes(&s->bits)) { + int ret = speex_bits_write(&s->bits, frame, buf_size); + speex_bits_reset(&s->bits); + return ret; + } else { + av_log(avctx, AV_LOG_ERROR, "output buffer too small"); + return AVERROR(EINVAL); + } + } + return 0; +} + +static av_cold int encode_close(AVCodecContext *avctx) +{ + LibSpeexEncContext *s = avctx->priv_data; + + speex_bits_destroy(&s->bits); + speex_encoder_destroy(s->enc_state); + + av_freep(&avctx->coded_frame); + av_freep(&avctx->extradata); + + return 0; +} + +#define OFFSET(x) offsetof(LibSpeexEncContext, x) +#define AE AV_OPT_FLAG_AUDIO_PARAM | AV_OPT_FLAG_ENCODING_PARAM +static const AVOption options[] = { + { "abr", "Use average bit rate", OFFSET(abr), AV_OPT_TYPE_INT, { 0 }, 0, 1, AE }, + { "cbr_quality", "Set quality value (0 to 10) for CBR", OFFSET(cbr_quality), AV_OPT_TYPE_INT, { 8 }, 0, 10, AE }, + { "frames_per_packet", "Number of frames to encode in each packet", OFFSET(frames_per_packet), AV_OPT_TYPE_INT, { 1 }, 1, 8, AE }, + { NULL }, +}; + +static const AVClass class = { + .class_name = "libspeex", + .item_name = av_default_item_name, + .option = options, + .version = LIBAVUTIL_VERSION_INT, +}; + +static const AVCodecDefault defaults[] = { + { "b", "0" }, + { "compression_level", "3" }, + { NULL }, +}; + +AVCodec ff_libspeex_encoder = { + .name = "libspeex", + .type = AVMEDIA_TYPE_AUDIO, + .id = CODEC_ID_SPEEX, + .priv_data_size = sizeof(LibSpeexEncContext), + .init = encode_init, + .encode = encode_frame, + .close = encode_close, + .capabilities = CODEC_CAP_DELAY, + .sample_fmts = (const enum SampleFormat[]){ AV_SAMPLE_FMT_S16, AV_SAMPLE_FMT_NONE }, + .long_name = NULL_IF_CONFIG_SMALL("libspeex Speex"), + .priv_class = &class, + .defaults = defaults, +}; diff --git a/libavcodec/version.h b/libavcodec/version.h index b89e6f17cc..ef30a694ee 100644 --- a/libavcodec/version.h +++ b/libavcodec/version.h @@ -21,7 +21,7 @@ #define AVCODEC_VERSION_H #define LIBAVCODEC_VERSION_MAJOR 53 -#define LIBAVCODEC_VERSION_MINOR 13 +#define LIBAVCODEC_VERSION_MINOR 14 #define LIBAVCODEC_VERSION_MICRO 0 #define LIBAVCODEC_VERSION_INT AV_VERSION_INT(LIBAVCODEC_VERSION_MAJOR, \