Files
UltraGrid/src/audio/codec/libavcodec.c
Martin Pulec 8b94de582c lavc: replace deprecated items
Some AVCodec properties like pix_fmts or sample_fmts are marked as
deprecated and should be replaced by avcodec_get_supported_config()
in current FFmpeg.

Created compat functions to get rid of deprecate warnings running with
both old and new FFmpeg.
2024-11-12 14:59:19 +01:00

877 lines
33 KiB
C

/**
* @file audio/codec/libavcodec.c
* @author Martin Pulec <pulec@cesnet.cz>
*/
/*
* Copyright (c) 2012-2023 CESNET z.s.p.o.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, is permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* 3. Neither the name of CESNET nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING,
* BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#define __STDC_CONSTANT_MACROS
#ifdef HAVE_CONFIG_H
#include "config.h"
#include "config_unix.h"
#include "config_win32.h"
#endif /* HAVE_CONFIG_H */
#include "debug.h"
#include "lib_common.h"
#include <inttypes.h>
#include <libavcodec/avcodec.h>
#include <libavutil/channel_layout.h>
#include <libavutil/mem.h>
#include <stddef.h>
#include "audio/audio.h"
#include "audio/codec.h"
#include "audio/utils.h"
#include "libavcodec/lavc_common.h"
#include "utils/color_out.h"
#include "utils/macros.h"
#include "utils/string.h"
#include "utils/text.h"
#define MAGIC 0xb135ca11
#define LOW_LATENCY_AUDIOENC_FRAME_DURATION 2.5
enum {
TMP_DATA_LEN = 1024 * 1024,
};
#define MOD_NAME "[lavcd aud.] "
struct libavcodec_codec_state;
static void *libavcodec_init(audio_codec_t audio_codec, audio_codec_direction_t direction,
bool silent, int bitrate);
static audio_channel *libavcodec_compress(void *, audio_channel *);
static audio_channel *libavcodec_decompress(void *, audio_channel *);
static void libavcodec_done(void *);
static void cleanup_common(struct libavcodec_codec_state *s);
struct codec_param {
enum AVCodecID id;
const char *preferred_encoder;
int default_bitrate;
};
static const struct codec_param mapping[AC_COUNT] = {
[AC_ALAW] = {AV_CODEC_ID_PCM_ALAW, NULL, 0 },
[AC_MULAW] = { AV_CODEC_ID_PCM_MULAW, NULL, 0 },
[AC_SPEEX] = { AV_CODEC_ID_SPEEX, NULL, 0 },
[AC_OPUS] = { AV_CODEC_ID_OPUS, "libopus", 96000},
[AC_G722] = { AV_CODEC_ID_ADPCM_G722, NULL, 0 },
[AC_FLAC] = { AV_CODEC_ID_FLAC, NULL, 0 },
[AC_MP3] = { AV_CODEC_ID_MP3, NULL, 0 },
[AC_AAC] = { AV_CODEC_ID_AAC, "libfdk_aac", 0 },
};
struct libavcodec_codec_state {
uint32_t magic;
struct AVPacket *pkt;
struct AVCodecContext *codec_ctx;
const struct AVCodec *codec;
AVFrame *av_frame;
struct audio_desc saved_desc;
audio_channel tmp;
_Alignas(16) char tmp_data[TMP_DATA_LEN]; ///< tmp.data, but non-const qualified
audio_channel output_channel;
_Alignas(16) char output_channel_data[TMP_DATA_LEN]; ///< output_channel.data, but non-const qualified
int bitrate;
bool context_initialized;
audio_codec_direction_t direction;
unsigned char *tmp_buffer;
size_t tmp_buffer_size;
};
///< reallocate tmp_buffer if requested size is greater than currently allocated size
static void resize_tmp_buffer(unsigned char **tmp_buffer, size_t *cur_size, size_t new_size) {
if (new_size <= *cur_size) {
return;
}
unsigned char *new_buf = realloc(*tmp_buffer, new_size);
assert(new_buf);
*tmp_buffer = new_buf;
*cur_size = new_size;
}
/**
* @todo
* Remove and use the global print_libav_error. Dependencies need to be resolved first.
*/
static void print_libav_audio_error(int verbosity, const char *msg, int rc) {
char errbuf[1024];
av_strerror(rc, errbuf, sizeof(errbuf));
log_msg(verbosity, "%s: %s\n", msg, errbuf);
}
static const struct AVCodec *
init_encoder(enum AVCodecID codec_id, const char *preferred_encoder,
bool silent)
{
const char *const requested_encoder =
get_commandline_param("audio-lavc-encoder");
if (requested_encoder != NULL) {
preferred_encoder = requested_encoder;
}
const struct AVCodec *ret = NULL;
if (preferred_encoder) {
ret = avcodec_find_encoder_by_name(preferred_encoder);
if (ret != NULL && ret->id != codec_id) {
MSG(ERROR,
"Requested encoder %s cannot handle "
"specified codec!\n",
preferred_encoder);
assert(preferred_encoder == requested_encoder);
return NULL;
}
if (ret == NULL && requested_encoder != NULL) {
MSG(ERROR, "Requested encoder %s was not found!\n",
requested_encoder);
return NULL;
}
}
if (ret == NULL) {
ret = avcodec_find_encoder(codec_id);
}
if (ret == NULL || silent ||
(ret->id != AV_CODEC_ID_MP3 && ret->id != AV_CODEC_ID_AAC)) {
return ret;
}
// using MP3 or AAC - suggest Opus instead
const int level = strcmp(ret->name, "aac") == 0 /* FF native AAC */
? LOG_LEVEL_WARNING
: LOG_LEVEL_INFO;
log_msg(
level,
MOD_NAME
"Consider using Opus instead of encoder %s,\nsee also: "
"https://github.com/CESNET/UltraGrid/wiki/Audio-Settings#codecs\n",
ret->name);
return ret;
}
static const struct AVCodec *
init_decoder(enum AVCodecID codec_id)
{
const char *pref_dec = get_commandline_param("audio-lavc-decoder");
if (pref_dec != NULL) {
const struct AVCodec *ret =
avcodec_find_decoder_by_name(pref_dec);
if (ret && ret->id == codec_id) {
return ret;
}
log_msg(LOG_LEVEL_ERROR, MOD_NAME "Requested decoder '%s' %s\n",
pref_dec,
ret ? "cannot handle received codec"
: "not found");
handle_error(EXIT_FAIL_USAGE);
}
return avcodec_find_decoder(codec_id);
}
ADD_TO_PARAM(
"audioenc-frame-duration",
"* audioenc-frame-duration=<ms>\n"
" Sets audio encoder frame duration (in ms), default is " TOSTRING(
LOW_LATENCY_AUDIOENC_FRAME_DURATION) " ms for low-latency-audio\n");
ADD_TO_PARAM("audio-lavc-decoder", "* audio-lavc-decoder=<decoder_name>\n"
" Use selected audio lavc decoder\n");
ADD_TO_PARAM("audio-lavc-encoder", "* audio-lavc-encoder=<encoder_name>\n"
" Use selected audio lavc encoder\n");
/**
* Initializates selected audio codec
* @param audio_codec requested audio codec
* @param direction which direction will be used (encoding or decoding)
* @param silent if true no error messages will be printed.
* This is intended for checking which codecs are present
* @retval NULL if initialization failed
* @retval !=NULL codec state
*/
static void *libavcodec_init(audio_codec_t audio_codec, audio_codec_direction_t direction, bool silent,
int bitrate)
{
ug_set_av_logging();
enum AVCodecID codec_id = AV_CODEC_ID_NONE;
const struct codec_param *it = &mapping[audio_codec];
if (!it->id) {
if (!silent) {
log_msg(LOG_LEVEL_ERROR, MOD_NAME "Cannot find mapping for codec \"%s\"!\n",
get_name_to_audio_codec(audio_codec));
}
return NULL;
} else {
codec_id = it->id;
}
#if LIBAVCODEC_VERSION_INT <= AV_VERSION_INT(58, 9, 100)
avcodec_register_all();
#endif
struct libavcodec_codec_state *s = calloc(1, sizeof *s);
s->magic = MAGIC;
s->direction = direction;
s->codec = direction == AUDIO_CODER
? init_encoder(codec_id, it->preferred_encoder, silent)
: init_decoder(codec_id);
if(!s->codec) {
if (!silent &&
(direction != AUDIO_CODER ||
get_commandline_param("audio-lavc-encoder") == NULL)) {
log_msg(LOG_LEVEL_ERROR, MOD_NAME "Your Libavcodec build doesn't contain codec \"%s\".\n",
get_name_to_audio_codec(audio_codec));
}
free(s);
return NULL;
}
if (!silent) {
log_msg(LOG_LEVEL_NOTICE, MOD_NAME "Using audio %scoder: %s\n",
(direction == AUDIO_CODER ? "en" : "de"), s->codec->name);
}
s->codec_ctx = avcodec_alloc_context3(s->codec);
if(!s->codec_ctx) { // not likely :)
if (!silent) {
log_msg(LOG_LEVEL_ERROR, MOD_NAME "Could not allocate audio codec context\n");
}
free(s);
return NULL;
}
s->codec_ctx->strict_std_compliance = -2;
s->bitrate = bitrate;
s->av_frame = av_frame_alloc();
s->pkt = av_packet_alloc();
s->tmp.data = s->tmp_data;
s->output_channel.data = s->output_channel_data;
if(direction == AUDIO_CODER) {
s->output_channel.codec = audio_codec;
} else {
s->output_channel.codec = AC_PCM;
}
return s;
}
/* check that a given sample format is supported by the encoder */
static int
check_sample_fmt(const AVCodecContext *ctx, enum AVSampleFormat sample_fmt)
{
const enum AVSampleFormat *p = avc_get_supported_sample_fmts(ctx, NULL);
while (*p != AV_SAMPLE_FMT_NONE) {
if (*p == sample_fmt)
return 1;
p++;
}
return 0;
}
static int
get_bitrate(struct libavcodec_codec_state *s)
{
if (s->bitrate > 0) {
MSG(INFO, "Setting bit rate to: %d bps\n", s->bitrate);
return s->bitrate;
}
const int default_rate =
mapping[s->output_channel.codec].default_bitrate;
if (default_rate > 0) {
MSG(INFO, "Setting defaulat bit rate: %d bps\n", default_rate);
return default_rate;
}
return 0;
}
static bool reinitialize_encoder(struct libavcodec_codec_state *s, struct audio_desc desc)
{
cleanup_common(s);
s->codec_ctx = avcodec_alloc_context3(s->codec);
if (s->codec_ctx == NULL) { // not likely :)
log_msg(LOG_LEVEL_ERROR, MOD_NAME "Could not allocate audio codec context\n");
return false;
}
s->codec_ctx->strict_std_compliance = -2;
/* put sample parameters */
s->codec_ctx->bit_rate = get_bitrate(s);
s->codec_ctx->sample_rate = desc.sample_rate;
enum AVSampleFormat sample_fmts[AV_SAMPLE_FMT_NB];
int count = 0;
switch(desc.bps) {
case 1:
sample_fmts[count++] = AV_SAMPLE_FMT_U8;
sample_fmts[count++] = AV_SAMPLE_FMT_U8P;
break;
case 2:
sample_fmts[count++] = AV_SAMPLE_FMT_S16;
sample_fmts[count++] = AV_SAMPLE_FMT_S16P;
break;
case 3:
case 4:
sample_fmts[count++] = AV_SAMPLE_FMT_S32;
sample_fmts[count++] = AV_SAMPLE_FMT_S32P;
sample_fmts[count++] = AV_SAMPLE_FMT_FLT;
sample_fmts[count++] = AV_SAMPLE_FMT_FLTP;
break;
}
s->codec_ctx->sample_fmt = AV_SAMPLE_FMT_NONE;
for (int i = 0; i < count; ++i) {
if (check_sample_fmt(s->codec_ctx, sample_fmts[i])) {
s->codec_ctx->sample_fmt = sample_fmts[i];
break;
}
}
if (s->codec_ctx->sample_fmt == AV_SAMPLE_FMT_NONE) {
int i = 0;
const enum AVSampleFormat *sample_fmts =
avc_get_supported_sample_fmts(s->codec_ctx, NULL);
while (sample_fmts[i] != AV_SAMPLE_FMT_NONE) {
if (sample_fmts[i] != AV_SAMPLE_FMT_DBL &&
sample_fmts[i] != AV_SAMPLE_FMT_DBLP) {
s->codec_ctx->sample_fmt = sample_fmts[i];
break;
}
i++;
}
}
if (s->codec_ctx->sample_fmt == AV_SAMPLE_FMT_NONE) {
log_msg(LOG_LEVEL_ERROR, "[Libavcodec] Unsupported audio sample!\n");
return false;
}
AVCODECCTX_CHANNELS(s->codec_ctx) = 1;
#if FF_API_NEW_CHANNEL_LAYOUT
s->codec_ctx->ch_layout = (struct AVChannelLayout) AV_CHANNEL_LAYOUT_MONO;
#else
s->codec_ctx->channel_layout = AV_CH_LAYOUT_MONO;
#endif
if (strcmp(s->codec->name, "libopus") == 0) {
int ret = av_opt_set(s->codec_ctx->priv_data, "application", "lowdelay", 0);
if (ret != 0) {
print_libav_audio_error(LOG_LEVEL_WARNING, "Could not set Opus low delay app type", ret);
}
} else if (strcmp(s->codec->name, "opus") == 0) {
char warn[] = MOD_NAME "Native FFmpeg Opus encoder seems to be currently broken "
"with UltraGrid. You may be able to use 'libopus' encoder instead. Please let "
"us know to " PACKAGE_BUGREPORT " if you either want to use the native encoder "
"or it even works for you.\n";
log_msg(LOG_LEVEL_WARNING, "%s", wrap_paragraph(warn));
int ret = av_opt_set_double(s->codec_ctx->priv_data, "opus_delay", 5, 0);
if (ret != 0) {
print_libav_audio_error(LOG_LEVEL_WARNING, "Cannot set Opus delay to 5", ret);
}
}
if (s->direction == AUDIO_CODER && (get_commandline_param("low-latency-audio") != NULL
|| get_commandline_param("audioenc-frame-duration") != NULL)) {
double frame_duration = get_commandline_param("audioenc-frame-duration") == NULL ?
LOW_LATENCY_AUDIOENC_FRAME_DURATION : atof(get_commandline_param("audioenc-frame-duration"));
if (s->codec->id == AV_CODEC_ID_OPUS) {
int ret = av_opt_set_double(s->codec_ctx->priv_data, "frame_duration", frame_duration, 0);
if (ret != 0) {
print_libav_audio_error(LOG_LEVEL_ERROR, "Could not set Opus frame duration", ret);
}
}
if (s->codec->id == AV_CODEC_ID_FLAC) {
s->codec_ctx->frame_size = desc.sample_rate * frame_duration / MS_IN_SEC;
}
}
/* open it */
int ret = avcodec_open2(s->codec_ctx, s->codec, NULL);
if (ret != 0) {
print_libav_audio_error(LOG_LEVEL_ERROR, "Could not open codec", ret);
return false;
}
if(s->codec->capabilities & AV_CODEC_CAP_VARIABLE_FRAME_SIZE) {
s->codec_ctx->frame_size = 1;
}
s->av_frame->nb_samples = s->codec_ctx->frame_size;
s->av_frame->format = s->codec_ctx->sample_fmt;
#if FF_API_NEW_CHANNEL_LAYOUT
s->av_frame->ch_layout = (struct AVChannelLayout) AV_CHANNEL_LAYOUT_MONO;
#else
s->av_frame->channel_layout = AV_CH_LAYOUT_MONO;
#endif
s->av_frame->sample_rate = s->codec_ctx->sample_rate;
MSG(VERBOSE,
"Setting AV frame: %d samples, format %d, sample rate %d\n",
s->av_frame->nb_samples, s->av_frame->format,
s->av_frame->sample_rate);
ret = av_frame_get_buffer(s->av_frame, 0);
if (ret != 0) {
print_libav_audio_error(LOG_LEVEL_ERROR, "Could not allocate audio data buffers", ret);
return false;
}
s->tmp.data_len = 0;
s->output_channel.sample_rate = desc.sample_rate;
s->output_channel.bps = av_get_bytes_per_sample(s->codec_ctx->sample_fmt);
s->saved_desc = desc;
s->context_initialized = true;
return true;
}
static bool reinitialize_decoder(struct libavcodec_codec_state *s, struct audio_desc desc)
{
cleanup_common(s);
s->codec_ctx = avcodec_alloc_context3(s->codec);
if (s->codec_ctx == NULL) { // not likely :)
log_msg(LOG_LEVEL_ERROR, MOD_NAME "Could not allocate audio codec context\n");
return false;
}
s->codec_ctx->strict_std_compliance = -2;
AVCODECCTX_CHANNELS(s->codec_ctx) = 1;
s->codec_ctx->bits_per_coded_sample = 4; // ADPCM
s->codec_ctx->sample_rate = desc.sample_rate;
s->codec_ctx->time_base = (AVRational) { 1, desc.sample_rate };
/* open it */
if (avcodec_open2(s->codec_ctx, s->codec, NULL) < 0) {
log_msg(LOG_LEVEL_ERROR, MOD_NAME "Could not open codec\n");
return false;
}
s->saved_desc = desc;
s->context_initialized = true;
return true;
}
static bool
process_input(struct libavcodec_codec_state *s, const audio_channel *channel)
{
if (channel == NULL) {
return true;
}
if (!audio_desc_eq(s->saved_desc,
audio_desc_from_audio_channel(channel))) {
if (!reinitialize_encoder(
s, audio_desc_from_audio_channel(channel))) {
MSG(ERROR, "Unable to reinitialize audio compress!\n");
return false;
}
}
if (s->output_channel.bps == channel->bps &&
s->codec_ctx->sample_fmt != AV_SAMPLE_FMT_FLT &&
s->codec_ctx->sample_fmt != AV_SAMPLE_FMT_FLTP) {
memcpy(s->tmp_data + s->tmp.data_len, channel->data,
channel->data_len);
s->tmp.data_len += channel->data_len;
return true;
}
if (s->codec_ctx->sample_fmt != AV_SAMPLE_FMT_FLT &&
s->codec_ctx->sample_fmt != AV_SAMPLE_FMT_FLTP) {
change_bps(s->tmp_data + s->tmp.data_len, s->output_channel.bps,
channel->data, s->saved_desc.bps, channel->data_len);
s->tmp.data_len += channel->data_len / s->saved_desc.bps *
s->output_channel.bps;
return true;
}
if (s->output_channel.bps == channel->bps) {
if (s->tmp.data_len + channel->data_len > TMP_DATA_LEN) {
MSG(ERROR, "Auxiliary buffer overflow!\n");
} else {
int2float(s->tmp_data + s->tmp.data_len, channel->data,
channel->data_len);
s->tmp.data_len += channel->data_len;
}
} else {
int data_len = channel->data_len / channel->bps * 4;
resize_tmp_buffer(&s->tmp_buffer, &s->tmp_buffer_size,
data_len);
change_bps((char *) s->tmp_buffer, 4, channel->data,
channel->bps, channel->data_len);
if (s->tmp.data_len + data_len > TMP_DATA_LEN) {
MSG(ERROR, "Auxiliary buffer overflow!\n");
} else {
int2float(s->tmp_data + s->tmp.data_len,
(char *) s->tmp_buffer, data_len);
s->tmp.data_len += data_len;
}
}
return true;
}
/// @returns timestamp of the channel minus already buffered frames (in s->tmp)
static int64_t
get_in_ts(const audio_channel *channel, int cached_samples)
{
if (channel->timestamp == -1) {
return AV_NOPTS_VALUE;
}
const int64_t ts = channel->timestamp * channel->sample_rate / kHz90;
return ts - cached_samples;
}
static void
set_out_ts(int recv_ret, struct libavcodec_codec_state *s)
{
if (recv_ret != 0 || s->output_channel.timestamp != -1 ||
s->pkt->pts < 0) {
return;
}
s->output_channel.timestamp =
(s->pkt->pts * kHz90 + s->codec_ctx->sample_rate - 1) /
s->codec_ctx->sample_rate;
}
static void
write_out_data(struct libavcodec_codec_state *s)
{
if (s->output_channel.data_len + s->pkt->size > TMP_DATA_LEN) {
MSG(ERROR, "Output buffer overflow!\n");
}
if (s->pkt->pts != AV_NOPTS_VALUE && s->pkt->pts < 0) {
return;
}
memcpy(s->output_channel_data + s->output_channel.data_len,
s->pkt->data, s->pkt->size);
s->output_channel.data_len += s->pkt->size;
s->output_channel.duration +=
s->codec_ctx->frame_size / (double) s->output_channel.sample_rate;
}
static audio_channel *libavcodec_compress(void *state, audio_channel * channel)
{
struct libavcodec_codec_state *s = (struct libavcodec_codec_state *) state;
assert(s->magic == MAGIC);
assert(s->codec_ctx->sample_fmt != AV_SAMPLE_FMT_DBL && // not supported yet
s->codec_ctx->sample_fmt != AV_SAMPLE_FMT_DBLP);
const int cached_bytes = s->tmp.data_len;
if (!process_input(s, channel)) {
return NULL;
}
if (channel != NULL) {
s->av_frame->pts =
get_in_ts(channel, cached_bytes / s->output_channel.bps);
}
int bps = s->output_channel.bps;
int offset = 0;
s->output_channel.data_len = 0;
s->output_channel.duration = 0.0;
s->output_channel.timestamp = -1;
int chunk_size = s->codec_ctx->frame_size * bps;
while(offset + chunk_size <= s->tmp.data_len) {
if (bps == 1) {
signed2unsigned((char *) s->av_frame->data[0], s->tmp.data + offset, chunk_size);
} else {
memcpy(s->av_frame->data[0], s->tmp.data + offset, chunk_size);
}
MSG(DEBUG2, "Sending PTS %" PRId64 "\n", s->av_frame->pts);
int ret = avcodec_send_frame(s->codec_ctx, s->av_frame);
if (ret != 0) {
print_libav_audio_error(LOG_LEVEL_ERROR, "Error encoding frame", ret);
return NULL;
}
if (s->av_frame->pts != AV_NOPTS_VALUE) {
s->av_frame->pts += s->codec_ctx->frame_size;
}
ret = avcodec_receive_packet(s->codec_ctx, s->pkt);
set_out_ts(ret, s);
while (ret == 0) {
write_out_data(s);
av_packet_unref(s->pkt);
ret = avcodec_receive_packet(s->codec_ctx, s->pkt);
}
if (ret != AVERROR(EAGAIN) && ret != 0) {
print_libav_audio_error(LOG_LEVEL_WARNING,
"Receive packet error", ret);
}
offset += chunk_size;
// since 2023-04-25, this may not be necessary (for AAC/MP3, it just triggers "Multiple frames in a packet." It seems not
// working only with native Opus encoder+decoder combination (which doesn't work anyways now). Consider removing this
// after some decent period (UG prior that date won't decode all frames for AAC when removed).
if(!(s->codec->capabilities & AV_CODEC_CAP_VARIABLE_FRAME_SIZE) && s->output_channel.data_len > 0)
break;
}
s->tmp.data_len -= offset;
memmove(s->tmp_data, s->tmp.data + offset, s->tmp.data_len);
///fprintf(stderr, "%d %d\n", i++% 2, s->output_channel.data_len);
if(s->output_channel.data_len) {
return &s->output_channel;
} else {
return NULL;
}
}
static audio_channel *libavcodec_decompress(void *state, audio_channel * channel)
{
struct libavcodec_codec_state *s = (struct libavcodec_codec_state *) state;
assert(channel->data_len > 0);
assert(s->magic == MAGIC);
if(!audio_desc_eq(s->saved_desc, audio_desc_from_audio_channel(channel))) {
if(!reinitialize_decoder(s, audio_desc_from_audio_channel(channel))) {
log_msg(LOG_LEVEL_ERROR, MOD_NAME "Unable to reinitialize audio decompress!\n");
return NULL;
}
}
// FFMPEG buffer needs to be FF_INPUT_BUFFER_PADDING_SIZE longer than data
resize_tmp_buffer(&s->tmp_buffer, &s->tmp_buffer_size, channel->data_len + AV_INPUT_BUFFER_PADDING_SIZE);
memcpy(s->tmp_buffer, channel->data, channel->data_len);
s->pkt->data = s->tmp_buffer;
s->pkt->size = channel->data_len;
s->output_channel.data_len = 0;
av_frame_unref(s->av_frame);
int ret = avcodec_send_packet(s->codec_ctx, s->pkt);
if (ret != 0) {
print_decoder_error(MOD_NAME "error sending decoded frame -", ret);
return NULL;
}
/* read all the output frames (in general there may be any number of them */
while (ret >= 0) {
ret = avcodec_receive_frame(s->codec_ctx, s->av_frame);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
break;
}
if (ret < 0) {
print_decoder_error(MOD_NAME "error receiving decoded frame -", ret);
return NULL;
}
int channels = 1;
/* if a frame has been decoded, output it */
int data_size = av_samples_get_buffer_size(NULL, channels,
s->av_frame->nb_samples,
s->codec_ctx->sample_fmt, 1);
memcpy(s->output_channel_data + s->output_channel.data_len, s->av_frame->data[0],
data_size);
s->output_channel.data_len += data_size;
s->pkt->dts = s->pkt->pts = AV_NOPTS_VALUE;
}
//
// perform needed conversions (float->int32, int32->dest_bps)
//
assert(s->codec_ctx->sample_fmt != AV_SAMPLE_FMT_DBL && // not supported yet
s->codec_ctx->sample_fmt != AV_SAMPLE_FMT_DBLP);
// convert from float if needed
if (s->codec_ctx->sample_fmt == AV_SAMPLE_FMT_FLT ||
s->codec_ctx->sample_fmt == AV_SAMPLE_FMT_FLTP) {
float2int(s->output_channel_data, s->output_channel.data, s->output_channel.data_len);
} else if (s->codec_ctx->sample_fmt == AV_SAMPLE_FMT_U8) {
signed2unsigned(s->output_channel_data, s->output_channel.data, s->output_channel.data_len);
}
s->output_channel.bps = av_get_bytes_per_sample(s->codec_ctx->sample_fmt);
s->output_channel.sample_rate = s->codec_ctx->sample_rate;
return &s->output_channel;
}
static const int *libavcodec_get_sample_rates(void *state)
{
struct libavcodec_codec_state *s = (struct libavcodec_codec_state *) state;
return avc_get_supported_sample_rates(s->codec_ctx, NULL);
}
static void cleanup_common(struct libavcodec_codec_state *s)
{
if (s->context_initialized) {
if (s->direction == AUDIO_DECODER) {
lavd_flush(s->codec_ctx);
} else {
int ret;
ret = avcodec_send_frame(s->codec_ctx, NULL);
if (ret != 0) {
log_msg(LOG_LEVEL_WARNING, MOD_NAME "Unexpected return value %d\n",
ret);
}
do {
ret = avcodec_receive_packet(s->codec_ctx, s->pkt);
av_packet_unref(s->pkt);
} while (ret >= 0 && ret != AVERROR_EOF && ret != AVERROR(EAGAIN));
if (ret < 0 && ret != AVERROR_EOF && ret != AVERROR(EAGAIN)) {
log_msg(LOG_LEVEL_WARNING, MOD_NAME "Unexpected return value %d\n",
ret);
}
}
av_frame_unref(s->av_frame);
}
avcodec_free_context(&s->codec_ctx);
s->context_initialized = false;
}
static void libavcodec_done(void *state)
{
struct libavcodec_codec_state *s = (struct libavcodec_codec_state *) state;
assert(s->magic == MAGIC);
cleanup_common(s);
av_frame_free(&s->av_frame);
av_packet_free(&s->pkt);
free(s->tmp_buffer);
free(s);
}
static const audio_codec_t supported_codecs[] = { AC_ALAW, AC_MULAW, AC_SPEEX, AC_OPUS, AC_G722, AC_FLAC, AC_MP3, AC_AAC, AC_NONE };
static void
append(char *dst, size_t dst_len, const char *name, bool prefered)
{
const char *const end = dst + dst_len - 1;
dst += strlen(dst);
strappend(&dst, end, " ");
if (prefered) {
strappend(&dst, end, "*");
}
strappend(&dst, end, TERM_BOLD);
strappend(&dst, end, name);
strappend(&dst, end, TERM_RESET);
*dst = '\0';
}
static void
get_encoders_decoders(enum AVCodecID id, const char *prefenc, char *buf,
size_t buflen)
{
char encoders[STR_LEN] = "";
char decoders[STR_LEN] = "";
const AVCodec *codec = NULL;
#if LIBAVCODEC_VERSION_INT > AV_VERSION_INT(58, 9, 100)
void *i = NULL;
while ((codec = av_codec_iterate(&i)) != NULL) {
if (codec->id != id) {
continue;
}
char *dst = av_codec_is_encoder(codec) ? encoders : decoders;
const bool prefered = av_codec_is_encoder(codec) &&
strcmp(codec->name, prefenc) == 0;
append(dst, sizeof encoders, codec->name, prefered);
}
#else
codec = avcodec_find_encoder(id);
while (codec != NULL) {
if (codec->id == id && av_codec_is_encoder(codec)) {
const bool prefered = strcmp(codec->name, prefenc) == 0;
append(encoders, sizeof encoders, codec->name,
prefered);
}
codec = av_codec_next(codec);
}
codec = avcodec_find_decoder(id);
while (codec != NULL) {
if (codec->id == id && av_codec_is_decoder(codec)) {
append(decoders, sizeof decoders, codec->name, false);
}
codec = av_codec_next(codec);
}
#endif
snprintf(buf, buflen, "encoders:%s decoders:%s", encoders, decoders);
}
static const char *
libavcodec_get_codec_desc(void *state, size_t buflen, char *buffer)
{
struct libavcodec_codec_state *s = state;
assert(buflen >= 1);
char *tmp = buffer;
const char *const end = buffer + buflen - 1;
if (s->output_channel.codec == AC_SPEEX) {
strappend(&tmp, end, TYELLOW("deprecated") "; ");
}
const char *prefenc =
mapping[s->output_channel.codec].preferred_encoder;
if (prefenc == NULL) {
prefenc = "";
}
get_encoders_decoders(mapping[s->output_channel.codec].id, prefenc,
tmp, end - tmp);
return buffer;
}
static const struct audio_compress_info libavcodec_audio_codec = {
supported_codecs,
libavcodec_init,
libavcodec_compress,
libavcodec_decompress,
libavcodec_get_sample_rates,
libavcodec_get_codec_desc,
libavcodec_done
};
REGISTER_MODULE(libavcodec, &libavcodec_audio_codec, LIBRARY_CLASS_AUDIO_COMPRESS, AUDIO_COMPRESS_ABI_VERSION);
/* vim: set expandtab sw=8 : */